mirror of
https://gitea.winboll.cc/Studio/OriginMaster.git
synced 2026-02-04 13:32:20 +08:00
Clone https://gitea.winboll.cc/Studio/WinBoLL_Bck20260112_122031_590.git WinBoLL 项目基础框架。
This commit is contained in:
101
.gitignore
vendored
Normal file
101
.gitignore
vendored
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
# Built application files
|
||||||
|
*.apk
|
||||||
|
*.aar
|
||||||
|
*.ap_
|
||||||
|
*.aab
|
||||||
|
|
||||||
|
# Files for the ART/Dalvik VM
|
||||||
|
*.dex
|
||||||
|
|
||||||
|
# Java class files
|
||||||
|
*.class
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
bin/
|
||||||
|
gen/
|
||||||
|
out/
|
||||||
|
# Uncomment the following line in case you need and you don't have the release build type files in your app
|
||||||
|
# release/
|
||||||
|
|
||||||
|
# Gradle files
|
||||||
|
.gradle/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# Local configuration file (sdk path, etc)
|
||||||
|
local.properties
|
||||||
|
|
||||||
|
# Proguard folder generated by Eclipse
|
||||||
|
proguard/
|
||||||
|
|
||||||
|
# Log Files
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Android Studio Navigation editor temp files
|
||||||
|
.navigation/
|
||||||
|
|
||||||
|
# Android Studio captures folder
|
||||||
|
captures/
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
*.iml
|
||||||
|
.idea/workspace.xml
|
||||||
|
.idea/tasks.xml
|
||||||
|
.idea/gradle.xml
|
||||||
|
.idea/assetWizardSettings.xml
|
||||||
|
.idea/dictionaries
|
||||||
|
.idea/libraries
|
||||||
|
# Android Studio 3 in .gitignore file.
|
||||||
|
.idea/caches
|
||||||
|
.idea/modules.xml
|
||||||
|
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
|
||||||
|
.idea/navEditor.xml
|
||||||
|
|
||||||
|
# Keystore files
|
||||||
|
# Uncomment the following lines if you do not want to check your keystore files in.
|
||||||
|
*.jks
|
||||||
|
*.keystore
|
||||||
|
|
||||||
|
# External native build folder generated in Android Studio 2.2 and later
|
||||||
|
.externalNativeBuild
|
||||||
|
.cxx/
|
||||||
|
|
||||||
|
# Google Services (e.g. APIs or Firebase)
|
||||||
|
# google-services.json
|
||||||
|
|
||||||
|
# Freeline
|
||||||
|
freeline.py
|
||||||
|
freeline/
|
||||||
|
freeline_project_description.json
|
||||||
|
|
||||||
|
# fastlane
|
||||||
|
fastlane/report.xml
|
||||||
|
fastlane/Preview.html
|
||||||
|
fastlane/screenshots
|
||||||
|
fastlane/test_output
|
||||||
|
fastlane/readme.md
|
||||||
|
|
||||||
|
# Version control
|
||||||
|
vcs.xml
|
||||||
|
|
||||||
|
# lint
|
||||||
|
lint/intermediates/
|
||||||
|
lint/generated/
|
||||||
|
lint/outputs/
|
||||||
|
lint/tmp/
|
||||||
|
# lint/reports/
|
||||||
|
|
||||||
|
# Android Profiling
|
||||||
|
*.hprof
|
||||||
|
|
||||||
|
# 忽略 Lint 输出文件
|
||||||
|
lint-results.xml
|
||||||
|
lint-results.html
|
||||||
|
|
||||||
|
## 忽略 AndroidIDE 临时文件夹
|
||||||
|
.androidide
|
||||||
|
|
||||||
|
## 忽略模块应用编译配置
|
||||||
|
/settings.gradle
|
||||||
|
/gradle.properties
|
||||||
|
/winboll.properties
|
||||||
|
/local.properties
|
||||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "libjc/jcc/libs"]
|
||||||
|
path = libjc/jcc/libs
|
||||||
|
url = https://gitea.winboll.cc/Studio/APP_libjc_jcc_libs.git
|
||||||
49
GenKeyStore/gen_debug_keystore.sh
Normal file
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
|
||||||
201
LICENSE
Normal file
201
LICENSE
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
165
README.md
Normal file
165
README.md
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
# OriginMaster
|
||||||
|
【OriginMaster】WinBoLL 源生态计划。正如话,我需要一个 Point, 去撬动成个地球。
|
||||||
|
|
||||||
|
########
|
||||||
|
## ☁ ☁ ☁ WinBoLL APP ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁
|
||||||
|
# ☁ ☁ WinBoLL Studio Android 应用开源项目。☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁
|
||||||
|
# ☁ ☁ ☁ WinBoLL 网站地址 https://www.winboll.cc/ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁
|
||||||
|
# ☁ ☁ ☁ WinBoLL 源码地址 <https://gitea.winboll.cc/Studio/WinBoLL.git> ☁ ☁ ☁ ☁ ☁ ☁ ☁
|
||||||
|
# ☁ ☁ ☁ GitHub 源码地址 <https://github.com/ZhanGSKen/WinBoLL.git> ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁
|
||||||
|
# ☁ ☁ ☁ 码云 源码地址 <https://gitee.com/zhangsken/winboll.git> ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁
|
||||||
|
# ☁ ☁ ☁ 在 jitpack.io 托管的 APPBase 类库源码<https://github.com/ZhanGSKen/APPBase.git> ☁ ☁ ☁ ☁
|
||||||
|
# ☁ ☁ ☁ 在 jitpack.io 托管的 AES 类库源码<https://github.com/ZhanGSKen/AES.git> ☁ ☁ ☁ ☁
|
||||||
|
## WinBoLL 提问
|
||||||
|
同样是 /sdcard 目录,在开发 Android 应用时,
|
||||||
|
能否实现手机编译与电脑编译的源码同步。
|
||||||
|
☁因而 WinBoLL 项目组诞生了。
|
||||||
|
|
||||||
|
## WinBoLL 项目组研发计划
|
||||||
|
致力于把 WinBoLL-APP 应用在手机端 Android 项目开发。
|
||||||
|
也在探索 https://gitea.winboll.cc/<WinBoLL 项目组>/APP.git 应用于 WinBoLL-APP APK 分发。
|
||||||
|
更想进阶 https://github.com/<WinBoLL 项目组>/APP.git 应用于 WinBoLL-APP Beta APK 分发。
|
||||||
|
|
||||||
|
## WinBoLL-APP 汗下...
|
||||||
|
#### ☁应用何置如此呢。且观用户云云。
|
||||||
|
|
||||||
|
#### ☁ 正当下 ☁ ###
|
||||||
|
#### ☁ 且容傻家叙说 ☁ WinBoLL-APP 应用场景
|
||||||
|
### ☁ WinBoLL 设备资源概述
|
||||||
|
#### ☁ 1. Raid Disk.
|
||||||
|
概述:这是一个矩阵存储类设备。
|
||||||
|
优点:该设备具有数据容错存储功能,
|
||||||
|
数据存储具有特长持久性。
|
||||||
|
缺点:设备使用能源消耗比较高,
|
||||||
|
设备存取速度一般。
|
||||||
|
|
||||||
|
#### ☁ 2. Data Disk.
|
||||||
|
概述:这是一个普通硬盘存储设备
|
||||||
|
优点:该设备独立于操作系统,
|
||||||
|
数据持久性一般,
|
||||||
|
存取能源消耗小于 Raid Disk。
|
||||||
|
缺点:数据存储速度一般,存储能源消耗一般。
|
||||||
|
|
||||||
|
#### ☁ 3. SSD Disk.
|
||||||
|
概述:这是一个 SSD 硬盘存储设备。
|
||||||
|
优点:存取速度快于 Data Disk 与 Raid Disk,
|
||||||
|
存取能源消耗小于 Data Disk 与 Raid Disk。
|
||||||
|
缺点:数据持久性一般,
|
||||||
|
设备位于操作系统内部文件系统。
|
||||||
|
数据持久性与操作系统挂钩。
|
||||||
|
|
||||||
|
#### ☁ 4. WinBoLL 用户资源概述。
|
||||||
|
1> /home/<用户名> 位于 WinBoLL 操作系统目录下。
|
||||||
|
2> /rdisk/<用户名> 挂载用户 Raid Disk.
|
||||||
|
3> /data/<用户名> 挂载用户 Data Disk.
|
||||||
|
4> /sdcard/<用户名> 挂载用户 SSD Disk.
|
||||||
|
|
||||||
|
#### ☁ 5. WinBoLL-APP 用户资源概述。
|
||||||
|
1> /sdcard 挂载用户手机 SD 存储/storage/emulated/0
|
||||||
|
|
||||||
|
### ☁ 稍稍歇 ☁ ###
|
||||||
|
### ☁ 急急停 ☁ WinBoLL 应用前置条件
|
||||||
|
☁ WinBoLL 主机建立 1Panel MySQL 应用。
|
||||||
|
☁ WinBoLL 主机建立 1Panel Gitea 应用。
|
||||||
|
☁ WinBoLL 主机设置 WinBoLL 应用为非登录状态。
|
||||||
|
☁ WinBoLL 主机建立 WinBoLL 账户与 WinBoLL 用户组。
|
||||||
|
☁ WinBoLL 账户 User ID 为: J。
|
||||||
|
☁ WinBoLL 用户组 Group ID 为: Studio。
|
||||||
|
☁ WinBoLL 主机 WinBoLL 1Panel Gitea 建立 WinBoLL 工作组。
|
||||||
|
☁ WinBoLL 主机 WinBoLL 1Panel Gitea 用户项目 APK 编译输出目录为 /sdcard/WinBoLLStudio/<用户名>/APKs/
|
||||||
|
☁ WinBoLL 项目配置文件示例为 "<WinBoLL 项目根目录>/.winboll/winboll.properties-demo"(WinBoLL 项目已设置)
|
||||||
|
☁ WinBoLL 项目配置文件为 "<WinBoLL 项目根目录>/.winboll/winboll.properties"
|
||||||
|
☁ WinBoLL 项目配置文件设定为源码提交时忽略。(WinBoLL 项目已设置)
|
||||||
|
☁ Gradle 项目配置文件示例为 "<WinBoLL 项目根目录>/.winboll/local.properties-demo"(WinBoLL 项目已设置)
|
||||||
|
☁ Gradle 项目配置文件为 "<WinBoLL 项目根目录>/local.properties"(WinBoLL 项目已设置)
|
||||||
|
☁ Gradle 项目配置文件设定为源码提交时忽略。(WinBoLL 项目已设置)
|
||||||
|
|
||||||
|
### ☁ 登高处 ☁ WinBoLL 应用需求规划
|
||||||
|
☁ WinBoLL 主机建立 WinBoLL 客户端用户数据库为 MySQL winbollclient 数据库。
|
||||||
|
☁ WinBoLL 主机设置 WinBoLL 客户端用户信息存储在 winbollclient 数据库中。
|
||||||
|
☁ MySQL winbollclient 数据库中
|
||||||
|
WinBoLL 客户端用户信息设定为:
|
||||||
|
<用户名, 验证密码, 验证邮箱, 验证手机, 唯一存储令牌Token, 备用验证邮箱>。
|
||||||
|
☁ WinBoLL 项目源码仓库托管在 WinBoLL 1Panel Gitea 目录 /opt/1panel/apps/gitea/gitea/data/git/repositories/studio/app.git中。
|
||||||
|
☁ WinBoLL 主机提供 WinBoLL 1Panel Gitea 应用的 WinBoLL 项目源码仓库存取功能。(Gitea 应用已提供)
|
||||||
|
☁ WinBoLL 主机提供 WinBoLL Gitea 项目仓库存档功能。(Gitea 应用已提供)
|
||||||
|
☁ 提供 WinBoLL 客户端用户登录功能。(Gitea 应用已提供)
|
||||||
|
|
||||||
|
### ☁ 看远方 ☁ ###
|
||||||
|
### ☁ 心忧虑 ☁ WinBoLL-APP 应用前置需求
|
||||||
|
☁ WinBoLL-APP WinBoLL 项目根目录设定为手机的 /sdcard/WinBoLLStudio/Sources 目录。(需要用户手动建立文件夹)
|
||||||
|
☁ WinBoLL-APP 具有手机 /sdcard/WinBoLL 目录的存储权限。(需要手机操作系统授权)
|
||||||
|
☁ WinBoLL-APP WinBoLL 项目仓库源码存储路径为 /sdcard/WinBoLLStudio/Sources/APP.git(需要用户手动建立文件夹)
|
||||||
|
☁ WinBoLL-APP 项目 APK 编译输出目录为 /sdcard/WinBoLLStudio/APKs/
|
||||||
|
☁ WinBoLL-APP 应用签名验证可定制化。(WinBoLL 项目已提供)
|
||||||
|
☁ WinBoLL-APP 与系列衍生 APP 应用共享 cc.winboll.studio 命名空间资源。(WinBoLL 项目已提供)
|
||||||
|
☁ WinBoLL-APP 用户客户端信息存储在命名空间为 WinBoLL APP MySQLLite 应用的 winbollappclient 数据库中。
|
||||||
|
☁ WinBoLL-APP MySQLLite 应用的 winbollappclient 数据库中,
|
||||||
|
WinBoLL 用户客户端信息设定为:
|
||||||
|
<用户名, 唯一存储令牌Token>。
|
||||||
|
|
||||||
|
### ☁ 云游四方 ☁ ###
|
||||||
|
### ☁ 呔! ☁ WinBoLL-APP 应用需求规划
|
||||||
|
☁ 如要使用 WinBoLL Android 项目的 Gradle 编译功能,则需要设置以下两个文件夹。
|
||||||
|
☁ 1. 则需要建立数据存储目录 /sdcard/WinBoLLStudio/APKs。
|
||||||
|
WinBoLL 项目源码编译出来的安装包会拷贝一份到 /sdcard/WinBoLLStudio/APKs 目录下。
|
||||||
|
☁ 2. 则需要建立数据存储目录 /sdcard/AppProjects。
|
||||||
|
WinBoLL 项目源码编译出来的安装包会拷贝一份并命名 "app.apk" 的安装文件为到 /sdcard/AppProjects 目录下。
|
||||||
|
|
||||||
|
|
||||||
|
### ☁ 吁! ☁ WinBoLL-APP 共享计划前景
|
||||||
|
☁ WinBoLL-APP 将会实现 https://winboll.cc/api 访问功能。
|
||||||
|
☁ WinBoLL-APP 将会实现手机端 Android 应用的开发与管理功能。
|
||||||
|
|
||||||
|
## ☁ WinBoLL ☁ WinBoLL 主机忧虑
|
||||||
|
☁ WinBoLL 将会提供 gitea.winboll.cc 域名用户注册登录功能。
|
||||||
|
☁ WinBoLL 将会提供 WinBoLL-APP 及其衍生应用的 Gitea 仓库管理服务。
|
||||||
|
☁ WinBoLL 将会提供 winboll.cc 域名 WinBoLL 项目组注册登录功能。
|
||||||
|
|
||||||
|
# 本项目要实际运用需要注意以下几个步骤:
|
||||||
|
# 在项目根目录下:
|
||||||
|
## ★. 项目模块编译环境设置(必须),settings.gradle-demo 要复制为 settings.gradle,并取消相应项目模块的注释。
|
||||||
|
## ★. 项目模块编译环境设置(必须) 在根目录拷贝 gradle.properties-androidx-demo 或者 gradle.properties-android-demo 文件为 gradle.properties。
|
||||||
|
## ★. 项目 Android SDK 编译环境设置(可选),local.properties-demo 要复制为 local.properties,并按需要设置 Android SDK 目录。
|
||||||
|
## ★. 应用签名密钥 keystore 设置问题。一般调试编译只需用【Termux】cd 进 GenKeyStore 目录执行 $ bash gen_debug_keystore.sh 命令即可完成设置。
|
||||||
|
## ☆. 应用 WiBoLL 签名密钥配置问题<非必须考虑>。设置时需要 clone 【keystore】模块源码并拷贝模块目录的 appkey.jks 与 appkey.keystore 到项目根目录即可。
|
||||||
|
## ☆. 类库型模块编译环境设置(可选),winboll.properties-demo 要复制为 winboll.properties,并按需要设置 WinBoLL Maven 库登录用户信息, 和 APK 文件额外输出路径。
|
||||||
|
|
||||||
|
|
||||||
|
# ☆类库型项目编译方法
|
||||||
|
## 先编译类库对应的模块测试项目
|
||||||
|
### 修改模块测试项目的 build.properties 文件
|
||||||
|
设置属性 libraryProject=<类库项目模块文件夹名称>
|
||||||
|
### 再编译测试项目
|
||||||
|
$ bash .winboll/bashPublishAPKAddTag.sh <应用项目模块文件夹名称>
|
||||||
|
#### 测试项目编译后,编译器会复制一份 APK 到 路径:"/sdcard/WinBoLLStudio/APKs/<项目根目录名称>/tag/" 文件夹。
|
||||||
|
#### 若是 winboll.properties 文件的 [ExtraAPKOutputPath] 属性设置了路径。编译器也会复制一份 APK 到这个路径。
|
||||||
|
### 最后编译类库项目
|
||||||
|
$ bash .winboll/bashPublishLIBAddTag.sh <类库项目模块文件夹名称>
|
||||||
|
#### 类库模块编译命令执行后,编译器会发布到 WinBoLL Nexus Maven 库:Maven 库地址可以参阅根项目目录配置 build.gradle 文件。
|
||||||
|
|
||||||
|
# ☆应用型项目编译方法
|
||||||
|
## 直接调用以下命令编译应用型项目
|
||||||
|
$ bash .winboll/bashPublishAPKAddTag.sh <应用项目模块文件夹名称>
|
||||||
|
#### 应用模块编译命令执行后,编译器会复制一份 APK 到
|
||||||
|
#### 测试项目编译后,编译器会复制一份 APK 到 路径:"/sdcard/WinBoLLStudio/APKs/<项目根目录名称>/tag/" 文件夹。
|
||||||
|
#### 若是 winboll.properties 文件的 [ExtraAPKOutputPath] 属性设置了路径。编译器也会复制一份 APK 到这个路径。
|
||||||
|
|
||||||
|
## ☆应用调试编译方法
|
||||||
|
使用以下命令编译调试:
|
||||||
|
|
||||||
|
### Beta调试使用:
|
||||||
|
$ bash gradlew assembleBetaDebug
|
||||||
|
|
||||||
|
### Stage调试使用:
|
||||||
|
$ bash gradlew assembleStageDebug
|
||||||
|
|
||||||
|
### 若是 winboll.properties 文件的 [ExtraAPKOutputPath] 属性设置了路径。编译器也会复制一份 APK 到这个路径。
|
||||||
|
|
||||||
|
# 应用版本号命名方式
|
||||||
|
## statge 渠道
|
||||||
|
V<应用开发环境编号><应用功能变更号><应用调试阶段号>
|
||||||
|
如:APPBase_15.7.0
|
||||||
|
## beta 渠道
|
||||||
|
V<应用开发环境编号><应用功能变更号><应用调试阶段号>-beta<调试编译计数>_<调试编译时间(分钟+秒钟)>
|
||||||
|
如:APPBase_15.9.6-beta8_5413
|
||||||
117
build.gradle
Normal file
117
build.gradle
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
mavenLocal {
|
||||||
|
// 设置本地Maven仓库路径
|
||||||
|
url 'file:///sdcard/.m2/repository/'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nexus Maven 库地址
|
||||||
|
// "WinBoLL Release"
|
||||||
|
maven { url "https://nexus.winboll.cc/repository/maven-public/" }
|
||||||
|
// "WinBoLL Snapshot"
|
||||||
|
maven { url "https://nexus.winboll.cc/repository/maven-snapshots/" }
|
||||||
|
|
||||||
|
maven { url 'https://maven.aliyun.com/repository/public/' }
|
||||||
|
maven { url 'https://maven.aliyun.com/repository/google/' }
|
||||||
|
maven { url 'https://maven.aliyun.com/repository/gradle-plugin/' }
|
||||||
|
maven { url 'https://dl.bintray.com/ppartisan/maven/' }
|
||||||
|
maven { url "https://clojars.org/repo/" }
|
||||||
|
maven { url "https://jitpack.io" }
|
||||||
|
mavenCentral()
|
||||||
|
google()
|
||||||
|
//println "mavenLocal : ==========="
|
||||||
|
//println mavenLocal().url
|
||||||
|
//println "mavenLocal : ==========="
|
||||||
|
//mavenLocal()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
// 适配MIUI12
|
||||||
|
classpath 'com.android.tools.build:gradle:7.2.1' // 对应 compileSdkVersion 32
|
||||||
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
|
// in the individual module build.gradle files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
mavenLocal {
|
||||||
|
// 设置本地Maven仓库路径
|
||||||
|
url 'file:///sdcard/.m2/repository/'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nexus Maven 库地址
|
||||||
|
// "WinBoLL Release"
|
||||||
|
maven { url "https://nexus.winboll.cc/repository/maven-public/" }
|
||||||
|
// "WinBoLL Snapshot"
|
||||||
|
maven { url "https://nexus.winboll.cc/repository/maven-snapshots/" }
|
||||||
|
|
||||||
|
maven { url 'https://maven.aliyun.com/repository/public/' }
|
||||||
|
maven { url 'https://maven.aliyun.com/repository/google/' }
|
||||||
|
maven { url 'https://maven.aliyun.com/repository/gradle-plugin/' }
|
||||||
|
maven { url 'https://dl.bintray.com/ppartisan/maven/' }
|
||||||
|
maven { url "https://clojars.org/repo/" }
|
||||||
|
maven { url "https://jitpack.io" }
|
||||||
|
mavenCentral()
|
||||||
|
google()
|
||||||
|
//println "mavenLocal : ==========="
|
||||||
|
//println mavenLocal().url
|
||||||
|
//println "mavenLocal : ==========="
|
||||||
|
//mavenLocal()
|
||||||
|
}
|
||||||
|
ext {
|
||||||
|
// 定义全局变量,常用于版本管理
|
||||||
|
// 变量在子模块的build.gradle中直接以: $NAME 的形式调用
|
||||||
|
// 定义版本管理文件
|
||||||
|
RootProjectDir = "${rootProject.projectDir}".replace("\\", "/")
|
||||||
|
bashCheckGitCommitStatusFilePath = ".winboll/bashCheckGitCommitStatus.sh"
|
||||||
|
bashCommitAppPublishBuildFlagInfoFilePath = ".winboll/bashCommitAppPublishBuildFlagInfo.sh"
|
||||||
|
|
||||||
|
winbollFilePath = "winboll.properties"
|
||||||
|
keyPropsFilePath = "appkey.keystore"
|
||||||
|
// 定义 lint 输出文件
|
||||||
|
lintXmlReportFilePath = "build/reports/lint-results.xml"
|
||||||
|
lintHTMLReportFilePath = "build/reports/lint-results.html"
|
||||||
|
|
||||||
|
// 检查编译配置文件
|
||||||
|
subProjectPath = "$RootProjectDir/$project.name"
|
||||||
|
println "Sub project path: $subProjectPath"
|
||||||
|
winbollBuildPropsDesc="Created by .winboll/winboll_app_build.gradle"
|
||||||
|
winbollBuildPropsFile = rootProject.file("$subProjectPath/build.properties")
|
||||||
|
winbollBuildPropsFilePath = winbollBuildPropsFile.getAbsolutePath();
|
||||||
|
assert(winbollBuildPropsFile.exists())
|
||||||
|
winbollBuildProps = new Properties()
|
||||||
|
// 读取编译标志位配置文件
|
||||||
|
winbollBuildProps.load(new FileInputStream(winbollBuildPropsFile))
|
||||||
|
// 读取编译标志位配置文件
|
||||||
|
assert (winbollBuildProps['stageCount'] != null)
|
||||||
|
assert (winbollBuildProps['baseVersion'] != null)
|
||||||
|
assert (winbollBuildProps['publishVersion'] != null)
|
||||||
|
assert (winbollBuildProps['buildCount'] != null)
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEvaluate {
|
||||||
|
task cleanLintFile() {
|
||||||
|
dependsOn tasks.findAll { task -> task.name.startsWith('lint') }
|
||||||
|
doFirst {
|
||||||
|
// 这里是将在Lint任务开始前执行的代码
|
||||||
|
println "Lint task will run after this setup."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subprojects {
|
||||||
|
// 1. 对纯 Java 模块的 JavaCompile 任务配置(升级为 Java 11)
|
||||||
|
tasks.withType(JavaCompile) {
|
||||||
|
options.compilerArgs << "-parameters"
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
// 可选:确保编码一致
|
||||||
|
options.encoding = "UTF-8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task clean(type: Delete) {
|
||||||
|
delete rootProject.buildDir
|
||||||
|
}
|
||||||
21
gradle.properties-android-demo
Normal file
21
gradle.properties-android-demo
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Project-wide Gradle settings.
|
||||||
|
# IDE (e.g. Android Studio) users:
|
||||||
|
# Gradle settings configured through the IDE *will override*
|
||||||
|
# any settings specified in this file.
|
||||||
|
# For more details on how to configure your build environment visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||||
|
# Specifies the JVM arguments used for the daemon process.
|
||||||
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
|
org.gradle.jvmargs=-Xmx2048m
|
||||||
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
|
# This option should only be used with decoupled projects. More details, visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||||
|
# org.gradle.parallel=true
|
||||||
|
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||||
|
# Android operating system, and which are packaged with your app"s APK
|
||||||
|
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||||
|
android.useAndroidX=false
|
||||||
|
# Automatically convert third-party libraries to use AndroidX
|
||||||
|
android.enableJetifier=false
|
||||||
|
# 保持与旧版Gradle插件的兼容
|
||||||
|
android.disableAutomaticComponentCreation=true
|
||||||
21
gradle.properties-androidx-demo
Normal file
21
gradle.properties-androidx-demo
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Project-wide Gradle settings.
|
||||||
|
# IDE (e.g. Android Studio) users:
|
||||||
|
# Gradle settings configured through the IDE *will override*
|
||||||
|
# any settings specified in this file.
|
||||||
|
# For more details on how to configure your build environment visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||||
|
# Specifies the JVM arguments used for the daemon process.
|
||||||
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
|
org.gradle.jvmargs=-Xmx2048m
|
||||||
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
|
# This option should only be used with decoupled projects. More details, visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||||
|
# org.gradle.parallel=true
|
||||||
|
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||||
|
# Android operating system, and which are packaged with your app"s APK
|
||||||
|
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||||
|
android.useAndroidX=true
|
||||||
|
# Automatically convert third-party libraries to use AndroidX
|
||||||
|
android.enableJetifier=true
|
||||||
|
# 保持与旧版Gradle插件的兼容
|
||||||
|
android.disableAutomaticComponentCreation=true
|
||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl = https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
185
gradlew
vendored
Normal file
185
gradlew
vendored
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright 2015 the original author or authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
##
|
||||||
|
## Gradle start up script for UN*X
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MSYS* | MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
NONSTOP* )
|
||||||
|
nonstop=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||||
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
|
if [ $? -eq 0 ] ; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
|
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW ; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=`expr $i + 1`
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
0) set -- ;;
|
||||||
|
1) set -- "$args0" ;;
|
||||||
|
2) set -- "$args0" "$args1" ;;
|
||||||
|
3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Escape application args
|
||||||
|
save () {
|
||||||
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
|
echo " "
|
||||||
|
}
|
||||||
|
APP_ARGS=`save "$@"`
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
||||||
8
local.properties-demo
Normal file
8
local.properties-demo
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
## This file must *NOT* be checked into Version Control Systems,
|
||||||
|
# as it contains information specific to your local configuration.
|
||||||
|
#
|
||||||
|
# Location of the SDK. This is only used by Gradle.
|
||||||
|
# For customization when using a Version Control System, please read the
|
||||||
|
# header note.
|
||||||
|
#Sat Apr 27 01:35:26 CST 2024
|
||||||
|
#sdk.dir=
|
||||||
79
settings.gradle-demo
Normal file
79
settings.gradle-demo
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
// AutoInstaller 项目编译设置
|
||||||
|
//include ':autoinstaller'
|
||||||
|
//rootProject.name = "autoinstaller"
|
||||||
|
|
||||||
|
// MJ 项目编译设置
|
||||||
|
//include ':mj'
|
||||||
|
//rootProject.name = "mj"
|
||||||
|
|
||||||
|
// PowerBell 项目编译设置
|
||||||
|
//include ':powerbell'
|
||||||
|
//rootProject.name = "powerbell"
|
||||||
|
|
||||||
|
// APPBase 项目编译设置
|
||||||
|
//include ':appbase'
|
||||||
|
//include ':libappbase'
|
||||||
|
//rootProject.name = "appbase"
|
||||||
|
|
||||||
|
// APPUtils 项目编译设置
|
||||||
|
//include ':apputils'
|
||||||
|
//include ':libapputils'
|
||||||
|
//rootProject.name = "apputils"
|
||||||
|
|
||||||
|
// JC 项目编译设置
|
||||||
|
//include ':jc'
|
||||||
|
//include ':libjc'
|
||||||
|
//rootProject.name = "jc"
|
||||||
|
|
||||||
|
// AES 项目编译设置
|
||||||
|
//include ':aes'
|
||||||
|
//include ':libaes'
|
||||||
|
//rootProject.name = "aes"
|
||||||
|
|
||||||
|
// Contacts 项目编译设置
|
||||||
|
//include ':contacts'
|
||||||
|
//rootProject.name = "contacts"
|
||||||
|
|
||||||
|
// MyMessageManager 项目编译设置
|
||||||
|
//include ':mymessagemanager'
|
||||||
|
//rootProject.name = "mymessagemanager"
|
||||||
|
|
||||||
|
// TimeStamp 项目编译设置
|
||||||
|
//include ':timestamp'
|
||||||
|
//rootProject.name = "timestamp"
|
||||||
|
|
||||||
|
// AndroidDemo 项目编译设置
|
||||||
|
//include ':androiddemo'
|
||||||
|
//rootProject.name = "androiddemo"
|
||||||
|
|
||||||
|
// AndroidXDemo 项目编译设置
|
||||||
|
//include ':androidxdemo'
|
||||||
|
//rootProject.name = "androidxdemo"
|
||||||
|
|
||||||
|
// Ollama 项目编译设置
|
||||||
|
//include ':ollama'
|
||||||
|
//rootProject.name = "ollama"
|
||||||
|
|
||||||
|
// NumTable 项目编译设置
|
||||||
|
//include ':numtable'
|
||||||
|
//rootProject.name = "numtable"
|
||||||
|
|
||||||
|
// MidiPlayer 项目编译设置
|
||||||
|
//include ':midiplayer'
|
||||||
|
//rootProject.name = "midiplayer"
|
||||||
|
|
||||||
|
// WebPageSources 项目编译设置
|
||||||
|
//include ':webpagesources'
|
||||||
|
//rootProject.name = "webpagesources"
|
||||||
|
|
||||||
|
// Positions 项目编译设置
|
||||||
|
//include ':positions'
|
||||||
|
//rootProject.name = "positions"
|
||||||
|
|
||||||
|
// WinBoLL 项目编译设置
|
||||||
|
//include ':winboll'
|
||||||
|
//rootProject.name = "winboll"
|
||||||
|
|
||||||
|
// RegExpUtils 项目编译设置
|
||||||
|
//include ':regexputils'
|
||||||
|
//rootProject.name = "regexputils"
|
||||||
13
winboll.properties-demo
Normal file
13
winboll.properties-demo
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
## WinBoLL Nexus Settings
|
||||||
|
## These properties is setting for publishing
|
||||||
|
## library project to WinBoLL Nexus Maven Repository.
|
||||||
|
##
|
||||||
|
|
||||||
|
## WinBoLL Extra APK file Output Path
|
||||||
|
#ExtraAPKOutputPath=/sdcard/AppProjects/app.apk
|
||||||
|
|
||||||
|
## WinBoLL Nexus UserName
|
||||||
|
#Nexus.name=nexustestuser1
|
||||||
|
|
||||||
|
## WinBoLL Nexus Password
|
||||||
|
#Nexus.password=nexustestuserpassword
|
||||||
34
winboll/README.md
Normal file
34
winboll/README.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# WinBoLL
|
||||||
|
|
||||||
|
#### 介绍
|
||||||
|
WinBoLL 网站浏览器。
|
||||||
|
|
||||||
|
#### 软件架构
|
||||||
|
适配安卓应用 [AIDE Pro] 的 Gradle 编译结构。
|
||||||
|
也适配安卓应用 [AndroidIDE] 的 Gradle 编译结构。
|
||||||
|
|
||||||
|
|
||||||
|
#### Gradle 编译说明
|
||||||
|
调试版编译命令 :gradle assembleBetaDebug
|
||||||
|
阶段版编译命令 :bash .winboll/bashPublishAPKAddTag.sh winboll
|
||||||
|
|
||||||
|
#### 使用说明
|
||||||
|
|
||||||
|
#### 参与贡献
|
||||||
|
|
||||||
|
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
winboll/app_update_description.txt
Normal file
1
winboll/app_update_description.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
101
winboll/build.gradle
Normal file
101
winboll/build.gradle
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
apply plugin: 'com.android.application'
|
||||||
|
apply from: '../.winboll/winboll_app_build.gradle'
|
||||||
|
apply from: '../.winboll/winboll_lint_build.gradle'
|
||||||
|
|
||||||
|
def genVersionName(def versionName){
|
||||||
|
// 检查编译标志位配置
|
||||||
|
assert (winbollBuildProps['stageCount'] != null)
|
||||||
|
assert (winbollBuildProps['baseVersion'] != null)
|
||||||
|
// 保存基础版本号
|
||||||
|
winbollBuildProps.setProperty("baseVersion", "${versionName}");
|
||||||
|
//保存编译标志配置
|
||||||
|
FileOutputStream fos = new FileOutputStream(winbollBuildPropsFile)
|
||||||
|
winbollBuildProps.store(fos, "${winbollBuildPropsDesc}");
|
||||||
|
fos.close();
|
||||||
|
|
||||||
|
// 返回编译版本号
|
||||||
|
return "${versionName}." + winbollBuildProps['stageCount']
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
// 适配MIUI12
|
||||||
|
compileSdkVersion 30
|
||||||
|
buildToolsVersion "30.0.3"
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "cc.winboll.studio.winboll"
|
||||||
|
minSdkVersion 23
|
||||||
|
// 适配MIUI12
|
||||||
|
targetSdkVersion 30
|
||||||
|
versionCode 1
|
||||||
|
// versionName 更新后需要手动设置
|
||||||
|
// .winboll/winbollBuildProps.properties 文件的 stageCount=0
|
||||||
|
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
|
||||||
|
versionName "15.11"
|
||||||
|
if(true) {
|
||||||
|
versionName = genVersionName("${versionName}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 米盟 SDK
|
||||||
|
packagingOptions {
|
||||||
|
doNotStrip "*/*/libmimo_1011.so"
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
jniLibs.srcDirs = ['libs'] // 若SO库放在libs目录下
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
api 'com.google.code.gson:gson:2.10.1'
|
||||||
|
|
||||||
|
// 下拉控件
|
||||||
|
api 'com.baoyz.pullrefreshlayout:library:1.2.0'
|
||||||
|
|
||||||
|
// SSH
|
||||||
|
api 'com.jcraft:jsch:0.1.55'
|
||||||
|
// Html 解析
|
||||||
|
api 'org.jsoup:jsoup:1.13.1'
|
||||||
|
// 二维码类库
|
||||||
|
api 'com.google.zxing:core:3.4.1'
|
||||||
|
api 'com.journeyapps:zxing-android-embedded:3.6.0'
|
||||||
|
// 应用介绍页类库
|
||||||
|
api 'io.github.medyo:android-about-page:2.0.0'
|
||||||
|
// 网络连接类库
|
||||||
|
api 'com.squareup.okhttp3:okhttp:4.4.1'
|
||||||
|
// OkHttp网络请求
|
||||||
|
implementation 'com.squareup.okhttp3:okhttp:3.14.9'
|
||||||
|
// FastJSON解析
|
||||||
|
implementation 'com.alibaba:fastjson:1.2.76'
|
||||||
|
|
||||||
|
// AndroidX 类库
|
||||||
|
api 'androidx.appcompat:appcompat:1.1.0'
|
||||||
|
//api 'com.google.android.material:material:1.4.0'
|
||||||
|
//api 'androidx.viewpager:viewpager:1.0.0'
|
||||||
|
//api 'androidx.vectordrawable:vectordrawable:1.1.0'
|
||||||
|
//api 'androidx.vectordrawable:vectordrawable-animated:1.1.0'
|
||||||
|
//api 'androidx.fragment:fragment:1.1.0'
|
||||||
|
|
||||||
|
// 米盟
|
||||||
|
api 'com.miui.zeus:mimo-ad-sdk:5.3.+'//请使用最新版sdk
|
||||||
|
//注意:以下5个库必须要引入
|
||||||
|
//implementation 'androidx.appcompat:appcompat:1.4.1'
|
||||||
|
api 'androidx.recyclerview:recyclerview:1.0.0'
|
||||||
|
api 'com.google.code.gson:gson:2.8.5'
|
||||||
|
api 'com.github.bumptech.glide:glide:4.9.0'
|
||||||
|
//annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
|
||||||
|
|
||||||
|
// WinBoLL库 nexus.winboll.cc 地址
|
||||||
|
api 'cc.winboll.studio:libaes:15.12.13'
|
||||||
|
api 'cc.winboll.studio:libappbase:15.14.2'
|
||||||
|
|
||||||
|
// WinBoLL备用库 jitpack.io 地址
|
||||||
|
//api 'com.github.ZhanGSKen:AES:aes-v15.12.9'
|
||||||
|
//api 'com.github.ZhanGSKen:APPBase:appbase-v15.14.1'
|
||||||
|
|
||||||
|
api fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
}
|
||||||
8
winboll/build.properties
Normal file
8
winboll/build.properties
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#Created by .winboll/winboll_app_build.gradle
|
||||||
|
#Sun Jan 11 04:13:06 GMT 2026
|
||||||
|
stageCount=9
|
||||||
|
libraryProject=
|
||||||
|
baseVersion=15.11
|
||||||
|
publishVersion=15.11.8
|
||||||
|
buildCount=14
|
||||||
|
baseBetaVersion=15.11.9
|
||||||
BIN
winboll/libs/libWeWorkSpecSDK.so
Normal file
BIN
winboll/libs/libWeWorkSpecSDK.so
Normal file
Binary file not shown.
137
winboll/proguard-rules.pro
vendored
Normal file
137
winboll/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# By default, the flags in this file are appended to flags specified
|
||||||
|
# in C:\tools\adt-bundle-windows-x86_64-20131030\sdk/tools/proguard/proguard-android.txt
|
||||||
|
# You can edit the include path and order by changing the proguardFiles
|
||||||
|
# directive in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# Add any project specific keep options here:
|
||||||
|
|
||||||
|
# ============================== 基础通用规则 ==============================
|
||||||
|
# 保留系统组件
|
||||||
|
-keep public class * extends android.app.Activity
|
||||||
|
-keep public class * extends android.app.Service
|
||||||
|
-keep public class * extends android.content.BroadcastReceiver
|
||||||
|
-keep public class * extends android.content.ContentProvider
|
||||||
|
-keep public class * extends android.app.backup.BackupAgentHelper
|
||||||
|
-keep public class * extends android.preference.Preference
|
||||||
|
|
||||||
|
# 保留 WinBoLL 核心包及子类(合并简化规则)
|
||||||
|
-keep class cc.winboll.studio.** { *; }
|
||||||
|
-keepclassmembers class cc.winboll.studio.** { *; }
|
||||||
|
|
||||||
|
# 保留所有类中的 public static final String TAG 字段(便于日志定位)
|
||||||
|
-keepclassmembers class * {
|
||||||
|
public static final java.lang.String TAG;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 保留序列化类(避免Parcelable/Gson解析异常)
|
||||||
|
-keep class * implements android.os.Parcelable {
|
||||||
|
public static final android.os.Parcelable$Creator *;
|
||||||
|
}
|
||||||
|
-keepclassmembers class * implements java.io.Serializable {
|
||||||
|
static final long serialVersionUID;
|
||||||
|
private static final java.io.ObjectStreamField[] serialPersistentFields;
|
||||||
|
private void writeObject(java.io.ObjectOutputStream);
|
||||||
|
private void readObject(java.io.ObjectInputStream);
|
||||||
|
java.lang.Object writeReplace();
|
||||||
|
java.lang.Object readResolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
# 保留 R 文件(避免资源ID混淆)
|
||||||
|
-keepclassmembers class **.R$* {
|
||||||
|
public static <fields>;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 保留 native 方法(避免JNI调用失败)
|
||||||
|
-keepclasseswithmembernames class * {
|
||||||
|
native <methods>;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 保留注解和泛型(避免反射/序列化异常)
|
||||||
|
-keepattributes *Annotation*
|
||||||
|
-keepattributes Signature
|
||||||
|
|
||||||
|
# 屏蔽 Java 8+ 警告(适配 Java 7 语法)
|
||||||
|
-dontwarn java.lang.invoke.*
|
||||||
|
-dontwarn android.support.v8.renderscript.*
|
||||||
|
-dontwarn java.util.function.**
|
||||||
|
|
||||||
|
# ============================== 第三方框架专项规则 ==============================
|
||||||
|
# OkHttp 4.4.1(米盟广告请求依赖,完善Lambda兼容)
|
||||||
|
-keep class okhttp3.** { *; }
|
||||||
|
-keep interface okhttp3.** { *; }
|
||||||
|
-keep class okhttp3.internal.** { *; }
|
||||||
|
-keep class okio.** { *; }
|
||||||
|
-dontwarn okhttp3.internal.platform.**
|
||||||
|
-dontwarn okio.**
|
||||||
|
|
||||||
|
# Glide 4.9.0(米盟广告图片加载依赖)
|
||||||
|
-keep public class * implements com.bumptech.glide.module.GlideModule
|
||||||
|
-keep public class * extends com.bumptech.glide.module.AppGlideModule
|
||||||
|
-keep public enum com.bumptech.glide.load.ImageHeaderParser$ImageType {
|
||||||
|
**[] $VALUES;
|
||||||
|
public *;
|
||||||
|
}
|
||||||
|
-keepclassmembers class * implements com.bumptech.glide.module.AppGlideModule {
|
||||||
|
<init>();
|
||||||
|
}
|
||||||
|
-dontwarn com.bumptech.glide.**
|
||||||
|
|
||||||
|
# Gson 2.8.5(米盟广告数据序列化依赖)
|
||||||
|
-keep class com.google.gson.** { *; }
|
||||||
|
-keep interface com.google.gson.** { *; }
|
||||||
|
-keepclassmembers class * {
|
||||||
|
@com.google.gson.annotations.SerializedName <fields>;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 米盟 SDK(核心广告组件,完整保留避免加载失败)
|
||||||
|
-keep class com.miui.zeus.** { *; }
|
||||||
|
-keep interface com.miui.zeus.** { *; }
|
||||||
|
# 保留米盟日志字段(便于广告加载失败排查)
|
||||||
|
-keepclassmembers class com.miui.zeus.mimo.sdk.** {
|
||||||
|
public static final java.lang.String TAG;
|
||||||
|
}
|
||||||
|
|
||||||
|
# RecyclerView 1.0.0(米盟广告布局渲染依赖)
|
||||||
|
-keep class androidx.recyclerview.** { *; }
|
||||||
|
-keep interface androidx.recyclerview.** { *; }
|
||||||
|
-keepclassmembers class androidx.recyclerview.widget.RecyclerView$Adapter {
|
||||||
|
public *;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 其他第三方框架(按引入依赖保留,无则可删除)
|
||||||
|
# XXPermissions 18.63
|
||||||
|
-keep class com.hjq.permissions.** { *; }
|
||||||
|
-keep interface com.hjq.permissions.** { *; }
|
||||||
|
|
||||||
|
# ZXing 二维码(核心解析组件)
|
||||||
|
-keep class com.google.zxing.** { *; }
|
||||||
|
-keep class com.journeyapps.zxing.** { *; }
|
||||||
|
|
||||||
|
# Jsoup HTML解析
|
||||||
|
-keep class org.jsoup.** { *; }
|
||||||
|
|
||||||
|
# Pinyin4j 拼音搜索
|
||||||
|
-keep class net.sourceforge.pinyin4j.** { *; }
|
||||||
|
|
||||||
|
# JSch SSH组件
|
||||||
|
-keep class com.jcraft.jsch.** { *; }
|
||||||
|
|
||||||
|
# AndroidX 基础组件
|
||||||
|
-keep class androidx.appcompat.** { *; }
|
||||||
|
-keep interface androidx.appcompat.** { *; }
|
||||||
|
|
||||||
|
# ============================== 优化与调试配置 ==============================
|
||||||
|
# 优化级别(平衡混淆效果与性能)
|
||||||
|
-optimizationpasses 5
|
||||||
|
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
|
||||||
|
|
||||||
|
# 调试辅助(保留行号便于崩溃定位)
|
||||||
|
-verbose
|
||||||
|
-dontpreverify
|
||||||
|
-dontusemixedcaseclassnames
|
||||||
|
-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
14
winboll/src/beta/AndroidManifest.xml
Normal file
14
winboll/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>
|
||||||
|
|
||||||
3
winboll/src/beta/res/values-zh/strings.xml
Normal file
3
winboll/src/beta/res/values-zh/strings.xml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
</resources>
|
||||||
8
winboll/src/beta/res/values/strings.xml
Normal file
8
winboll/src/beta/res/values/strings.xml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<string name="app_name">WinBoLL+</string>
|
||||||
|
<string name="app_name_cn1">筋斗云★</string>
|
||||||
|
<string name="app_name_cn2">金抖云☆</string>
|
||||||
|
|
||||||
|
</resources>
|
||||||
46
winboll/src/beta/res/xml/shortcutsmaincn1.xml
Normal file
46
winboll/src/beta/res/xml/shortcutsmaincn1.xml
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- 切换启动入口的快捷菜单 -->
|
||||||
|
<shortcut
|
||||||
|
android:shortcutId="switchto_en1"
|
||||||
|
android:enabled="true"
|
||||||
|
android:icon="@drawable/ic_launcher"
|
||||||
|
android:shortcutShortLabel="@string/switchto_en1"
|
||||||
|
android:shortcutLongLabel="@string/switchto_en1"
|
||||||
|
android:shortcutDisabledMessage="@string/en1_switch_disabled">
|
||||||
|
<intent
|
||||||
|
android:action="cc.winboll.studio.winboll.App.ACTION_SWITCHTO_EN1"
|
||||||
|
android:targetPackage="cc.winboll.studio.winboll.beta"
|
||||||
|
android:targetClass="cc.winboll.studio.winboll.activities.ShortcutActionActivity"
|
||||||
|
android:data="switchto_en1" />
|
||||||
|
<categories android:name="android.shortcut.conversation" />
|
||||||
|
</shortcut>
|
||||||
|
<!--<shortcut
|
||||||
|
android:shortcutId="switchto_cn1"
|
||||||
|
android:enabled="true"
|
||||||
|
android:icon="@drawable/ic_launcher"
|
||||||
|
android:shortcutShortLabel="@string/switchto_cn1"
|
||||||
|
android:shortcutLongLabel="@string/switchto_cn1"
|
||||||
|
android:shortcutDisabledMessage="@string/cn1_switch_disabled">
|
||||||
|
<intent
|
||||||
|
android:action="cc.winboll.studio.winboll.App.ACTION_SWITCHTO_CN1"
|
||||||
|
android:targetPackage="cc.winboll.studio.winboll.beta"
|
||||||
|
android:targetClass="cc.winboll.studio.winboll.activities.ShortcutActionActivity"
|
||||||
|
android:data="switchto_cn1" />
|
||||||
|
<categories android:name="android.shortcut.conversation" />
|
||||||
|
</shortcut>-->
|
||||||
|
<shortcut
|
||||||
|
android:shortcutId="switchto_cn2"
|
||||||
|
android:enabled="true"
|
||||||
|
android:icon="@drawable/ic_launcher"
|
||||||
|
android:shortcutShortLabel="@string/switchto_cn2"
|
||||||
|
android:shortcutLongLabel="@string/switchto_cn2"
|
||||||
|
android:shortcutDisabledMessage="@string/cn2_switch_disabled">
|
||||||
|
<intent
|
||||||
|
android:action="cc.winboll.studio.winboll.App.ACTION_SWITCHTO_CN2"
|
||||||
|
android:targetPackage="cc.winboll.studio.winboll.beta"
|
||||||
|
android:targetClass="cc.winboll.studio.winboll.activities.ShortcutActionActivity"
|
||||||
|
android:data="switchto_cn2" />
|
||||||
|
<categories android:name="android.shortcut.conversation" />
|
||||||
|
</shortcut>
|
||||||
|
</shortcuts>
|
||||||
46
winboll/src/beta/res/xml/shortcutsmaincn2.xml
Normal file
46
winboll/src/beta/res/xml/shortcutsmaincn2.xml
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- 切换启动入口的快捷菜单 -->
|
||||||
|
<shortcut
|
||||||
|
android:shortcutId="switchto_en1"
|
||||||
|
android:enabled="true"
|
||||||
|
android:icon="@drawable/ic_launcher"
|
||||||
|
android:shortcutShortLabel="@string/switchto_en1"
|
||||||
|
android:shortcutLongLabel="@string/switchto_en1"
|
||||||
|
android:shortcutDisabledMessage="@string/en1_switch_disabled">
|
||||||
|
<intent
|
||||||
|
android:action="cc.winboll.studio.winboll.App.ACTION_SWITCHTO_EN1"
|
||||||
|
android:targetPackage="cc.winboll.studio.winboll.beta"
|
||||||
|
android:targetClass="cc.winboll.studio.winboll.activities.ShortcutActionActivity"
|
||||||
|
android:data="switchto_en1" />
|
||||||
|
<categories android:name="android.shortcut.conversation" />
|
||||||
|
</shortcut>
|
||||||
|
<shortcut
|
||||||
|
android:shortcutId="switchto_cn1"
|
||||||
|
android:enabled="true"
|
||||||
|
android:icon="@drawable/ic_launcher"
|
||||||
|
android:shortcutShortLabel="@string/switchto_cn1"
|
||||||
|
android:shortcutLongLabel="@string/switchto_cn1"
|
||||||
|
android:shortcutDisabledMessage="@string/cn1_switch_disabled">
|
||||||
|
<intent
|
||||||
|
android:action="cc.winboll.studio.winboll.App.ACTION_SWITCHTO_CN1"
|
||||||
|
android:targetPackage="cc.winboll.studio.winboll.beta"
|
||||||
|
android:targetClass="cc.winboll.studio.winboll.activities.ShortcutActionActivity"
|
||||||
|
android:data="switchto_cn1" />
|
||||||
|
<categories android:name="android.shortcut.conversation" />
|
||||||
|
</shortcut>
|
||||||
|
<!--<shortcut
|
||||||
|
android:shortcutId="switchto_cn2"
|
||||||
|
android:enabled="true"
|
||||||
|
android:icon="@drawable/ic_launcher"
|
||||||
|
android:shortcutShortLabel="@string/switchto_cn2"
|
||||||
|
android:shortcutLongLabel="@string/switchto_cn2"
|
||||||
|
android:shortcutDisabledMessage="@string/cn2_switch_disabled">
|
||||||
|
<intent
|
||||||
|
android:action="cc.winboll.studio.winboll.App.ACTION_SWITCHTO_CN2"
|
||||||
|
android:targetPackage="cc.winboll.studio.winboll.beta"
|
||||||
|
android:targetClass="cc.winboll.studio.winboll.activities.ShortcutActionActivity"
|
||||||
|
android:data="switchto_cn2" />
|
||||||
|
<categories android:name="android.shortcut.conversation" />
|
||||||
|
</shortcut>-->
|
||||||
|
</shortcuts>
|
||||||
46
winboll/src/beta/res/xml/shortcutsmainen1.xml
Normal file
46
winboll/src/beta/res/xml/shortcutsmainen1.xml
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- 切换启动入口的快捷菜单 -->
|
||||||
|
<!--<shortcut
|
||||||
|
android:shortcutId="switchto_en1"
|
||||||
|
android:enabled="true"
|
||||||
|
android:icon="@drawable/ic_launcher"
|
||||||
|
android:shortcutShortLabel="@string/switchto_en1"
|
||||||
|
android:shortcutLongLabel="@string/switchto_en1"
|
||||||
|
android:shortcutDisabledMessage="@string/en1_switch_disabled">
|
||||||
|
<intent
|
||||||
|
android:action="cc.winboll.studio.winboll.App.ACTION_SWITCHTO_EN1"
|
||||||
|
android:targetPackage="cc.winboll.studio.winboll.beta"
|
||||||
|
android:targetClass="cc.winboll.studio.winboll.activities.ShortcutActionActivity"
|
||||||
|
android:data="switchto_en1" />
|
||||||
|
<categories android:name="android.shortcut.conversation" />
|
||||||
|
</shortcut>-->
|
||||||
|
<shortcut
|
||||||
|
android:shortcutId="switchto_cn1"
|
||||||
|
android:enabled="true"
|
||||||
|
android:icon="@drawable/ic_launcher"
|
||||||
|
android:shortcutShortLabel="@string/switchto_cn1"
|
||||||
|
android:shortcutLongLabel="@string/switchto_cn1"
|
||||||
|
android:shortcutDisabledMessage="@string/cn1_switch_disabled">
|
||||||
|
<intent
|
||||||
|
android:action="cc.winboll.studio.winboll.App.ACTION_SWITCHTO_CN1"
|
||||||
|
android:targetPackage="cc.winboll.studio.winboll.beta"
|
||||||
|
android:targetClass="cc.winboll.studio.winboll.activities.ShortcutActionActivity"
|
||||||
|
android:data="switchto_cn1" />
|
||||||
|
<categories android:name="android.shortcut.conversation" />
|
||||||
|
</shortcut>
|
||||||
|
<shortcut
|
||||||
|
android:shortcutId="switchto_cn2"
|
||||||
|
android:enabled="true"
|
||||||
|
android:icon="@drawable/ic_launcher"
|
||||||
|
android:shortcutShortLabel="@string/switchto_cn2"
|
||||||
|
android:shortcutLongLabel="@string/switchto_cn2"
|
||||||
|
android:shortcutDisabledMessage="@string/cn2_switch_disabled">
|
||||||
|
<intent
|
||||||
|
android:action="cc.winboll.studio.winboll.App.ACTION_SWITCHTO_CN2"
|
||||||
|
android:targetPackage="cc.winboll.studio.winboll.beta"
|
||||||
|
android:targetClass="cc.winboll.studio.winboll.activities.ShortcutActionActivity"
|
||||||
|
android:data="switchto_cn2" />
|
||||||
|
<categories android:name="android.shortcut.conversation" />
|
||||||
|
</shortcut>
|
||||||
|
</shortcuts>
|
||||||
287
winboll/src/main/AndroidManifest.xml
Normal file
287
winboll/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<manifest
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="cc.winboll.studio.winboll">
|
||||||
|
|
||||||
|
<!-- 拥有完全的网络访问权限 -->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
|
||||||
|
<!-- 发送持久广播 -->
|
||||||
|
<uses-permission android:name="android.permission.BROADCAST_STICKY"/>
|
||||||
|
|
||||||
|
<!-- 对正在运行的应用重新排序 -->
|
||||||
|
<uses-permission android:name="android.permission.REORDER_TASKS"/>
|
||||||
|
|
||||||
|
<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"
|
||||||
|
android:usesCleartextTraffic="true"
|
||||||
|
android:networkSecurityConfig="@xml/network_security_config">
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:exported="true">
|
||||||
|
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name=".activities.ShortcutActionActivity"/>
|
||||||
|
|
||||||
|
<activity-alias
|
||||||
|
android:name=".MainActivityEN1"
|
||||||
|
android:targetActivity=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:icon="@drawable/ic_launcher"
|
||||||
|
android:enabled="true">
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="android.app.shortcuts"
|
||||||
|
android:resource="@xml/shortcutsmainen1"/>
|
||||||
|
|
||||||
|
</activity-alias>
|
||||||
|
|
||||||
|
<activity-alias
|
||||||
|
android:name=".MainActivityCN1"
|
||||||
|
android:targetActivity=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:label="@string/app_name_cn1"
|
||||||
|
android:icon="@drawable/ic_winboll_jindouyun1"
|
||||||
|
android:enabled="false">
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="android.app.shortcuts"
|
||||||
|
android:resource="@xml/shortcutsmaincn1"/>
|
||||||
|
|
||||||
|
</activity-alias>
|
||||||
|
|
||||||
|
<activity-alias
|
||||||
|
android:name=".MainActivityCN2"
|
||||||
|
android:targetActivity=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:label="@string/app_name_cn2"
|
||||||
|
android:icon="@drawable/ic_winboll_jindouyun2"
|
||||||
|
android:enabled="false">
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="android.app.shortcuts"
|
||||||
|
android:resource="@xml/shortcutsmaincn2"/>
|
||||||
|
|
||||||
|
</activity-alias>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".activities.WinBoLLUnitTestActivity"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:exported="true"
|
||||||
|
android:resizeableActivity="true"
|
||||||
|
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation">
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
|
||||||
|
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES"/>
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
|
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".activities.NewActivity"
|
||||||
|
android:label="NewActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:resizeableActivity="true"
|
||||||
|
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"/>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".activities.New2Activity"
|
||||||
|
android:label="New2Activity"
|
||||||
|
android:exported="true"
|
||||||
|
android:resizeableActivity="true"
|
||||||
|
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"/>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".MyTileService"
|
||||||
|
android:exported="true"
|
||||||
|
android:label="@string/tileservice_name"
|
||||||
|
android:icon="@drawable/ic_launcher"
|
||||||
|
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
|
||||||
|
<action android:name="android.service.quicksettings.action.QS_TILE"/>
|
||||||
|
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".services.MainService"
|
||||||
|
android:exported="true"/>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name="cc.winboll.studio.appbase.services.TestDemoBindService"
|
||||||
|
android:exported="true"/>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name="cc.winboll.studio.appbase.services.TestDemoService"
|
||||||
|
android:exported="true"/>
|
||||||
|
|
||||||
|
<service android:name=".services.AssistantService"/>
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name="cc.winboll.studio.appbase.receivers.MainReceiver"
|
||||||
|
android:exported="true">
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
|
||||||
|
<action android:name="cc.winboll.studio.appbase.receivers.MainReceiver"/>
|
||||||
|
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name=".widgets.APPNewsWidget"
|
||||||
|
android:exported="true">
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
|
||||||
|
<action android:name="cc.winboll.studio.appbase.widgets.APPNewsWidget.ACTION_WAKEUP_SERVICE"/>
|
||||||
|
|
||||||
|
<action android:name="cc.winboll.studio.appbase.widgets.APPNewsWidget.ACTION_RELOAD_REPORT"/>
|
||||||
|
|
||||||
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
|
||||||
|
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="android.appwidget.provider"
|
||||||
|
android:resource="@xml/widget_provider_info_sos"/>
|
||||||
|
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name=".receivers.APPNewsWidgetClickListener"
|
||||||
|
android:exported="true">
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
|
||||||
|
<action android:name="cc.winboll.studio.appbase.receivers.APPNewsWidgetClickListener.ACTION_PRE"/>
|
||||||
|
|
||||||
|
<action android:name="cc.winboll.studio.appbase.receivers.APPNewsWidgetClickListener.ACTION_NEXT"/>
|
||||||
|
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".SimpleOperateSignalCenterService"
|
||||||
|
android:exported="true">
|
||||||
|
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".services.TestService"
|
||||||
|
android:exported="true"/>
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name=".receiver.MyBroadcastReceiver"
|
||||||
|
android:exported="true">
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
|
||||||
|
<action android:name="cc.winboll.studio.libappbase.action.SOS"/>
|
||||||
|
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name=".widgets.StatusWidget"
|
||||||
|
android:exported="true">
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
|
||||||
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
|
||||||
|
|
||||||
|
<action android:name="cc.winboll.studio.libappbase.widgets.StatusWidget.ACTION_STATUS_UPDATE"/>
|
||||||
|
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="android.appwidget.provider"
|
||||||
|
android:resource="@xml/widget_provider_info_status"/>
|
||||||
|
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name=".widgets.StatusWidgetClickListener"
|
||||||
|
android:exported="true">
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
|
||||||
|
<action android:name="cc.winboll.studio.libappbase.widgets.StatusWidgetClickListener.ACTION_IVAPP"/>
|
||||||
|
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<service android:name="cc.winboll.studio.libappbase.sos.SOSCenter"/>
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name="cc.winboll.studio.libappbase.sos.SOSCenterServiceReceiver"
|
||||||
|
android:exported="true">
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
|
||||||
|
<action android:name="cc.winboll.studio.libappbase.sos.SOSCenterServiceReceiver"/>
|
||||||
|
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<activity android:name="cc.winboll.studio.libappbase.activities.YunActivity"/>
|
||||||
|
|
||||||
|
<activity android:name="cc.winboll.studio.libappbase.activities.LogonActivity"/>
|
||||||
|
|
||||||
|
<activity android:name="cc.winboll.studio.winboll.activities.AboutActivity"/>
|
||||||
|
|
||||||
|
<activity android:name="cc.winboll.studio.winboll.activities.SettingsActivity"/>
|
||||||
|
|
||||||
|
<activity android:name="cc.winboll.studio.winboll.unittest.TestWeWorkSpecSDK"/>
|
||||||
|
|
||||||
|
<activity android:name="cc.winboll.studio.winboll.activities.WXPayActivity"/>
|
||||||
|
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
366
winboll/src/main/java/cc/winboll/studio/winboll/App.java
Normal file
366
winboll/src/main/java/cc/winboll/studio/winboll/App.java
Normal file
@@ -0,0 +1,366 @@
|
|||||||
|
package cc.winboll.studio.winboll;
|
||||||
|
|
||||||
|
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 cc.winboll.studio.libappbase.ToastUtils;
|
||||||
|
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;
|
||||||
|
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
|
||||||
|
|
||||||
|
public class App extends GlobalApplication {
|
||||||
|
|
||||||
|
public static final String TAG = "App";
|
||||||
|
|
||||||
|
public static final String COMPONENT_EN1 = "cc.winboll.studio.winboll.MainActivityEN1";
|
||||||
|
public static final String COMPONENT_CN1 = "cc.winboll.studio.winboll.MainActivityCN1";
|
||||||
|
public static final String COMPONENT_CN2 = "cc.winboll.studio.winboll.MainActivityCN2";
|
||||||
|
public static final String ACTION_SWITCHTO_EN1 = "cc.winboll.studio.winboll.App.ACTION_SWITCHTO_EN1";
|
||||||
|
public static final String ACTION_SWITCHTO_CN1 = "cc.winboll.studio.winboll.App.ACTION_SWITCHTO_CN1";
|
||||||
|
public static final String ACTION_SWITCHTO_CN2 = "cc.winboll.studio.winboll.App.ACTION_SWITCHTO_CN2";
|
||||||
|
|
||||||
|
private static Handler MAIN_HANDLER = new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
setIsDebugging(BuildConfig.DEBUG);
|
||||||
|
//setIsDebugging(false);
|
||||||
|
|
||||||
|
WinBoLLActivityManager.init(this);
|
||||||
|
|
||||||
|
// 初始化 Toast 框架
|
||||||
|
ToastUtils.init(this);
|
||||||
|
// 设置 Toast 布局样式
|
||||||
|
//ToastUtils.setView(R.layout.view_toast);
|
||||||
|
//ToastUtils.setStyle(new WhiteToastStyle());
|
||||||
|
//ToastUtils.setGravity(Gravity.BOTTOM, 0, 200);
|
||||||
|
|
||||||
|
//CrashHandler.getInstance().registerGlobal(this);
|
||||||
|
//CrashHandler.getInstance().registerPart(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTerminate() {
|
||||||
|
super.onTerminate();
|
||||||
|
ToastUtils.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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,97 @@
|
|||||||
|
package cc.winboll.studio.winboll;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2025/03/28 19:12:12
|
||||||
|
* @Describe 应用主要服务组件类守护进程服务组件类
|
||||||
|
*/
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import cc.winboll.studio.libaes.models.WinBoLLClientServiceBean;
|
||||||
|
import cc.winboll.studio.winboll.WinBoLLClientService;
|
||||||
|
import cc.winboll.studio.winboll.utils.ServiceUtils;
|
||||||
|
|
||||||
|
public class AssistantService extends Service {
|
||||||
|
|
||||||
|
public final static String TAG = "AssistantService";
|
||||||
|
|
||||||
|
WinBoLLClientServiceBean mWinBoLLServiceBean;
|
||||||
|
MyServiceConnection mMyServiceConnection;
|
||||||
|
volatile boolean mIsServiceRunning;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
mWinBoLLServiceBean = WinBoLLClientServiceBean.loadWinBoLLClientServiceBean(this);
|
||||||
|
if (mMyServiceConnection == null) {
|
||||||
|
mMyServiceConnection = new MyServiceConnection();
|
||||||
|
}
|
||||||
|
// 设置运行参数
|
||||||
|
mIsServiceRunning = false;
|
||||||
|
run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
|
run();
|
||||||
|
return START_STICKY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
mIsServiceRunning = false;
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// 运行服务内容
|
||||||
|
//
|
||||||
|
void run() {
|
||||||
|
mWinBoLLServiceBean = WinBoLLClientServiceBean.loadWinBoLLClientServiceBean(this);
|
||||||
|
if (mWinBoLLServiceBean.isEnable()) {
|
||||||
|
if (mIsServiceRunning == false) {
|
||||||
|
// 设置运行状态
|
||||||
|
mIsServiceRunning = true;
|
||||||
|
// 唤醒和绑定主进程
|
||||||
|
wakeupAndBindMain();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// 唤醒和绑定主进程
|
||||||
|
//
|
||||||
|
void wakeupAndBindMain() {
|
||||||
|
if (ServiceUtils.isServiceAlive(getApplicationContext(), WinBoLLClientService.class.getName()) == false) {
|
||||||
|
startForegroundService(new Intent(AssistantService.this, WinBoLLClientService.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
bindService(new Intent(AssistantService.this, WinBoLLClientService.class), mMyServiceConnection, Context.BIND_IMPORTANT);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// 主进程与守护进程连接时需要用到此类
|
||||||
|
//
|
||||||
|
class MyServiceConnection implements ServiceConnection {
|
||||||
|
@Override
|
||||||
|
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceDisconnected(ComponentName name) {
|
||||||
|
mWinBoLLServiceBean = WinBoLLClientServiceBean.loadWinBoLLClientServiceBean(AssistantService.this);
|
||||||
|
if (mWinBoLLServiceBean.isEnable()) {
|
||||||
|
wakeupAndBindMain();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package cc.winboll.studio.winboll;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@188.com>
|
||||||
|
* @Date 2025/05/22 13:08
|
||||||
|
*/
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
|
||||||
|
public class CustomToolbar extends Toolbar {
|
||||||
|
|
||||||
|
private View viewMain;
|
||||||
|
|
||||||
|
public CustomToolbar(Context context) {
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CustomToolbar(Context context, @Nullable AttributeSet attrs) {
|
||||||
|
this(context, attrs, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CustomToolbar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
initView(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initView(Context context, AttributeSet attrs) {
|
||||||
|
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomToolbar);
|
||||||
|
|
||||||
|
// 获取属性值
|
||||||
|
String toolbarTitle = typedArray.getString(R.styleable.CustomToolbar_toolbarTitle);
|
||||||
|
int toolbarTitleColor = typedArray.getColor(R.styleable.CustomToolbar_toolbarTitleColor, android.graphics.Color.WHITE);
|
||||||
|
int toolbarBackgroundColor = typedArray.getColor(R.styleable.CustomToolbar_toolbarBackgroundColor, android.graphics.Color.BLUE);
|
||||||
|
|
||||||
|
// 加载布局
|
||||||
|
viewMain = LayoutInflater.from(context).inflate(R.layout.view_toolbar, this, true);
|
||||||
|
|
||||||
|
// 应用属性值
|
||||||
|
TextView toolbarTitleTextView = viewMain.findViewById(R.id.toolbar_title);
|
||||||
|
toolbarTitleTextView.setText(toolbarTitle);
|
||||||
|
toolbarTitleTextView.setTextColor(toolbarTitleColor);
|
||||||
|
viewMain.setBackgroundColor(toolbarBackgroundColor);
|
||||||
|
|
||||||
|
// 释放 TypedArray
|
||||||
|
typedArray.recycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package cc.winboll.studio.winboll;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2025/03/28 19:11:27
|
||||||
|
* @Describe WinBoLL UI 状态图标枚举
|
||||||
|
*/
|
||||||
|
import cc.winboll.studio.libaes.R;
|
||||||
|
|
||||||
|
public enum EWUIStatusIconDrawable {
|
||||||
|
NORMAL(0),
|
||||||
|
NEWS(1)
|
||||||
|
;
|
||||||
|
|
||||||
|
static final String TAG = "WUIStatusIconDrawable";
|
||||||
|
|
||||||
|
static String[] _mlistCNName = { "正常", "新的消息" };
|
||||||
|
|
||||||
|
private int value = 0;
|
||||||
|
private EWUIStatusIconDrawable(int value) { //必须是private的,否则编译错误
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getIconDrawableId(EWUIStatusIconDrawable drawableId) {
|
||||||
|
int res;
|
||||||
|
switch(drawableId){
|
||||||
|
case NEWS :
|
||||||
|
res = R.drawable.ic_winbollbeta;
|
||||||
|
break;
|
||||||
|
default :
|
||||||
|
res = R.drawable.ic_winboll;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
package cc.winboll.studio.winboll;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import cc.winboll.studio.libaes.activitys.AboutActivity;
|
||||||
|
import cc.winboll.studio.libaes.activitys.DrawerFragmentActivity;
|
||||||
|
import cc.winboll.studio.libaes.models.APPInfo;
|
||||||
|
import cc.winboll.studio.libaes.models.DrawerMenuBean;
|
||||||
|
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import cc.winboll.studio.winboll.R;
|
||||||
|
import cc.winboll.studio.winboll.activities.SettingsActivity;
|
||||||
|
import cc.winboll.studio.winboll.activities.WXPayActivity;
|
||||||
|
import cc.winboll.studio.winboll.fragments.BrowserFragment;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class MainActivity extends DrawerFragmentActivity {
|
||||||
|
|
||||||
|
|
||||||
|
public static final String TAG = "MainActivity";
|
||||||
|
|
||||||
|
BrowserFragment mBrowserFragment;
|
||||||
|
|
||||||
|
// ------------------- 新增:Handler 消息定义(接收URL历史更新消息) -------------------
|
||||||
|
// 消息标识:URL加载历史更新(刷新抽屉菜单的历史列表)
|
||||||
|
public static final int MSG_URLLOADHISTORY_UPDATE = 1002;
|
||||||
|
// 自定义Handler(接收应用内消息,如BrowserFragment发送的历史更新消息)
|
||||||
|
private static Handler _mMainHandler;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
// ------------------- 新增:初始化MainActivity的Handler(关键) -------------------
|
||||||
|
initMainHandler();
|
||||||
|
|
||||||
|
if (mBrowserFragment == null) {
|
||||||
|
mBrowserFragment = new BrowserFragment();
|
||||||
|
addFragment(mBrowserFragment);
|
||||||
|
}
|
||||||
|
showFragment(mBrowserFragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sendMessage(Message msg) {
|
||||||
|
_mMainHandler.sendMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化Handler(接收MSG_URLLOADHISTORY_UPDATE消息,刷新抽屉历史菜单)
|
||||||
|
*/
|
||||||
|
private void initMainHandler() {
|
||||||
|
// 清理旧数据
|
||||||
|
if (_mMainHandler != null) {
|
||||||
|
_mMainHandler.removeCallbacksAndMessages(null);
|
||||||
|
_mMainHandler = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Java 7 匿名内部类实现Handler(主线程创建,安全更新UI/抽屉菜单)
|
||||||
|
_mMainHandler = new Handler() {
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message msg) {
|
||||||
|
super.handleMessage(msg);
|
||||||
|
switch (msg.what) {
|
||||||
|
case MSG_URLLOADHISTORY_UPDATE:
|
||||||
|
// 处理URL历史更新消息:刷新抽屉菜单的历史列表
|
||||||
|
LogUtils.d(TAG, "收到URL历史更新消息,刷新抽屉菜单");
|
||||||
|
refreshUrlHistoryDrawerMenu();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initDrawerMenuItemList(ArrayList<DrawerMenuBean> listDrawerMenu) {
|
||||||
|
super.initDrawerMenuItemList(listDrawerMenu);
|
||||||
|
//LogUtils.d(TAG, "initDrawerMenuItemList");
|
||||||
|
// 加载URL历史菜单(初始化时加载)
|
||||||
|
refreshUrlHistoryDrawerMenu();
|
||||||
|
notifyDrawerMenuDataChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reinitDrawerMenuItemList(ArrayList<DrawerMenuBean> listDrawerMenu) {
|
||||||
|
super.reinitDrawerMenuItemList(listDrawerMenu);
|
||||||
|
//LogUtils.d(TAG, "reinitDrawerMenuItemList");
|
||||||
|
// 重新加载URL历史菜单(菜单重置时加载)
|
||||||
|
refreshUrlHistoryDrawerMenu();
|
||||||
|
notifyDrawerMenuDataChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadUrlLoadHistotyMenu(ArrayList<DrawerMenuBean> listDrawerMenu) {
|
||||||
|
listDrawerMenu.clear();
|
||||||
|
if (BrowserFragment._mUrlLoadHistory != null) {
|
||||||
|
for (String url : BrowserFragment._mUrlLoadHistory) {
|
||||||
|
listDrawerMenu.add(new DrawerMenuBean(R.drawable.ic_launcher, url));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------- 新增:刷新URL历史抽屉菜单(提取独立方法,复用) -------------------
|
||||||
|
private void refreshUrlHistoryDrawerMenu() {
|
||||||
|
// 获取抽屉菜单列表,重新加载历史数据并刷新
|
||||||
|
ArrayList<DrawerMenuBean> drawerMenuList = super.malDrawerMenuItem; // 假设父类提供获取菜单列表的方法
|
||||||
|
if (drawerMenuList != null) {
|
||||||
|
loadUrlLoadHistotyMenu(drawerMenuList); // 重新加载更新后的历史数据
|
||||||
|
notifyDrawerMenuDataChanged(); // 通知抽屉菜单刷新UI
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DrawerFragmentActivity.ActivityType initActivityType() {
|
||||||
|
return DrawerFragmentActivity.ActivityType.Main;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
getMenuInflater().inflate(R.menu.toolbar_main, menu);
|
||||||
|
return super.onCreateOptionsMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
super.onItemClick(parent, view, position, id);
|
||||||
|
if (mBrowserFragment != null && mBrowserFragment.getBrowserHandler() != null) {
|
||||||
|
Message msg = Message.obtain();
|
||||||
|
msg.what = BrowserFragment.MSG_HISTORY_POSITION;
|
||||||
|
msg.obj = position;
|
||||||
|
mBrowserFragment.getBrowserHandler().sendMessage(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
int nItemId = item.getItemId();
|
||||||
|
if (nItemId == R.id.item_home) {
|
||||||
|
// 发送MSG_HOMEPAGE消息给BrowserFragment
|
||||||
|
if (mBrowserFragment != null && mBrowserFragment.getBrowserHandler() != null) {
|
||||||
|
Message msg = Message.obtain();
|
||||||
|
msg.what = BrowserFragment.MSG_HOMEPAGE;
|
||||||
|
mBrowserFragment.getBrowserHandler().sendMessage(msg);
|
||||||
|
}
|
||||||
|
} else if (nItemId == R.id.item_settings) {
|
||||||
|
WinBoLLActivityManager.getInstance().startWinBoLLActivity(getApplicationContext(), SettingsActivity.class);
|
||||||
|
} else if (nItemId == R.id.item_wxpayactivity) {
|
||||||
|
WinBoLLActivityManager.getInstance().startWinBoLLActivity(getApplicationContext(), WXPayActivity.class);
|
||||||
|
} else if (nItemId == cc.winboll.studio.libaes.R.id.item_about) {
|
||||||
|
Intent intent = new Intent(getApplicationContext(), AboutActivity.class);
|
||||||
|
APPInfo appInfo = genDefaultAPPInfo();
|
||||||
|
intent.putExtra(AboutActivity.EXTRA_APPINFO, appInfo);
|
||||||
|
WinBoLLActivityManager.getInstance().startWinBoLLActivity(getApplicationContext(), intent, AboutActivity.class);
|
||||||
|
} else {
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
APPInfo genDefaultAPPInfo() {
|
||||||
|
String szBranchName = "winboll";
|
||||||
|
APPInfo appInfo = new APPInfo();
|
||||||
|
appInfo.setAppName("WinBoLL");
|
||||||
|
appInfo.setAppIcon(cc.winboll.studio.libaes.R.drawable.ic_winboll);
|
||||||
|
appInfo.setAppDescription("WinBoLL 网站浏览器。");
|
||||||
|
appInfo.setAppGitName("WinBoLL");
|
||||||
|
appInfo.setAppGitOwner("Studio");
|
||||||
|
appInfo.setAppGitAPPBranch(szBranchName);
|
||||||
|
appInfo.setAppGitAPPSubProjectFolder(szBranchName);
|
||||||
|
appInfo.setAppHomePage("https://www.winboll.cc/apks/index.php?project=WinBoLL");
|
||||||
|
appInfo.setAppAPKName("WinBoLL");
|
||||||
|
appInfo.setAppAPKFolderName("WinBoLL");
|
||||||
|
return appInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------- 新增:对外提供Handler(供其他组件发送消息,如BrowserFragment) -------------------
|
||||||
|
public Handler getMainHandler() {
|
||||||
|
return _mMainHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------- 新增:生命周期管理(防止Handler内存泄漏) -------------------
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
// 清除Handler所有消息和回调,避免内存泄漏
|
||||||
|
if (_mMainHandler != null) {
|
||||||
|
_mMainHandler.removeCallbacksAndMessages(null);
|
||||||
|
_mMainHandler = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package cc.winboll.studio.winboll;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2025/02/13 19:30:10
|
||||||
|
*/
|
||||||
|
import android.content.Context;
|
||||||
|
import cc.winboll.studio.winboll.R;
|
||||||
|
import android.service.quicksettings.Tile;
|
||||||
|
import android.service.quicksettings.TileService;
|
||||||
|
import cc.winboll.studio.winboll.models.MainServiceBean;
|
||||||
|
import cc.winboll.studio.winboll.services.MainService;
|
||||||
|
|
||||||
|
public class MyTileService extends TileService {
|
||||||
|
public static final String TAG = "MyTileService";
|
||||||
|
|
||||||
|
volatile static MyTileService _MyTileService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStartListening() {
|
||||||
|
super.onStartListening();
|
||||||
|
_MyTileService = this;
|
||||||
|
Tile tile = getQsTile();
|
||||||
|
MainServiceBean bean = MainServiceBean.loadBean(this, MainServiceBean.class);
|
||||||
|
if (bean != null && bean.isEnable()) {
|
||||||
|
//MainService.startMainService(context);
|
||||||
|
tile.setState(Tile.STATE_ACTIVE);
|
||||||
|
tile.setIcon(android.graphics.drawable.Icon.createWithResource(this, R.drawable.ic_cloud));
|
||||||
|
} else {
|
||||||
|
//MainService.stopMainService(context);
|
||||||
|
tile.setState(Tile.STATE_INACTIVE);
|
||||||
|
tile.setIcon(android.graphics.drawable.Icon.createWithResource(this, R.drawable.ic_cloud_outline));
|
||||||
|
}
|
||||||
|
tile.updateTile();
|
||||||
|
// Tile tile = getQsTile();
|
||||||
|
// tile.setState(Tile.STATE_INACTIVE);
|
||||||
|
// tile.setLabel(getString(R.string.tileservice_name));
|
||||||
|
// tile.setIcon(android.graphics.drawable.Icon.createWithResource(this, R.drawable.ic_cloud_outline));
|
||||||
|
// tile.updateTile();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick() {
|
||||||
|
super.onClick();
|
||||||
|
Tile tile = getQsTile();
|
||||||
|
MainServiceBean bean = MainServiceBean.loadBean(this, MainServiceBean.class);
|
||||||
|
if (bean == null) {
|
||||||
|
bean = new MainServiceBean();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tile.getState() == Tile.STATE_ACTIVE) {
|
||||||
|
bean.setIsEnable(false);
|
||||||
|
MainServiceBean.saveBean(this, bean);
|
||||||
|
MainService.stopMainService(this);
|
||||||
|
} else if (tile.getState() == Tile.STATE_INACTIVE) {
|
||||||
|
bean.setIsEnable(true);
|
||||||
|
MainServiceBean.saveBean(this, bean);
|
||||||
|
MainService.startMainService(this);
|
||||||
|
}
|
||||||
|
updateServiceIconStatus(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void updateServiceIconStatus(Context context) {
|
||||||
|
if (_MyTileService == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Tile tile = _MyTileService.getQsTile();
|
||||||
|
MainServiceBean bean = MainServiceBean.loadBean(context, MainServiceBean.class);
|
||||||
|
if (bean != null && bean.isEnable()) {
|
||||||
|
tile.setState(Tile.STATE_ACTIVE);
|
||||||
|
tile.setIcon(android.graphics.drawable.Icon.createWithResource(context, R.drawable.ic_cloud));
|
||||||
|
} else {
|
||||||
|
tile.setState(Tile.STATE_INACTIVE);
|
||||||
|
tile.setIcon(android.graphics.drawable.Icon.createWithResource(context, R.drawable.ic_cloud_outline));
|
||||||
|
}
|
||||||
|
tile.updateTile();
|
||||||
|
}
|
||||||
|
}
|
||||||
40
winboll/src/main/java/cc/winboll/studio/winboll/WinBoLL.java
Normal file
40
winboll/src/main/java/cc/winboll/studio/winboll/WinBoLL.java
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package cc.winboll.studio.winboll;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2025/05/10 10:13
|
||||||
|
* @Describe WinBoLL 系列应用通用管理类
|
||||||
|
*/
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import cc.winboll.studio.winboll.models.WinBoLLModel;
|
||||||
|
|
||||||
|
public class WinBoLL {
|
||||||
|
|
||||||
|
public static final String TAG = "WinBoLL";
|
||||||
|
|
||||||
|
public static final String ACTION_BIND = WinBoLL.class.getName() + ".ACTION_BIND";
|
||||||
|
public static final String EXTRA_WINBOLLMODEL = "EXTRA_WINBOLLMODEL";
|
||||||
|
|
||||||
|
public static void bindToAPPBase(Context context, String appMainService) {
|
||||||
|
LogUtils.d(TAG, "bindToAPPBase(...)");
|
||||||
|
String toPackage = "cc.winboll.studio.appbase";
|
||||||
|
startBind(context, toPackage, appMainService);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void bindToAPPBaseBeta(Context context, String appMainService) {
|
||||||
|
LogUtils.d(TAG, "bindToAPPBaseBeta(...)");
|
||||||
|
String toPackage = "cc.winboll.studio.appbase.beta";
|
||||||
|
startBind(context, toPackage, appMainService);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void startBind(Context context, String toPackage, String appMainService) {
|
||||||
|
Intent intent = new Intent(ACTION_BIND);
|
||||||
|
intent.putExtra(EXTRA_WINBOLLMODEL, (new WinBoLLModel(toPackage, appMainService)).toString());
|
||||||
|
intent.setPackage(toPackage);
|
||||||
|
LogUtils.d(TAG, String.format("ACTION_BIND :\nTo Package : %s\nAPP Main Service : %s", toPackage, appMainService));
|
||||||
|
context.sendBroadcast(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package cc.winboll.studio.winboll;
|
||||||
|
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.IBinder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen
|
||||||
|
* @Date 2025/05/03 19:28
|
||||||
|
*/
|
||||||
|
public class WinBoLLClientService extends Service {
|
||||||
|
|
||||||
|
public static final String TAG = "WinBoLLClientService";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package cc.winboll.studio.winboll;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen
|
||||||
|
* @Date 2025/05/03 19:14
|
||||||
|
*/
|
||||||
|
public class WinBoLLServiceStatusView extends LinearLayout {
|
||||||
|
|
||||||
|
public static final String TAG = "WinBoLLServiceStatusView";
|
||||||
|
|
||||||
|
public WinBoLLServiceStatusView(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WinBoLLServiceStatusView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WinBoLLServiceStatusView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WinBoLLServiceStatusView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void setServerHost(String szWinBoLLServerHost) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void setAuthInfo(String szDevUserName, String szDevUserPassword) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package cc.winboll.studio.winboll;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信支付配置类
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2026/01/07
|
||||||
|
*/
|
||||||
|
public class WxPayConfig {
|
||||||
|
// ========== 核心修改点:替换为你的服务端地址 ==========
|
||||||
|
// 服务端IP/域名 + 端口(Docker部署的服务端,需确保安卓端可访问)
|
||||||
|
public static final String BASE_URL = "https://wxpay.winboll.cc";
|
||||||
|
|
||||||
|
// 统一下单接口路径(对应服务端的测试接口)
|
||||||
|
public static final String CREATE_ORDER_URL = BASE_URL + "/pay/createOrder";
|
||||||
|
|
||||||
|
// 订单查询接口路径
|
||||||
|
public static final String QUERY_ORDER_URL = BASE_URL + "/pay/queryOrder";
|
||||||
|
|
||||||
|
// ========== 固定支付配置 ==========
|
||||||
|
public static final String ORDER_BODY = "定额测试支付"; // 商品描述
|
||||||
|
public static final int TOTAL_FEE = 1; // 固定金额:1分(沙箱环境推荐)
|
||||||
|
public static final String TRADE_TYPE = "NATIVE"; // 支付类型:二维码
|
||||||
|
|
||||||
|
// ========== 轮询配置 ==========
|
||||||
|
public static final long POLL_INTERVAL = 10000; // 轮询间隔:10秒
|
||||||
|
public static final long POLL_TIMEOUT = 45000; // 轮询超时:45秒
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
package cc.winboll.studio.winboll.activities;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.RadioButton;
|
||||||
|
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
|
||||||
|
import cc.winboll.studio.libappbase.BuildConfig;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import cc.winboll.studio.libappbase.LogView;
|
||||||
|
import cc.winboll.studio.winboll.R;
|
||||||
|
import cc.winboll.studio.winboll.models.UserInfoModel;
|
||||||
|
import cc.winboll.studio.winboll.utils.RSAUtils;
|
||||||
|
import cc.winboll.studio.winboll.utils.YunUtils;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.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,65 @@
|
|||||||
|
package cc.winboll.studio.winboll.activities;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2025/03/25 11:46:40
|
||||||
|
* @Describe 测试窗口2
|
||||||
|
*/
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Toolbar;
|
||||||
|
import cc.winboll.studio.winboll.R;
|
||||||
|
|
||||||
|
public class New2Activity extends WinBoLLActivity {
|
||||||
|
|
||||||
|
public static final String TAG = "New2Activity";
|
||||||
|
|
||||||
|
Toolbar mToolbar;
|
||||||
|
//LogView mLogView;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_new2);
|
||||||
|
|
||||||
|
// mLogView = findViewById(R.id.logview);
|
||||||
|
// mLogView.start();
|
||||||
|
mToolbar = findViewById(R.id.toolbar);
|
||||||
|
setActionBar(mToolbar);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
//mLogView.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onCloseThisActivity(View view) {
|
||||||
|
//WinBoLLActivityManager.getInstance().finish(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onCloseAllActivity(View view) {
|
||||||
|
//WinBoLLActivityManager.getInstance().finishAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onNewActivity(View view) {
|
||||||
|
//WinBoLLActivityManager.getInstance().startWinBoLLActivity(this, NewActivity.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
//getMenuInflater().inflate(R.menu.toolbar_main, menu);
|
||||||
|
return super.onCreateOptionsMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
// 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package cc.winboll.studio.winboll.activities;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||||
|
* @Date 2025/03/25 05:04:22
|
||||||
|
* @Describe
|
||||||
|
*/
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Toolbar;
|
||||||
|
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
|
||||||
|
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
|
||||||
|
import cc.winboll.studio.winboll.R;
|
||||||
|
import cc.winboll.studio.winboll.App;
|
||||||
|
|
||||||
|
public class NewActivity extends WinBoLLActivity implements IWinBoLLActivity {
|
||||||
|
|
||||||
|
public static final String TAG = "NewActivity";
|
||||||
|
|
||||||
|
Toolbar mToolbar;
|
||||||
|
//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_new);
|
||||||
|
// mLogView = findViewById(R.id.logview);
|
||||||
|
// mLogView.start();
|
||||||
|
mToolbar = findViewById(R.id.toolbar);
|
||||||
|
setActionBar(mToolbar);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
//mLogView.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onCloseThisActivity(View view) {
|
||||||
|
WinBoLLActivityManager.getInstance().finish(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onCloseAllActivity(View view) {
|
||||||
|
WinBoLLActivityManager.getInstance().finishAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onNew2Activity(View view) {
|
||||||
|
// WinBoLLActivityManager.getInstance().startWinBoLLActivity(App.getInstance(), New2Activity.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
//getMenuInflater().inflate(R.menu.toolbar_main, menu);
|
||||||
|
return super.onCreateOptionsMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
// 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package cc.winboll.studio.winboll.activities;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
|
||||||
|
import cc.winboll.studio.libaes.utils.AESThemeUtil;
|
||||||
|
import cc.winboll.studio.winboll.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||||
|
* @Date 2025/12/05 18:48
|
||||||
|
* @Describe Settings Activity
|
||||||
|
*/
|
||||||
|
public class SettingsActivity extends WinBoLLActivity implements IWinBoLLActivity {
|
||||||
|
|
||||||
|
public static final String TAG = "SettingsActivity";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Activity getActivity() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTag() {
|
||||||
|
return TAG;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
AESThemeUtil.applyAppTheme(this);
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_settings);
|
||||||
|
|
||||||
|
// 设置工具栏
|
||||||
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
getSupportActionBar().setSubtitle(TAG);
|
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
finish(); // 点击导航栏返回按钮,触发 finish()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package cc.winboll.studio.winboll.activities;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import cc.winboll.studio.libappbase.ToastUtils;
|
||||||
|
import cc.winboll.studio.winboll.App;
|
||||||
|
import cc.winboll.studio.winboll.R;
|
||||||
|
import cc.winboll.studio.winboll.utils.APPPlusUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||||
|
* @Date 2025/11/27 09:00
|
||||||
|
* @Describe 应用快捷方式活动类
|
||||||
|
*/
|
||||||
|
public class ShortcutActionActivity extends Activity {
|
||||||
|
|
||||||
|
public static final String TAG = "ShortcutActionActivity";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
// 处理应用级别的切换请求
|
||||||
|
handleSwitchRequest();
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理应用图标快捷菜单的请求
|
||||||
|
*/
|
||||||
|
private void handleSwitchRequest() {
|
||||||
|
//ToastUtils.show("handleSwitchRequest");
|
||||||
|
Intent intent = getIntent();
|
||||||
|
if (intent != null && "switchto_en1".equals(intent.getDataString())) {
|
||||||
|
APPPlusUtils.switchAppLauncherToComponent(this, App.COMPONENT_EN1);
|
||||||
|
ToastUtils.show("切换至" + getString(R.string.app_name) + "图标");
|
||||||
|
//moveTaskToBack(true);
|
||||||
|
}
|
||||||
|
if (intent != null && "switchto_cn1".equals(intent.getDataString())) {
|
||||||
|
APPPlusUtils.switchAppLauncherToComponent(this, App.COMPONENT_CN1);
|
||||||
|
ToastUtils.show("切换至" + getString(R.string.app_name_cn1) + "图标");
|
||||||
|
//moveTaskToBack(true);
|
||||||
|
}
|
||||||
|
if (intent != null && "switchto_cn2".equals(intent.getDataString())) {
|
||||||
|
APPPlusUtils.switchAppLauncherToComponent(this, App.COMPONENT_CN2);
|
||||||
|
ToastUtils.show("切换至" + getString(R.string.app_name_cn2) + "图标");
|
||||||
|
//moveTaskToBack(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,211 @@
|
|||||||
|
package cc.winboll.studio.winboll.activities;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
|
||||||
|
import cc.winboll.studio.winboll.R;
|
||||||
|
import cc.winboll.studio.winboll.WxPayConfig;
|
||||||
|
import cc.winboll.studio.winboll.utils.SpecUtil;
|
||||||
|
import cc.winboll.studio.winboll.utils.WxPayApi;
|
||||||
|
import cc.winboll.studio.winboll.utils.ZXingUtils;
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主界面:生成二维码 + 轮询查询支付结果
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2026/01/07
|
||||||
|
*/
|
||||||
|
public class WXPayActivity extends WinBoLLActivity implements IWinBoLLActivity {
|
||||||
|
|
||||||
|
private static final String TAG = "WXPayActivity";
|
||||||
|
|
||||||
|
// Handler消息标识
|
||||||
|
private static final int MSG_POLL_TIMEOUT = 1001;
|
||||||
|
private static final int MSG_POLL_SUCCESS = 1002;
|
||||||
|
private static final int MSG_POLL_FAILED = 1003;
|
||||||
|
|
||||||
|
private ImageView mIvQrCode;
|
||||||
|
private TextView mTvOrderNo;
|
||||||
|
private TextView mTvPayStatus;
|
||||||
|
private Button mBtnCreateOrder;
|
||||||
|
|
||||||
|
private String mOutTradeNo; // 商户订单号
|
||||||
|
private Timer mPollTimer; // 轮询定时器
|
||||||
|
private long mPollStartTime; // 轮询开始时间
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Activity getActivity() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTag() {
|
||||||
|
return TAG;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Handler mPollHandler = new Handler() {
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message msg) {
|
||||||
|
super.handleMessage(msg);
|
||||||
|
switch (msg.what) {
|
||||||
|
case MSG_POLL_TIMEOUT:
|
||||||
|
stopPoll();
|
||||||
|
mTvPayStatus.setText("支付状态:轮询超时");
|
||||||
|
mTvPayStatus.setTextColor(getResources().getColor(android.R.color.darker_gray));
|
||||||
|
Toast.makeText(WXPayActivity.this, "轮询超时,请手动查询", Toast.LENGTH_SHORT).show();
|
||||||
|
break;
|
||||||
|
case MSG_POLL_SUCCESS:
|
||||||
|
boolean isPaySuccess = (boolean) msg.obj;
|
||||||
|
String tradeState = (String) msg.getData().getString("tradeState");
|
||||||
|
stopPoll();
|
||||||
|
if (isPaySuccess) {
|
||||||
|
mTvPayStatus.setText("支付状态:支付成功 ✅");
|
||||||
|
mTvPayStatus.setTextColor(getResources().getColor(android.R.color.holo_green_dark));
|
||||||
|
Toast.makeText(WXPayActivity.this, "支付成功!", Toast.LENGTH_SHORT).show();
|
||||||
|
} else {
|
||||||
|
mTvPayStatus.setText("支付状态:" + tradeState);
|
||||||
|
mTvPayStatus.setTextColor(getResources().getColor(android.R.color.holo_red_dark));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MSG_POLL_FAILED:
|
||||||
|
String errorMsg = (String) msg.obj;
|
||||||
|
mTvPayStatus.setText("查询失败:" + errorMsg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_wxpay);
|
||||||
|
|
||||||
|
initView();
|
||||||
|
initListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initView() {
|
||||||
|
mIvQrCode = findViewById(R.id.iv_qrcode);
|
||||||
|
mTvOrderNo = findViewById(R.id.tv_order_no);
|
||||||
|
mTvPayStatus = findViewById(R.id.tv_pay_status);
|
||||||
|
mBtnCreateOrder = findViewById(R.id.btn_create_order);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initListener() {
|
||||||
|
mBtnCreateOrder.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
createOrder();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一下单,生成二维码
|
||||||
|
*/
|
||||||
|
private void createOrder() {
|
||||||
|
mBtnCreateOrder.setEnabled(false);
|
||||||
|
mTvPayStatus.setText("支付状态:生成订单中...");
|
||||||
|
mIvQrCode.setImageBitmap(null);
|
||||||
|
|
||||||
|
WxPayApi.createOrder(new WxPayApi.OnCreateOrderCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(String outTradeNo, String codeUrl) {
|
||||||
|
mOutTradeNo = outTradeNo;
|
||||||
|
mTvOrderNo.setText("商户订单号:" + outTradeNo);
|
||||||
|
mTvPayStatus.setText("支付状态:未支付,请扫码");
|
||||||
|
|
||||||
|
// 生成二维码
|
||||||
|
Bitmap qrCodeBitmap = ZXingUtils.createQRCodeBitmap(codeUrl, 250, 250);
|
||||||
|
if (qrCodeBitmap != null) {
|
||||||
|
mIvQrCode.setImageBitmap(qrCodeBitmap);
|
||||||
|
// 开始轮询查询支付结果
|
||||||
|
startPoll();
|
||||||
|
} else {
|
||||||
|
mTvPayStatus.setText("支付状态:生成二维码失败");
|
||||||
|
mBtnCreateOrder.setEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(String errorMsg) {
|
||||||
|
SpecUtil.WWSpecLogError(TAG, "统一下单失败:" + errorMsg);
|
||||||
|
mTvPayStatus.setText("生成订单失败:" + errorMsg);
|
||||||
|
mBtnCreateOrder.setEnabled(true);
|
||||||
|
Toast.makeText(WXPayActivity.this, errorMsg, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始轮询查询支付结果
|
||||||
|
*/
|
||||||
|
private void startPoll() {
|
||||||
|
stopPoll(); // 先停止之前的轮询
|
||||||
|
mPollStartTime = System.currentTimeMillis();
|
||||||
|
mPollTimer = new Timer();
|
||||||
|
mPollTimer.scheduleAtFixedRate(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// 检查是否超时
|
||||||
|
long elapsedTime = System.currentTimeMillis() - mPollStartTime;
|
||||||
|
if (elapsedTime >= WxPayConfig.POLL_TIMEOUT) {
|
||||||
|
mPollHandler.sendEmptyMessage(MSG_POLL_TIMEOUT);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询订单状态
|
||||||
|
WxPayApi.queryOrder(mOutTradeNo, new WxPayApi.OnQueryOrderCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(boolean isPaySuccess, String tradeState) {
|
||||||
|
Message msg = Message.obtain();
|
||||||
|
msg.what = MSG_POLL_SUCCESS;
|
||||||
|
msg.obj = isPaySuccess;
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putString("tradeState", tradeState);
|
||||||
|
msg.setData(bundle);
|
||||||
|
mPollHandler.sendMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(String errorMsg) {
|
||||||
|
Message msg = Message.obtain();
|
||||||
|
msg.what = MSG_POLL_FAILED;
|
||||||
|
msg.obj = errorMsg;
|
||||||
|
mPollHandler.sendMessage(msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 0, WxPayConfig.POLL_INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 停止轮询
|
||||||
|
*/
|
||||||
|
private void stopPoll() {
|
||||||
|
if (mPollTimer != null) {
|
||||||
|
mPollTimer.cancel();
|
||||||
|
mPollTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
stopPoll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package cc.winboll.studio.winboll.activities;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2025/05/10 09:48
|
||||||
|
* @Describe WinBoLL 窗口基础类
|
||||||
|
*/
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
|
||||||
|
public class WinBoLLActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
public static final String TAG = "WinBoLLActivity";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
/*if (item.getItemId() == R.id.item_log) {
|
||||||
|
WinBoLLActivityManager.getInstance().startLogActivity(this);
|
||||||
|
return true;
|
||||||
|
} else if (item.getItemId() == R.id.item_home) {
|
||||||
|
startActivity(new Intent(this, MainActivity.class));
|
||||||
|
return true;
|
||||||
|
}*/
|
||||||
|
// 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostCreate(Bundle savedInstanceState) {
|
||||||
|
super.onPostCreate(savedInstanceState);
|
||||||
|
//WinBoLLActivityManager.getInstance().add(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
//WinBoLLActivityManager.getInstance().registeRemove(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
package cc.winboll.studio.winboll.activities;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.CheckBox;
|
||||||
|
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
|
||||||
|
import cc.winboll.studio.libappbase.CrashHandler;
|
||||||
|
import cc.winboll.studio.libappbase.GlobalApplication;
|
||||||
|
import cc.winboll.studio.libappbase.GlobalCrashActivity;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import cc.winboll.studio.libappbase.ToastUtils;
|
||||||
|
import cc.winboll.studio.winboll.App;
|
||||||
|
import cc.winboll.studio.winboll.R;
|
||||||
|
import cc.winboll.studio.winboll.services.MainService;
|
||||||
|
import cc.winboll.studio.winboll.services.TestDemoBindService;
|
||||||
|
import cc.winboll.studio.winboll.services.TestDemoService;
|
||||||
|
import cc.winboll.studio.winboll.sos.SOS;
|
||||||
|
import cc.winboll.studio.winboll.widgets.StatusWidget;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
|
||||||
|
public class WinBoLLUnitTestActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
public static final String TAG = "MainActivity";
|
||||||
|
|
||||||
|
Toolbar mToolbar;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
ToastUtils.show("onCreate");
|
||||||
|
setContentView(R.layout.activity_winbollunittest);
|
||||||
|
|
||||||
|
mToolbar = findViewById(R.id.toolbar);
|
||||||
|
setSupportActionBar(mToolbar);
|
||||||
|
|
||||||
|
CheckBox cbIsDebugMode = findViewById(R.id.activitymainCheckBox1);
|
||||||
|
cbIsDebugMode.setChecked(GlobalApplication.isDebugging());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
//getMenuInflater().inflate(R.menu.toolbar_main, menu);
|
||||||
|
return super.onCreateOptionsMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
// if(item.getItemId() == R.id.item_yun) {
|
||||||
|
// WinBoLLActivityManager.getInstance().startWinBoLLActivity(this, cc.winboll.studio.winboll.activities.YunActivity.class);
|
||||||
|
// } else if(item.getItemId() == R.id.item_logon) {
|
||||||
|
// WinBoLLActivityManager.getInstance().startWinBoLLActivity(this, cc.winboll.studio.winboll.activities.LogonActivity.class);
|
||||||
|
// }
|
||||||
|
// 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
Intent intentAPPWidget = new Intent(this, StatusWidget.class);
|
||||||
|
intentAPPWidget.setAction(StatusWidget.ACTION_STATUS_UPDATE);
|
||||||
|
sendBroadcast(intentAPPWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onSwitchDebugMode(View view) {
|
||||||
|
boolean isDebuging = ((CheckBox)view).isChecked();
|
||||||
|
GlobalApplication.setIsDebugging(isDebuging);
|
||||||
|
GlobalApplication.saveDebugStatus((App.getInstance()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onPreviewGlobalCrashActivity(View view) {
|
||||||
|
Intent intent = new Intent(this, GlobalCrashActivity.class);
|
||||||
|
intent.putExtra(CrashHandler.EXTRA_CRASH_LOG, "Demo log...");
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onStartCenter(View view) {
|
||||||
|
MainService.startMainService(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onStopCenter(View view) {
|
||||||
|
MainService.stopMainService(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onTestStopMainServiceWithoutSettingEnable(View view) {
|
||||||
|
LogUtils.d(TAG, "onTestStopMainServiceWithoutSettingEnable");
|
||||||
|
stopService(new Intent(this, MainService.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onTestUseComponentStartService(View view) {
|
||||||
|
LogUtils.d(TAG, "onTestUseComponentStartService");
|
||||||
|
|
||||||
|
// 目标服务的包名和类名
|
||||||
|
String packageName = this.getPackageName();
|
||||||
|
String serviceClassName = TestDemoService.class.getName();
|
||||||
|
|
||||||
|
// 构建Intent
|
||||||
|
Intent intentService = new Intent();
|
||||||
|
intentService.setComponent(new ComponentName(packageName, serviceClassName));
|
||||||
|
|
||||||
|
startService(intentService);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onTestDemoServiceSOS(View view) {
|
||||||
|
Intent intent = new Intent(this, TestDemoService.class);
|
||||||
|
stopService(intent);
|
||||||
|
if (App.isDebugging()) {
|
||||||
|
SOS.sosToAppBaseBeta(this, TestDemoService.class.getName());
|
||||||
|
} else {
|
||||||
|
SOS.sosToAppBase(this, TestDemoService.class.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onSartTestDemoService(View view) {
|
||||||
|
Intent intent = new Intent(this, TestDemoService.class);
|
||||||
|
intent.setAction(TestDemoService.ACTION_ENABLE);
|
||||||
|
startService(intent);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onStopTestDemoService(View view) {
|
||||||
|
Intent intent = new Intent(this, TestDemoService.class);
|
||||||
|
intent.setAction(TestDemoService.ACTION_DISABLE);
|
||||||
|
startService(intent);
|
||||||
|
|
||||||
|
Intent intentStop = new Intent(this, TestDemoService.class);
|
||||||
|
stopService(intentStop);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onStopTestDemoServiceNoSettings(View view) {
|
||||||
|
Intent intent = new Intent(this, TestDemoService.class);
|
||||||
|
stopService(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onSartTestDemoBindService(View view) {
|
||||||
|
Intent intent = new Intent(this, TestDemoBindService.class);
|
||||||
|
intent.setAction(TestDemoBindService.ACTION_ENABLE);
|
||||||
|
startService(intent);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onStopTestDemoBindService(View view) {
|
||||||
|
Intent intent = new Intent(this, TestDemoBindService.class);
|
||||||
|
intent.setAction(TestDemoBindService.ACTION_DISABLE);
|
||||||
|
startService(intent);
|
||||||
|
|
||||||
|
Intent intentStop = new Intent(this, TestDemoBindService.class);
|
||||||
|
stopService(intentStop);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onStopTestDemoBindServiceNoSettings(View view) {
|
||||||
|
Intent intent = new Intent(this, TestDemoBindService.class);
|
||||||
|
stopService(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onTestOpenNewActivity(View view) {
|
||||||
|
WinBoLLActivityManager.getInstance().startWinBoLLActivity(this, NewActivity.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
package cc.winboll.studio.winboll.activities;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2025/06/04 11:06
|
||||||
|
* @Describe 云宝云
|
||||||
|
*/
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.RadioButton;
|
||||||
|
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
|
||||||
|
import cc.winboll.studio.libappbase.BuildConfig;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import cc.winboll.studio.libappbase.LogView;
|
||||||
|
import cc.winboll.studio.winboll.R;
|
||||||
|
import java.io.IOException;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
|
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,319 @@
|
|||||||
|
package cc.winboll.studio.winboll.fragments;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import cc.winboll.studio.winboll.MainActivity;
|
||||||
|
import cc.winboll.studio.winboll.R;
|
||||||
|
import cc.winboll.studio.winboll.views.WinBoLLView;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import android.app.Activity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||||
|
* @Date 2025/11/27 11:09
|
||||||
|
* @Describe 浏览器Fragment(Java 7 语法完整版,新增Handler消息接收)
|
||||||
|
* 适配Java 7特性,支持接收应用内消息(如MSG_HOMEPAGE跳转首页)
|
||||||
|
*/
|
||||||
|
public class BrowserFragment extends Fragment implements View.OnClickListener, WinBoLLView.OnPageStatusListener {
|
||||||
|
|
||||||
|
// 控件声明(Java 7 成员变量显式声明)
|
||||||
|
private EditText mEtUrl;
|
||||||
|
private Button mBtnLoad;
|
||||||
|
private Button mBtnRefresh;
|
||||||
|
private Button mBtnStop;
|
||||||
|
private Button mBtnForward;
|
||||||
|
private Button mBtnBack;
|
||||||
|
private ProgressBar mProgressBar;
|
||||||
|
private WinBoLLView mWinBoLLView;
|
||||||
|
public static ArrayList<String> _mUrlLoadHistory = new ArrayList<String>();
|
||||||
|
|
||||||
|
// ------------------- 新增:Handler 消息定义(应用内通信) -------------------
|
||||||
|
// 消息标识:跳转首页(winboll.cc)
|
||||||
|
public static final int MSG_HOMEPAGE = 1001;
|
||||||
|
// 跳转到历史记录位置
|
||||||
|
public static final int MSG_HISTORY_POSITION = 1002;
|
||||||
|
// 自定义Handler(接收应用内其他页面发送的消息)
|
||||||
|
private Handler mBrowserHandler;
|
||||||
|
|
||||||
|
// 单例创建方法(Java 7 静态工厂模式)
|
||||||
|
public static BrowserFragment newInstance() {
|
||||||
|
return new BrowserFragment();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
|
// 加载布局(Java 7 显式强转,无菱形语法)
|
||||||
|
View view = inflater.inflate(R.layout.fragment_browser, container, false);
|
||||||
|
// 清理旧历史记录
|
||||||
|
_mUrlLoadHistory.clear();
|
||||||
|
|
||||||
|
// 初始化控件
|
||||||
|
initViews(view);
|
||||||
|
// 绑定事件
|
||||||
|
initEvents();
|
||||||
|
// 初始化WinBoLLView
|
||||||
|
initWinBoLLView();
|
||||||
|
// ------------------- 新增:初始化Handler(关键) -------------------
|
||||||
|
initHandler();
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化控件(Java 7 显式绑定,无Stream简化)
|
||||||
|
*/
|
||||||
|
private void initViews(View view) {
|
||||||
|
mEtUrl = (EditText) view.findViewById(R.id.et_url);
|
||||||
|
mBtnLoad = (Button) view.findViewById(R.id.btn_load);
|
||||||
|
mBtnRefresh = (Button) view.findViewById(R.id.btn_refresh);
|
||||||
|
mBtnStop = (Button) view.findViewById(R.id.btn_stop);
|
||||||
|
mBtnForward = (Button) view.findViewById(R.id.btn_forward);
|
||||||
|
mBtnBack = (Button) view.findViewById(R.id.btn_back);
|
||||||
|
mProgressBar = (ProgressBar) view.findViewById(R.id.progress_bar);
|
||||||
|
mWinBoLLView = (WinBoLLView) view.findViewById(R.id.winboll_webview);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绑定点击事件(Java 7 匿名内部类,无Lambda)
|
||||||
|
*/
|
||||||
|
private void initEvents() {
|
||||||
|
// 功能按钮点击事件
|
||||||
|
mBtnLoad.setOnClickListener(this);
|
||||||
|
mBtnRefresh.setOnClickListener(this);
|
||||||
|
mBtnStop.setOnClickListener(this);
|
||||||
|
mBtnForward.setOnClickListener(this);
|
||||||
|
mBtnBack.setOnClickListener(this);
|
||||||
|
|
||||||
|
// 输入框软键盘“前往”按钮事件(Java 7 匿名内部类实现)
|
||||||
|
mEtUrl.setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onEditorAction(TextView v, int actionId, android.view.KeyEvent event) {
|
||||||
|
// 处理软键盘“前往”点击
|
||||||
|
loadUrlFromInput();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化WinBoLLView(Java 7 显式调用,无方法引用)
|
||||||
|
*/
|
||||||
|
private void initWinBoLLView() {
|
||||||
|
// 绑定进度条
|
||||||
|
mWinBoLLView.setProgressBar(mProgressBar);
|
||||||
|
// 设置页面状态监听(this 实现 OnPageStatusListener)
|
||||||
|
mWinBoLLView.setOnPageStatusListener(this);
|
||||||
|
// 预加载默认页面(winboll.cc 首页)
|
||||||
|
String defaultUrl = "https://www.winboll.cc";
|
||||||
|
mWinBoLLView.loadUrlSafe(defaultUrl);
|
||||||
|
mEtUrl.setText(defaultUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------- 新增:初始化Handler(接收应用内消息) -------------------
|
||||||
|
private void initHandler() {
|
||||||
|
// Java 7 匿名内部类实现Handler(主线程中创建,用于更新UI)
|
||||||
|
mBrowserHandler = new Handler() {
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message msg) {
|
||||||
|
super.handleMessage(msg);
|
||||||
|
// 根据消息标识处理不同逻辑
|
||||||
|
switch (msg.what) {
|
||||||
|
case MSG_HOMEPAGE:
|
||||||
|
// 处理“跳转首页”消息:加载winboll.cc
|
||||||
|
String homeUrl = "https://www.winboll.cc";
|
||||||
|
mWinBoLLView.loadUrlSafe(homeUrl);
|
||||||
|
mEtUrl.setText(homeUrl);
|
||||||
|
showToast("已跳转至首页");
|
||||||
|
break;
|
||||||
|
case MSG_HISTORY_POSITION:
|
||||||
|
int position = (int)msg.obj;
|
||||||
|
if(-1 < position && position < _mUrlLoadHistory.size()) {
|
||||||
|
// 处理“跳转首页”消息:加载winboll.cc
|
||||||
|
String historyUrl = _mUrlLoadHistory.get(position);
|
||||||
|
mWinBoLLView.loadUrlSafe(historyUrl);
|
||||||
|
mEtUrl.setText(historyUrl);
|
||||||
|
//showToast("已跳转至" + historyUrl);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点击事件处理(Java 7 switch-case 语句,无增强switch)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
int id = v.getId();
|
||||||
|
if (id == R.id.btn_load) {
|
||||||
|
// 加载输入框中的URL
|
||||||
|
loadUrlFromInput();
|
||||||
|
} else if (id == R.id.btn_refresh) {
|
||||||
|
// 刷新当前页面
|
||||||
|
mWinBoLLView.refreshPage();
|
||||||
|
} else if (id == R.id.btn_stop) {
|
||||||
|
// 停止页面加载
|
||||||
|
mWinBoLLView.stopPageLoad();
|
||||||
|
} else if (id == R.id.btn_forward) {
|
||||||
|
// 前进(无历史则提示)
|
||||||
|
if (!mWinBoLLView.goForwardSafe()) {
|
||||||
|
showToast("无前进历史");
|
||||||
|
}
|
||||||
|
} else if (id == R.id.btn_back) {
|
||||||
|
// 后退(无历史则提示)
|
||||||
|
if (!mWinBoLLView.goBackSafe()) {
|
||||||
|
showToast("无后退历史");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从输入框获取URL并加载(Java 7 显式空值校验)
|
||||||
|
*/
|
||||||
|
private void loadUrlFromInput() {
|
||||||
|
// 空值校验(Java 7 显式判断,无Objects.requireNonNull)
|
||||||
|
if (mEtUrl == null) {
|
||||||
|
showToast("控件初始化失败");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String url = mEtUrl.getText().toString().trim();
|
||||||
|
// 调用WinBoLLView安全加载方法
|
||||||
|
mWinBoLLView.loadUrlSafe(url);
|
||||||
|
// 隐藏软键盘
|
||||||
|
hideSoftKeyboard();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 隐藏软键盘(Java 7 显式获取系统服务,无Lambda简化)
|
||||||
|
*/
|
||||||
|
private void hideSoftKeyboard() {
|
||||||
|
if (getActivity() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 获取InputMethodManager(Java 7 显式强转)
|
||||||
|
InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(android.content.Context.INPUT_METHOD_SERVICE);
|
||||||
|
if (imm != null && getActivity().getCurrentFocus() != null) {
|
||||||
|
imm.hideSoftInputFromWindow(getActivity().getCurrentFocus().getWindowToken(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示Toast提示(Java 7 简化封装,避免重复代码)
|
||||||
|
*/
|
||||||
|
private void showToast(String msg) {
|
||||||
|
if (getActivity() == null || msg == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Java 7 显式创建Toast,无Toast.makeText简化链式调用
|
||||||
|
Toast toast = Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT);
|
||||||
|
toast.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------- WinBoLLView.OnPageStatusListener 实现(Java 7 显式重写) -------------------
|
||||||
|
@Override
|
||||||
|
public void onPageStarted(String url) {
|
||||||
|
// 页面开始加载:更新输入框URL(Java 7 显式非空判断)
|
||||||
|
if (mEtUrl != null && url != null) {
|
||||||
|
mEtUrl.setText(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPageFinished(String url) {
|
||||||
|
// 页面加载完成:更新输入框URL
|
||||||
|
if (mEtUrl != null && url != null) {
|
||||||
|
mEtUrl.setText(url);
|
||||||
|
addUrlToHistory(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPageError(String errorMsg) {
|
||||||
|
// 页面加载错误:显示错误提示
|
||||||
|
showToast("加载失败:" + errorMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------- 新增:对外提供Handler(供其他页面获取并发送消息) -------------------
|
||||||
|
public Handler getBrowserHandler() {
|
||||||
|
return mBrowserHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------- 生命周期管理(防止内存泄漏,Java 7 显式重写) -------------------
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
// 销毁WinBoLLView(释放资源,避免内存泄漏)
|
||||||
|
if (mWinBoLLView != null) {
|
||||||
|
mWinBoLLView.destroyWebView();
|
||||||
|
mWinBoLLView = null;
|
||||||
|
}
|
||||||
|
// ------------------- 新增:移除Handler消息(关键,防止内存泄漏) -------------------
|
||||||
|
if (mBrowserHandler != null) {
|
||||||
|
mBrowserHandler.removeCallbacksAndMessages(null); // 清除所有消息和回调
|
||||||
|
mBrowserHandler = null;
|
||||||
|
}
|
||||||
|
// 置空控件(帮助GC回收)
|
||||||
|
mEtUrl = null;
|
||||||
|
mBtnLoad = null;
|
||||||
|
mBtnRefresh = null;
|
||||||
|
mBtnStop = null;
|
||||||
|
mBtnForward = null;
|
||||||
|
mBtnBack = null;
|
||||||
|
mProgressBar = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
// 彻底释放资源
|
||||||
|
if (mWinBoLLView != null) {
|
||||||
|
mWinBoLLView.destroyWebView();
|
||||||
|
mWinBoLLView = null;
|
||||||
|
}
|
||||||
|
// 再次清除Handler(双重保险)
|
||||||
|
if (mBrowserHandler != null) {
|
||||||
|
mBrowserHandler.removeCallbacksAndMessages(null);
|
||||||
|
mBrowserHandler = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在 BrowserFragment 中添加以下代码(Java 7 语法)
|
||||||
|
/**
|
||||||
|
* 发送URL历史更新消息给MainActivity(当历史列表变化时调用)
|
||||||
|
*/
|
||||||
|
private void sendUrlHistoryUpdateMsg() {
|
||||||
|
Message msg = Message.obtain();
|
||||||
|
msg.what = MainActivity.MSG_URLLOADHISTORY_UPDATE;
|
||||||
|
MainActivity.sendMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用时机示例(在BrowserFragment加载URL并更新历史列表后调用)
|
||||||
|
// 假设BrowserFragment中有添加URL到历史的方法:
|
||||||
|
private void addUrlToHistory(String url) {
|
||||||
|
if (_mUrlLoadHistory == null) {
|
||||||
|
_mUrlLoadHistory = new ArrayList<String>();
|
||||||
|
}
|
||||||
|
if (!_mUrlLoadHistory.contains(url)) {
|
||||||
|
_mUrlLoadHistory.add(0, url);
|
||||||
|
// 关键:添加历史后发送更新消息,通知MainActivity刷新抽屉菜单
|
||||||
|
sendUrlHistoryUpdateMsg();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package cc.winboll.studio.winboll.fragments;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||||
|
* @Date 2025/09/29 13:15
|
||||||
|
* @Describe MainFragment
|
||||||
|
*/
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.Switch;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import cc.winboll.studio.libappbase.ToastUtils;
|
||||||
|
import cc.winboll.studio.winboll.R;
|
||||||
|
|
||||||
|
|
||||||
|
public class MainFragment extends Fragment {
|
||||||
|
|
||||||
|
public static final String TAG = "MainFragment";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
View view = inflater.inflate(R.layout.fragment_main, container, false);
|
||||||
|
Switch swEnablePosition = view.findViewById(R.id.fragmentmainSwitch1);
|
||||||
|
swEnablePosition.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
ToastUtils.show("Position");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package cc.winboll.studio.winboll.handlers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2025/02/14 03:51:40
|
||||||
|
*/
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Message;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import cc.winboll.studio.winboll.services.MainService;
|
||||||
|
|
||||||
|
public class MainServiceHandler extends Handler {
|
||||||
|
public static final String TAG = "MainServiceHandler";
|
||||||
|
|
||||||
|
public static final int MSG_REMINDTHREAD = 0;
|
||||||
|
|
||||||
|
WeakReference<MainService> serviceWeakReference;
|
||||||
|
public MainServiceHandler(MainService service) {
|
||||||
|
serviceWeakReference = new WeakReference<MainService>(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message msg) {
|
||||||
|
switch (msg.what) {
|
||||||
|
case MSG_REMINDTHREAD: // 处理下载完成消息,更新UI
|
||||||
|
{
|
||||||
|
// 显示提醒消息
|
||||||
|
//
|
||||||
|
//LogUtils.d(TAG, "显示提醒消息");
|
||||||
|
MainService mainService = serviceWeakReference.get();
|
||||||
|
if (mainService != null) {
|
||||||
|
mainService.appenMessage((String)msg.obj);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package cc.winboll.studio.winboll.models;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2025/02/13 07:06:13
|
||||||
|
*/
|
||||||
|
import android.util.JsonReader;
|
||||||
|
import android.util.JsonWriter;
|
||||||
|
import cc.winboll.studio.libappbase.BaseBean;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class MainServiceBean extends BaseBean {
|
||||||
|
|
||||||
|
public static final String TAG = "MainServiceBean";
|
||||||
|
|
||||||
|
boolean isEnable;
|
||||||
|
|
||||||
|
public MainServiceBean() {
|
||||||
|
this.isEnable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsEnable(boolean isEnable) {
|
||||||
|
this.isEnable = isEnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnable() {
|
||||||
|
return isEnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return MainServiceBean.class.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
|
||||||
|
super.writeThisToJsonWriter(jsonWriter);
|
||||||
|
jsonWriter.name("isEnable").value(isEnable());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
|
||||||
|
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
|
||||||
|
if (name.equals("isEnable")) {
|
||||||
|
setIsEnable(jsonReader.nextBoolean());
|
||||||
|
} 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,53 @@
|
|||||||
|
package cc.winboll.studio.winboll.models;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.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,67 @@
|
|||||||
|
package cc.winboll.studio.winboll.models;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2025/03/07 12:47:22
|
||||||
|
* @Describe TestServiceBean
|
||||||
|
*/
|
||||||
|
import android.util.JsonReader;
|
||||||
|
import android.util.JsonWriter;
|
||||||
|
import cc.winboll.studio.libappbase.BaseBean;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class TestDemoBindServiceBean extends BaseBean {
|
||||||
|
|
||||||
|
public static final String TAG = "TestServiceBean";
|
||||||
|
|
||||||
|
boolean isEnable;
|
||||||
|
|
||||||
|
public TestDemoBindServiceBean() {
|
||||||
|
this.isEnable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsEnable(boolean isEnable) {
|
||||||
|
this.isEnable = isEnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnable() {
|
||||||
|
return isEnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return TestDemoBindServiceBean.class.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
|
||||||
|
super.writeThisToJsonWriter(jsonWriter);
|
||||||
|
jsonWriter.name("isEnable").value(isEnable());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
|
||||||
|
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
|
||||||
|
if (name.equals("isEnable")) {
|
||||||
|
setIsEnable(jsonReader.nextBoolean());
|
||||||
|
} 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,68 @@
|
|||||||
|
package cc.winboll.studio.winboll.models;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2025/03/07 12:49:21
|
||||||
|
* @Describe TestDemoServiceBean
|
||||||
|
*/
|
||||||
|
import android.util.JsonReader;
|
||||||
|
import android.util.JsonWriter;
|
||||||
|
import cc.winboll.studio.libappbase.BaseBean;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class TestDemoServiceBean extends BaseBean {
|
||||||
|
|
||||||
|
public static final String TAG = "TestDemoServiceBean";
|
||||||
|
|
||||||
|
boolean isEnable;
|
||||||
|
|
||||||
|
public TestDemoServiceBean() {
|
||||||
|
this.isEnable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsEnable(boolean isEnable) {
|
||||||
|
this.isEnable = isEnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnable() {
|
||||||
|
return isEnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return TestDemoServiceBean.class.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
|
||||||
|
super.writeThisToJsonWriter(jsonWriter);
|
||||||
|
jsonWriter.name("isEnable").value(isEnable());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
|
||||||
|
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
|
||||||
|
if (name.equals("isEnable")) {
|
||||||
|
setIsEnable(jsonReader.nextBoolean());
|
||||||
|
} 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,92 @@
|
|||||||
|
package cc.winboll.studio.winboll.models;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.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,92 @@
|
|||||||
|
package cc.winboll.studio.winboll.models;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2025/05/10 10:16
|
||||||
|
* @Describe WinBoLLModel
|
||||||
|
*/
|
||||||
|
import android.util.JsonReader;
|
||||||
|
import android.util.JsonWriter;
|
||||||
|
import cc.winboll.studio.libappbase.BaseBean;
|
||||||
|
import java.io.IOException;
|
||||||
|
import cc.winboll.studio.libappbase.APPModel;
|
||||||
|
|
||||||
|
public class WinBoLLModel extends BaseBean {
|
||||||
|
|
||||||
|
public static final String TAG = "WinBoLLModel";
|
||||||
|
|
||||||
|
String appPackageName;
|
||||||
|
String appMainServiveName;
|
||||||
|
|
||||||
|
public WinBoLLModel() {
|
||||||
|
this.appPackageName = "";
|
||||||
|
this.appMainServiveName = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public WinBoLLModel(boolean isDebuging, String appPackageName, String appMainServiveName) {
|
||||||
|
this.appPackageName = appPackageName;
|
||||||
|
this.appMainServiveName = appMainServiveName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WinBoLLModel(String appPackageName, String appMainServiveName) {
|
||||||
|
this.appPackageName = appPackageName;
|
||||||
|
this.appMainServiveName = appMainServiveName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAppPackageName(String appPackageName) {
|
||||||
|
this.appPackageName = appPackageName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAppPackageName() {
|
||||||
|
return appPackageName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAppMainServiveName(String appMainServiveName) {
|
||||||
|
this.appMainServiveName = appMainServiveName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAppMainServiveName() {
|
||||||
|
return appMainServiveName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return APPModel.class.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
|
||||||
|
super.writeThisToJsonWriter(jsonWriter);
|
||||||
|
jsonWriter.name("appPackageName").value(getAppPackageName());
|
||||||
|
jsonWriter.name("appMainServiveName").value(getAppMainServiveName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
|
||||||
|
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
|
||||||
|
if (name.equals("appPackageName")) {
|
||||||
|
setAppPackageName(jsonReader.nextString());
|
||||||
|
} else if (name.equals("appMainServiveName")) {
|
||||||
|
setAppMainServiveName(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,71 @@
|
|||||||
|
package cc.winboll.studio.winboll.models;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2025/05/10 09:36
|
||||||
|
* @Describe WinBoLL 应用消息数据模型
|
||||||
|
*/
|
||||||
|
import android.util.JsonReader;
|
||||||
|
import android.util.JsonWriter;
|
||||||
|
import cc.winboll.studio.libappbase.BaseBean;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class WinBoLLNewsBean extends BaseBean {
|
||||||
|
|
||||||
|
public static final String TAG = "WinBoLLNewsBean";
|
||||||
|
|
||||||
|
String message;
|
||||||
|
|
||||||
|
public WinBoLLNewsBean() {
|
||||||
|
this.message = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public WinBoLLNewsBean(String message) {
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessage(String message) {
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return WinBoLLNewsBean.class.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
|
||||||
|
super.writeThisToJsonWriter(jsonWriter);
|
||||||
|
jsonWriter.name("message").value(getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
|
||||||
|
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
|
||||||
|
if (name.equals("message")) {
|
||||||
|
setMessage(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,36 @@
|
|||||||
|
package cc.winboll.studio.winboll.receivers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2025/03/24 07:11:44
|
||||||
|
*/
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import cc.winboll.studio.winboll.widgets.APPNewsWidget;
|
||||||
|
|
||||||
|
public class APPNewsWidgetClickListener extends BroadcastReceiver {
|
||||||
|
|
||||||
|
public static final String TAG = "APPNewsWidgetClickListener";
|
||||||
|
public static final String ACTION_PRE = APPNewsWidgetClickListener.class.getName() + ".ACTION_PRE";
|
||||||
|
public static final String ACTION_NEXT = APPNewsWidgetClickListener.class.getName() + ".ACTION_NEXT";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
String action = intent.getAction();
|
||||||
|
if (action == null) {
|
||||||
|
LogUtils.d(TAG, String.format("action %s", action));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (action.equals(ACTION_PRE)) {
|
||||||
|
LogUtils.d(TAG, "ACTION_PRE");
|
||||||
|
APPNewsWidget.prePage(context);
|
||||||
|
} else if (action.equals(ACTION_NEXT)) {
|
||||||
|
LogUtils.d(TAG, "ACTION_NEXT");
|
||||||
|
APPNewsWidget.nextPage(context);
|
||||||
|
} else {
|
||||||
|
LogUtils.d(TAG, String.format("action %s", action));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
package cc.winboll.studio.winboll.receivers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2025/02/13 06:58:04
|
||||||
|
* @Describe 主要广播接收器
|
||||||
|
*/
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import cc.winboll.studio.libappbase.ToastUtils;
|
||||||
|
import cc.winboll.studio.winboll.WinBoLL;
|
||||||
|
import cc.winboll.studio.winboll.models.WinBoLLModel;
|
||||||
|
import cc.winboll.studio.winboll.models.WinBoLLNewsBean;
|
||||||
|
import cc.winboll.studio.winboll.services.MainService;
|
||||||
|
import cc.winboll.studio.winboll.sos.SOS;
|
||||||
|
import cc.winboll.studio.winboll.sos.SOSObject;
|
||||||
|
import cc.winboll.studio.winboll.widgets.APPNewsWidget;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public class MainReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
|
public static final String TAG = "MainReceiver";
|
||||||
|
|
||||||
|
public static final String ACTION_BOOT_COMPLETED = "android.intent.action.BOOT_COMPLETED";
|
||||||
|
|
||||||
|
WeakReference<MainService> mwrService;
|
||||||
|
|
||||||
|
public MainReceiver(MainService service) {
|
||||||
|
mwrService = new WeakReference<MainService>(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
String szAction = intent.getAction();
|
||||||
|
if (szAction.equals(ACTION_BOOT_COMPLETED)) {
|
||||||
|
ToastUtils.show("ACTION_BOOT_COMPLETED");
|
||||||
|
} else if (szAction.equals(IWinBoLLActivity.ACTION_BIND)) {
|
||||||
|
LogUtils.d(TAG, "ACTION_BIND");
|
||||||
|
LogUtils.d(TAG, String.format("context.getPackageName() %s", context.getPackageName()));
|
||||||
|
LogUtils.d(TAG, String.format("intent.getAction() %s", intent.getAction()));
|
||||||
|
String szWinBoLLModel = intent.getStringExtra(WinBoLL.EXTRA_WINBOLLMODEL);
|
||||||
|
LogUtils.d(TAG, String.format("szAPPModel %s", szWinBoLLModel));
|
||||||
|
if (szWinBoLLModel != null && !szWinBoLLModel.equals("")) {
|
||||||
|
try {
|
||||||
|
WinBoLLModel bean = WinBoLLModel.parseStringToBean(szWinBoLLModel, WinBoLLModel.class);
|
||||||
|
if (bean != null) {
|
||||||
|
String szAppPackageName = bean.getAppPackageName();
|
||||||
|
LogUtils.d(TAG, String.format("szAppPackageName %s", szAppPackageName));
|
||||||
|
String szAppMainServiveName = bean.getAppMainServiveName();
|
||||||
|
LogUtils.d(TAG, String.format("szAppMainServiveName %s", szAppMainServiveName));
|
||||||
|
mwrService.get().bindWinBoLLModelConnection(bean);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (intent.getAction().equals(SOS.ACTION_SOS)) {
|
||||||
|
LogUtils.d(TAG, "ACTION_SOS");
|
||||||
|
// String sos = intent.getStringExtra(SOS.EXTRA_OBJECT);
|
||||||
|
// LogUtils.d(TAG, String.format("SOS %s", sos));
|
||||||
|
// if (sos != null && !sos.equals("")) {
|
||||||
|
// SOSObject bean = SOS.parseSOSObject(sos);
|
||||||
|
// if (bean != null) {
|
||||||
|
// String szObjectPackageName = bean.getObjectPackageName();
|
||||||
|
// LogUtils.d(TAG, String.format("szObjectPackageName %s", szObjectPackageName));
|
||||||
|
// String szObjectServiveName = bean.getObjectServiveName();
|
||||||
|
// LogUtils.d(TAG, String.format("szObjectServiveName %s", szObjectServiveName));
|
||||||
|
//
|
||||||
|
// Intent intentService = new Intent();
|
||||||
|
// intentService.setComponent(new ComponentName(szObjectPackageName, szObjectServiveName));
|
||||||
|
// context.startService(intentService);
|
||||||
|
//
|
||||||
|
// String appName = AppUtils.getAppNameByPackageName(context, szObjectPackageName);
|
||||||
|
// LogUtils.d(TAG, String.format("appName %s", appName));
|
||||||
|
// WinBoLLNewsBean appWinBoLLNewsBean = new WinBoLLNewsBean(appName);
|
||||||
|
// SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
|
||||||
|
// String currentTime = sdf.format(new Date());
|
||||||
|
// StringBuilder sbLine = new StringBuilder();
|
||||||
|
// sbLine.append("[");
|
||||||
|
// sbLine.append(currentTime);
|
||||||
|
// sbLine.append("] Power to ");
|
||||||
|
// sbLine.append(appName);
|
||||||
|
// appWinBoLLNewsBean.setMessage(sbLine.toString());
|
||||||
|
//
|
||||||
|
// APPNewsWidget.addWinBoLLNewsBean(context, appWinBoLLNewsBean);
|
||||||
|
//
|
||||||
|
// Intent intentWidget = new Intent(context, APPNewsWidget.class);
|
||||||
|
// intentWidget.setAction(APPNewsWidget.ACTION_RELOAD_REPORT);
|
||||||
|
// context.sendBroadcast(intentWidget);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
} else {
|
||||||
|
ToastUtils.show(szAction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册 Receiver
|
||||||
|
//
|
||||||
|
public void registerAction(MainService service) {
|
||||||
|
IntentFilter filter=new IntentFilter();
|
||||||
|
filter.addAction(ACTION_BOOT_COMPLETED);
|
||||||
|
filter.addAction(SOS.ACTION_SOS);
|
||||||
|
filter.addAction(WinBoLL.ACTION_BIND);
|
||||||
|
//filter.addAction(Intent.ACTION_BATTERY_CHANGED);
|
||||||
|
service.registerReceiver(this, filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package cc.winboll.studio.winboll.receivers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2025/02/13 21:19:09
|
||||||
|
* @Describe MyBroadcastReceiver
|
||||||
|
*/
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import cc.winboll.studio.libappbase.R;
|
||||||
|
|
||||||
|
public class MyBroadcastReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
|
public static final String TAG = "MyBroadcastReceiver";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
// if (context.getString(R.string.action_sos).equals(intent.getAction())) {
|
||||||
|
// String message = intent.getStringExtra("message");
|
||||||
|
// String sosPackage = intent.getStringExtra("sosPackage");
|
||||||
|
//
|
||||||
|
// // 处理接收到的广播消息
|
||||||
|
// LogUtils.d(TAG, String.format("MyBroadcastReceiver action %s \n%s\n%s", intent.getAction(), sosPackage, message));
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
package cc.winboll.studio.winboll.services;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2025/02/14 03:38:31
|
||||||
|
* @Describe 守护进程服务
|
||||||
|
*/
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import android.os.Binder;
|
||||||
|
import cc.winboll.studio.winboll.models.MainServiceBean;
|
||||||
|
|
||||||
|
public class AssistantService extends Service {
|
||||||
|
|
||||||
|
public static final String TAG = "AssistantService";
|
||||||
|
|
||||||
|
MainServiceBean mMainServiceBean;
|
||||||
|
MyServiceConnection mMyServiceConnection;
|
||||||
|
MainService mMainService;
|
||||||
|
boolean isBound = false;
|
||||||
|
volatile boolean isThreadAlive = false;
|
||||||
|
|
||||||
|
public synchronized void setIsThreadAlive(boolean isThreadAlive) {
|
||||||
|
LogUtils.d(TAG, "setIsThreadAlive(...)");
|
||||||
|
LogUtils.d(TAG, String.format("isThreadAlive %s", isThreadAlive));
|
||||||
|
this.isThreadAlive = isThreadAlive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isThreadAlive() {
|
||||||
|
return isThreadAlive;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
return new MyBinder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
LogUtils.d(TAG, "onCreate");
|
||||||
|
super.onCreate();
|
||||||
|
|
||||||
|
//mMyBinder = new MyBinder();
|
||||||
|
if (mMyServiceConnection == null) {
|
||||||
|
mMyServiceConnection = new MyServiceConnection();
|
||||||
|
}
|
||||||
|
// 设置运行参数
|
||||||
|
setIsThreadAlive(false);
|
||||||
|
assistantService();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
|
LogUtils.d(TAG, "call onStartCommand(...)");
|
||||||
|
assistantService();
|
||||||
|
return START_STICKY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
//LogUtils.d(TAG, "onDestroy");
|
||||||
|
setIsThreadAlive(false);
|
||||||
|
// 解除绑定
|
||||||
|
if (isBound) {
|
||||||
|
unbindService(mMyServiceConnection);
|
||||||
|
isBound = false;
|
||||||
|
}
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 运行服务内容
|
||||||
|
//
|
||||||
|
void assistantService() {
|
||||||
|
LogUtils.d(TAG, "assistantService()");
|
||||||
|
mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
|
||||||
|
LogUtils.d(TAG, String.format("mMainServiceBean.isEnable() %s", mMainServiceBean.isEnable()));
|
||||||
|
if (mMainServiceBean.isEnable()) {
|
||||||
|
LogUtils.d(TAG, String.format("mIsThreadAlive %s", isThreadAlive()));
|
||||||
|
if (isThreadAlive() == false) {
|
||||||
|
// 设置运行状态
|
||||||
|
setIsThreadAlive(true);
|
||||||
|
// 唤醒和绑定主进程
|
||||||
|
wakeupAndBindMain();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 唤醒和绑定主进程
|
||||||
|
//
|
||||||
|
void wakeupAndBindMain() {
|
||||||
|
LogUtils.d(TAG, "wakeupAndBindMain()");
|
||||||
|
// 绑定服务的Intent
|
||||||
|
Intent intent = new Intent(this, MainService.class);
|
||||||
|
startService(new Intent(this, MainService.class));
|
||||||
|
bindService(intent, mMyServiceConnection, Context.BIND_IMPORTANT);
|
||||||
|
|
||||||
|
// startService(new Intent(this, MainService.class));
|
||||||
|
// bindService(new Intent(AssistantService.this, MainService.class), mMyServiceConnection, Context.BIND_IMPORTANT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主进程与守护进程连接时需要用到此类
|
||||||
|
//
|
||||||
|
class MyServiceConnection implements ServiceConnection {
|
||||||
|
@Override
|
||||||
|
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||||
|
LogUtils.d(TAG, "onServiceConnected(...)");
|
||||||
|
MainService.MyBinder binder = (MainService.MyBinder) service;
|
||||||
|
mMainService = binder.getService();
|
||||||
|
isBound = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceDisconnected(ComponentName name) {
|
||||||
|
LogUtils.d(TAG, "onServiceDisconnected(...)");
|
||||||
|
mMainServiceBean = MainServiceBean.loadBean(AssistantService.this, MainServiceBean.class);
|
||||||
|
if (mMainServiceBean.isEnable()) {
|
||||||
|
wakeupAndBindMain();
|
||||||
|
}
|
||||||
|
isBound = false;
|
||||||
|
mMainService = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用于返回服务实例的Binder
|
||||||
|
public class MyBinder extends Binder {
|
||||||
|
AssistantService getService() {
|
||||||
|
LogUtils.d(TAG, "AssistantService MyBinder getService()");
|
||||||
|
return AssistantService.this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,316 @@
|
|||||||
|
package cc.winboll.studio.winboll.services;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2025/02/13 06:56:41
|
||||||
|
* @Describe 拨号主服务
|
||||||
|
* 参考:
|
||||||
|
* 进程保活-双进程守护的正确姿势
|
||||||
|
* https://blog.csdn.net/sinat_35159441/article/details/75267380
|
||||||
|
* Android Service之onStartCommand方法研究
|
||||||
|
* https://blog.csdn.net/cyp331203/article/details/38920491
|
||||||
|
*/
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
|
import android.os.Binder;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import cc.winboll.studio.winboll.MyTileService;
|
||||||
|
import cc.winboll.studio.winboll.handlers.MainServiceHandler;
|
||||||
|
import cc.winboll.studio.winboll.models.MainServiceBean;
|
||||||
|
import cc.winboll.studio.winboll.models.WinBoLLModel;
|
||||||
|
import cc.winboll.studio.winboll.receivers.MainReceiver;
|
||||||
|
import cc.winboll.studio.winboll.services.AssistantService;
|
||||||
|
import cc.winboll.studio.winboll.threads.MainServiceThread;
|
||||||
|
import cc.winboll.studio.winboll.widgets.APPNewsWidget;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class MainService extends Service {
|
||||||
|
|
||||||
|
public static final String TAG = "MainService";
|
||||||
|
|
||||||
|
public static final int MSG_UPDATE_STATUS = 0;
|
||||||
|
|
||||||
|
static MainService _mControlCenterService;
|
||||||
|
|
||||||
|
volatile boolean isServiceRunning;
|
||||||
|
|
||||||
|
MainServiceBean mMainServiceBean;
|
||||||
|
MainServiceThread mMainServiceThread;
|
||||||
|
MainServiceHandler mMainServiceHandler;
|
||||||
|
MyServiceConnection mMyServiceConnection;
|
||||||
|
AssistantService mAssistantService;
|
||||||
|
boolean isBound = false;
|
||||||
|
MainReceiver mMainReceiver;
|
||||||
|
ArrayList<APPConnection> mAPPModelConnectionList;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
return new MyBinder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MainServiceThread getRemindThread() {
|
||||||
|
return mMainServiceThread;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
LogUtils.d(TAG, "onCreate()");
|
||||||
|
mAPPModelConnectionList = new ArrayList<APPConnection>();
|
||||||
|
|
||||||
|
_mControlCenterService = MainService.this;
|
||||||
|
isServiceRunning = false;
|
||||||
|
mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
|
||||||
|
|
||||||
|
if (mMyServiceConnection == null) {
|
||||||
|
mMyServiceConnection = new MyServiceConnection();
|
||||||
|
}
|
||||||
|
mMainServiceHandler = new MainServiceHandler(this);
|
||||||
|
|
||||||
|
// 运行服务内容
|
||||||
|
mainService();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
|
LogUtils.d(TAG, "onStartCommand(...)");
|
||||||
|
// 运行服务内容
|
||||||
|
mainService();
|
||||||
|
return (mMainServiceBean.isEnable()) ? START_STICKY : super.onStartCommand(intent, flags, startId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 运行服务内容
|
||||||
|
//
|
||||||
|
void mainService() {
|
||||||
|
LogUtils.d(TAG, "mainService()");
|
||||||
|
mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
|
||||||
|
if (mMainServiceBean.isEnable() && isServiceRunning == false) {
|
||||||
|
LogUtils.d(TAG, "mainService() start running");
|
||||||
|
isServiceRunning = true;
|
||||||
|
// 唤醒守护进程
|
||||||
|
wakeupAndBindAssistant();
|
||||||
|
|
||||||
|
if (mMainReceiver == null) {
|
||||||
|
// 注册广播接收器
|
||||||
|
mMainReceiver = new MainReceiver(this);
|
||||||
|
mMainReceiver.registerAction(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 启动小部件
|
||||||
|
Intent intentTimeWidget = new Intent(this, APPNewsWidget.class);
|
||||||
|
intentTimeWidget.setAction(APPNewsWidget.ACTION_RELOAD_REPORT);
|
||||||
|
this.sendBroadcast(intentTimeWidget);
|
||||||
|
|
||||||
|
startMainServiceThread();
|
||||||
|
|
||||||
|
MyTileService.updateServiceIconStatus(this);
|
||||||
|
|
||||||
|
LogUtils.i(TAG, "Main Service Is Start.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 唤醒和绑定守护进程
|
||||||
|
//
|
||||||
|
void wakeupAndBindAssistant() {
|
||||||
|
LogUtils.d(TAG, "wakeupAndBindAssistant()");
|
||||||
|
|
||||||
|
Intent intent = new Intent(this, AssistantService.class);
|
||||||
|
startService(intent);
|
||||||
|
// 绑定服务的Intent
|
||||||
|
bindService(intent, mMyServiceConnection, Context.BIND_IMPORTANT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开启提醒铃声线程
|
||||||
|
//
|
||||||
|
public void startMainServiceThread() {
|
||||||
|
LogUtils.d(TAG, "startMainServiceThread");
|
||||||
|
if (mMainServiceThread == null) {
|
||||||
|
mMainServiceThread = new MainServiceThread(this, mMainServiceHandler);
|
||||||
|
LogUtils.d(TAG, "new MainServiceThread");
|
||||||
|
} else {
|
||||||
|
if (mMainServiceThread.isExist() == true) {
|
||||||
|
mMainServiceThread = new MainServiceThread(this, mMainServiceHandler);
|
||||||
|
LogUtils.d(TAG, "renew MainServiceThread");
|
||||||
|
} else {
|
||||||
|
// 提醒进程正在进行中就更新状态后退出
|
||||||
|
LogUtils.d(TAG, "A mMainServiceThread running.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mMainServiceThread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopRemindThread() {
|
||||||
|
if (mMainServiceThread != null) {
|
||||||
|
mMainServiceThread.setIsExist(true);
|
||||||
|
mMainServiceThread = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
//LogUtils.d(TAG, "onDestroy");
|
||||||
|
mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
|
||||||
|
if (mMainServiceBean.isEnable() == false) {
|
||||||
|
// 设置运行状态
|
||||||
|
isServiceRunning = false;// 解除绑定
|
||||||
|
if (isBound) {
|
||||||
|
unbindService(mMyServiceConnection);
|
||||||
|
isBound = false;
|
||||||
|
}
|
||||||
|
// 停止守护进程
|
||||||
|
Intent intent = new Intent(this, AssistantService.class);
|
||||||
|
stopService(intent);
|
||||||
|
// 停止Receiver
|
||||||
|
if (mMainReceiver != null) {
|
||||||
|
unregisterReceiver(mMainReceiver);
|
||||||
|
mMainReceiver = null;
|
||||||
|
}
|
||||||
|
// 停止前台通知栏
|
||||||
|
stopForeground(true);
|
||||||
|
// 停止消息提醒进程
|
||||||
|
stopRemindThread();
|
||||||
|
|
||||||
|
MyTileService.updateServiceIconStatus(this);
|
||||||
|
|
||||||
|
super.onDestroy();
|
||||||
|
//LogUtils.d(TAG, "onDestroy done");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bindWinBoLLModelConnection(WinBoLLModel bean) {
|
||||||
|
LogUtils.d(TAG, "bindAPPModelConnection(...)");
|
||||||
|
// 清理旧的绑定链接
|
||||||
|
for (int i = mAPPModelConnectionList.size() - 1; i > -1; i--) {
|
||||||
|
APPConnection item = mAPPModelConnectionList.get(i);
|
||||||
|
if (item.isBindToAPP(bean)) {
|
||||||
|
LogUtils.d(TAG, "Bind Servive exist.");
|
||||||
|
unbindService(item);
|
||||||
|
mAPPModelConnectionList.remove(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绑定服务
|
||||||
|
APPConnection appConnection = new APPConnection();
|
||||||
|
Intent intentService = new Intent();
|
||||||
|
intentService.setComponent(new ComponentName(bean.getAppPackageName(), bean.getAppMainServiveName()));
|
||||||
|
bindService(intentService, appConnection, Context.BIND_IMPORTANT);
|
||||||
|
mAPPModelConnectionList.add(appConnection);
|
||||||
|
|
||||||
|
Intent intentWidget = new Intent(this, APPNewsWidget.class);
|
||||||
|
intentWidget.setAction(APPNewsWidget.ACTION_WAKEUP_SERVICE);
|
||||||
|
WinBoLLModel appSOSBean = new WinBoLLModel(bean.getAppPackageName(), bean.getAppMainServiveName());
|
||||||
|
intentWidget.putExtra("APPSOSBean", appSOSBean.toString());
|
||||||
|
sendBroadcast(intentWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class APPConnection implements ServiceConnection {
|
||||||
|
|
||||||
|
ComponentName mComponentName;
|
||||||
|
|
||||||
|
boolean isBindToAPP(WinBoLLModel bean) {
|
||||||
|
return mComponentName != null
|
||||||
|
&& mComponentName.getClassName().equals(bean.getAppMainServiveName())
|
||||||
|
&& mComponentName.getPackageName().equals(bean.getAppPackageName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||||
|
LogUtils.d(TAG, "onServiceConnected(...)");
|
||||||
|
mComponentName = name;
|
||||||
|
LogUtils.d(TAG, String.format("onServiceConnected : \ngetClassName %s\ngetPackageName %s", name.getClassName(), name.getPackageName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceDisconnected(ComponentName name) {
|
||||||
|
LogUtils.d(TAG, "onServiceDisconnected(...)");
|
||||||
|
LogUtils.d(TAG, String.format("onServiceDisconnected : \ngetClassName %s\ngetPackageName %s", name.getClassName(), name.getPackageName()));
|
||||||
|
|
||||||
|
// 尝试无参数启动一下服务
|
||||||
|
String appPackage = mComponentName.getPackageName();
|
||||||
|
LogUtils.d(TAG, String.format("appPackage %s", appPackage));
|
||||||
|
String appMainServiceClassName = mComponentName.getClassName();
|
||||||
|
LogUtils.d(TAG, String.format("appMainServiceClassName %s", appMainServiceClassName));
|
||||||
|
|
||||||
|
Intent intentService = new Intent();
|
||||||
|
intentService.setComponent(new ComponentName(appPackage, appMainServiceClassName));
|
||||||
|
startService(intentService);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主进程与守护进程连接时需要用到此类
|
||||||
|
//
|
||||||
|
private class MyServiceConnection implements ServiceConnection {
|
||||||
|
@Override
|
||||||
|
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||||
|
LogUtils.d(TAG, "onServiceConnected(...)");
|
||||||
|
AssistantService.MyBinder binder = (AssistantService.MyBinder) service;
|
||||||
|
mAssistantService = binder.getService();
|
||||||
|
isBound = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceDisconnected(ComponentName name) {
|
||||||
|
LogUtils.d(TAG, "onServiceDisconnected(...)");
|
||||||
|
|
||||||
|
if (mMainServiceBean.isEnable()) {
|
||||||
|
// 唤醒守护进程
|
||||||
|
wakeupAndBindAssistant();
|
||||||
|
}
|
||||||
|
isBound = false;
|
||||||
|
mAssistantService = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 用于返回服务实例的Binder
|
||||||
|
public class MyBinder extends Binder {
|
||||||
|
MainService getService() {
|
||||||
|
LogUtils.d(TAG, "MainService MyBinder getService()");
|
||||||
|
return MainService.this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// //
|
||||||
|
// // 启动服务
|
||||||
|
// //
|
||||||
|
// public static void startControlCenterService(Context context) {
|
||||||
|
// Intent intent = new Intent(context, MainService.class);
|
||||||
|
// context.startForegroundService(intent);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// //
|
||||||
|
// // 停止服务
|
||||||
|
// //
|
||||||
|
// public static void stopControlCenterService(Context context) {
|
||||||
|
// Intent intent = new Intent(context, MainService.class);
|
||||||
|
// context.stopService(intent);
|
||||||
|
// }
|
||||||
|
|
||||||
|
public void appenMessage(String message) {
|
||||||
|
LogUtils.d(TAG, String.format("Message : %s", message));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void stopMainService(Context context) {
|
||||||
|
LogUtils.d(TAG, "stopMainService");
|
||||||
|
MainServiceBean bean = new MainServiceBean();
|
||||||
|
bean.setIsEnable(false);
|
||||||
|
MainServiceBean.saveBean(context, bean);
|
||||||
|
context.stopService(new Intent(context, MainService.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void startMainService(Context context) {
|
||||||
|
LogUtils.d(TAG, "startMainService");
|
||||||
|
MainServiceBean bean = new MainServiceBean();
|
||||||
|
bean.setIsEnable(true);
|
||||||
|
MainServiceBean.saveBean(context, bean);
|
||||||
|
context.startService(new Intent(context, MainService.class));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
package cc.winboll.studio.winboll.services;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2025/03/07 12:45:49
|
||||||
|
* @Describe 启动时申请绑定到APPBase主服务的服务示例
|
||||||
|
*/
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Binder;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import cc.winboll.studio.winboll.App;
|
||||||
|
import cc.winboll.studio.winboll.WinBoLL;
|
||||||
|
import cc.winboll.studio.winboll.models.TestDemoBindServiceBean;
|
||||||
|
import cc.winboll.studio.winboll.services.TestDemoBindService;
|
||||||
|
import cc.winboll.studio.winboll.sos.SOS;
|
||||||
|
|
||||||
|
public class TestDemoBindService extends Service {
|
||||||
|
|
||||||
|
public static final String TAG = "TestDemoBindService";
|
||||||
|
|
||||||
|
public static final String ACTION_ENABLE = TestDemoBindService.class.getName() + ".ACTION_ENABLE";
|
||||||
|
public static final String ACTION_DISABLE = TestDemoBindService.class.getName() + ".ACTION_DISABLE";
|
||||||
|
|
||||||
|
volatile static TestThread _TestThread;
|
||||||
|
|
||||||
|
volatile static boolean _IsRunning;
|
||||||
|
|
||||||
|
public synchronized static void setIsRunning(boolean isRunning) {
|
||||||
|
_IsRunning = isRunning;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isRunning() {
|
||||||
|
return _IsRunning;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
return new MyBinder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MyBinder extends Binder {
|
||||||
|
public TestDemoBindService getService() {
|
||||||
|
return TestDemoBindService.this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
LogUtils.d(TAG, "onCreate()");
|
||||||
|
|
||||||
|
run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
|
LogUtils.d(TAG, "onStartCommand(...)");
|
||||||
|
TestDemoBindServiceBean bean = TestDemoBindServiceBean.loadBean(this, TestDemoBindServiceBean.class);
|
||||||
|
if (bean == null) {
|
||||||
|
bean = new TestDemoBindServiceBean();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (intent.getAction() != null) {
|
||||||
|
if (intent.getAction().equals(ACTION_ENABLE)) {
|
||||||
|
bean.setIsEnable(true);
|
||||||
|
LogUtils.d(TAG, "setIsEnable(true);");
|
||||||
|
TestDemoBindServiceBean.saveBean(this, bean);
|
||||||
|
} else if (intent.getAction().equals(ACTION_DISABLE)) {
|
||||||
|
bean.setIsEnable(false);
|
||||||
|
LogUtils.d(TAG, "setIsEnable(false);");
|
||||||
|
TestDemoBindServiceBean.saveBean(this, bean);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run();
|
||||||
|
|
||||||
|
return (bean.isEnable()) ? START_STICKY : super.onStartCommand(intent, flags, startId);
|
||||||
|
//return super.onStartCommand(intent, flags, startId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void run() {
|
||||||
|
LogUtils.d(TAG, "run()");
|
||||||
|
TestDemoBindServiceBean bean = TestDemoBindServiceBean.loadBean(this, TestDemoBindServiceBean.class);
|
||||||
|
if (bean == null) {
|
||||||
|
bean = new TestDemoBindServiceBean();
|
||||||
|
TestDemoBindServiceBean.saveBean(this, bean);
|
||||||
|
}
|
||||||
|
if (bean.isEnable()) {
|
||||||
|
LogUtils.d(TAG, "run() bean.isEnable()");
|
||||||
|
TestThread.getInstance(this).start();
|
||||||
|
LogUtils.d(TAG, "_TestThread.start()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
LogUtils.d(TAG, "onDestroy()");
|
||||||
|
TestDemoBindServiceBean bean = TestDemoBindServiceBean.loadBean(this, TestDemoBindServiceBean.class);
|
||||||
|
if (bean == null) {
|
||||||
|
bean = new TestDemoBindServiceBean();
|
||||||
|
}
|
||||||
|
|
||||||
|
TestThread.getInstance(this).setIsExit(true);
|
||||||
|
|
||||||
|
// 预防 APPBase 应用重启绑定失效。
|
||||||
|
// 所以退出时检查本服务是否配置启用,如果启用就发送一个 SOS 信号。
|
||||||
|
// 这样 APPBase 就会用组件方式启动本服务。
|
||||||
|
if (bean.isEnable()) {
|
||||||
|
if (App.isDebugging()) {
|
||||||
|
SOS.sosToAppBaseBeta(this, TestDemoBindService.class.getName());
|
||||||
|
} else {
|
||||||
|
SOS.sosToAppBase(this, TestDemoBindService.class.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_IsRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static class TestThread extends Thread {
|
||||||
|
|
||||||
|
volatile static TestThread _TestThread;
|
||||||
|
Context mContext;
|
||||||
|
volatile boolean isStarted = false;
|
||||||
|
volatile boolean isExit = false;
|
||||||
|
|
||||||
|
TestThread(Context context) {
|
||||||
|
super();
|
||||||
|
mContext = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static synchronized TestThread getInstance(Context context) {
|
||||||
|
if (_TestThread != null) {
|
||||||
|
_TestThread.setIsExit(true);
|
||||||
|
}
|
||||||
|
_TestThread = new TestThread(context);
|
||||||
|
|
||||||
|
return _TestThread;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void setIsExit(boolean isExit) {
|
||||||
|
this.isExit = isExit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isExit() {
|
||||||
|
return isExit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (isStarted == false) {
|
||||||
|
isStarted = true;
|
||||||
|
super.run();
|
||||||
|
LogUtils.d(TAG, "run() start");
|
||||||
|
if (App.isDebugging()) {
|
||||||
|
WinBoLL.bindToAPPBaseBeta(mContext, TestDemoBindService.class.getName());
|
||||||
|
} else {
|
||||||
|
WinBoLL.bindToAPPBase(mContext, TestDemoBindService.class.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!isExit()) {
|
||||||
|
LogUtils.d(TAG, "run()");
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LogUtils.d(TAG, "run() exit");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
package cc.winboll.studio.winboll.services;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2025/03/07 12:39:24
|
||||||
|
* @Describe 普通服务示例
|
||||||
|
*/
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Binder;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import cc.winboll.studio.winboll.models.TestDemoServiceBean;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import cc.winboll.studio.winboll.models.TestDemoServiceBean;
|
||||||
|
|
||||||
|
public class TestDemoService extends Service {
|
||||||
|
|
||||||
|
public static final String TAG = "TestDemoService";
|
||||||
|
|
||||||
|
public static final String ACTION_ENABLE = TestDemoService.class.getName() + ".ACTION_ENABLE";
|
||||||
|
public static final String ACTION_DISABLE = TestDemoService.class.getName() + ".ACTION_DISABLE";
|
||||||
|
|
||||||
|
volatile static TestThread _TestThread;
|
||||||
|
|
||||||
|
volatile static boolean _IsRunning;
|
||||||
|
|
||||||
|
public synchronized static void setIsRunning(boolean isRunning) {
|
||||||
|
_IsRunning = isRunning;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isRunning() {
|
||||||
|
return _IsRunning;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
return new MyBinder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MyBinder extends Binder {
|
||||||
|
public TestDemoService getService() {
|
||||||
|
return TestDemoService.this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
LogUtils.d(TAG, "onCreate()");
|
||||||
|
|
||||||
|
|
||||||
|
run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
|
LogUtils.d(TAG, "onStartCommand(...)");
|
||||||
|
TestDemoServiceBean bean = TestDemoServiceBean.loadBean(this, TestDemoServiceBean.class);
|
||||||
|
if (bean == null) {
|
||||||
|
bean = new TestDemoServiceBean();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (intent.getAction() != null) {
|
||||||
|
if (intent.getAction().equals(ACTION_ENABLE)) {
|
||||||
|
bean.setIsEnable(true);
|
||||||
|
LogUtils.d(TAG, "setIsEnable(true);");
|
||||||
|
TestDemoServiceBean.saveBean(this, bean);
|
||||||
|
} else if (intent.getAction().equals(ACTION_DISABLE)) {
|
||||||
|
bean.setIsEnable(false);
|
||||||
|
LogUtils.d(TAG, "setIsEnable(false);");
|
||||||
|
TestDemoServiceBean.saveBean(this, bean);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run();
|
||||||
|
|
||||||
|
return (bean.isEnable()) ? START_STICKY : super.onStartCommand(intent, flags, startId);
|
||||||
|
//return super.onStartCommand(intent, flags, startId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void run() {
|
||||||
|
LogUtils.d(TAG, "run()");
|
||||||
|
TestDemoServiceBean bean = TestDemoServiceBean.loadBean(this, TestDemoServiceBean.class);
|
||||||
|
if (bean == null) {
|
||||||
|
bean = new TestDemoServiceBean();
|
||||||
|
TestDemoServiceBean.saveBean(this, bean);
|
||||||
|
}
|
||||||
|
if (bean.isEnable()) {
|
||||||
|
LogUtils.d(TAG, "run() bean.isEnable()");
|
||||||
|
TestThread.getInstance(this).start();
|
||||||
|
LogUtils.d(TAG, "_TestThread.start()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
LogUtils.d(TAG, "onDestroy()");
|
||||||
|
TestThread.getInstance(this).setIsExit(true);
|
||||||
|
|
||||||
|
_IsRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static class TestThread extends Thread {
|
||||||
|
|
||||||
|
volatile static TestThread _TestThread;
|
||||||
|
Context mContext;
|
||||||
|
volatile boolean isStarted = false;
|
||||||
|
volatile boolean isExit = false;
|
||||||
|
|
||||||
|
TestThread(Context context) {
|
||||||
|
super();
|
||||||
|
mContext = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static synchronized TestThread getInstance(Context context) {
|
||||||
|
if (_TestThread != null) {
|
||||||
|
_TestThread.setIsExit(true);
|
||||||
|
}
|
||||||
|
_TestThread = new TestThread(context);
|
||||||
|
|
||||||
|
return _TestThread;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void setIsExit(boolean isExit) {
|
||||||
|
this.isExit = isExit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isExit() {
|
||||||
|
return isExit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (isStarted == false) {
|
||||||
|
isStarted = true;
|
||||||
|
super.run();
|
||||||
|
LogUtils.d(TAG, "run() start");
|
||||||
|
|
||||||
|
while (!isExit()) {
|
||||||
|
LogUtils.d(TAG, "run()");
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LogUtils.d(TAG, "run() exit");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
59
winboll/src/main/java/cc/winboll/studio/winboll/sos/SOS.java
Normal file
59
winboll/src/main/java/cc/winboll/studio/winboll/sos/SOS.java
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package cc.winboll.studio.winboll.sos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2025/03/02 09:36:29
|
||||||
|
* @Describe WinBoLL 应用 SOS 机理保护类
|
||||||
|
*/
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class SOS {
|
||||||
|
|
||||||
|
public static final String TAG = "SOS";
|
||||||
|
|
||||||
|
public static final String ACTION_SOS = SOS.class.getName() + ".ACTION_SOS";
|
||||||
|
public static final String EXTRA_OBJECT = "EXTRA_OBJECT";
|
||||||
|
|
||||||
|
public static void sosToAppBase(Context context, String sosService) {
|
||||||
|
LogUtils.d(TAG, "sosToAppBase()");
|
||||||
|
String szToPackage = "cc.winboll.studio.appbase";
|
||||||
|
sos(context, szToPackage, sosService);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sosToAppBaseBeta(Context context, String sosService) {
|
||||||
|
LogUtils.d(TAG, "sosToAppBaseBeta()");
|
||||||
|
String szToPackage = "cc.winboll.studio.appbase.beta";
|
||||||
|
sos(context, szToPackage, sosService);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sos(Context context, String szToPackage, String sosService) {
|
||||||
|
LogUtils.d(TAG, "sos(...)");
|
||||||
|
Intent intent = new Intent(ACTION_SOS);
|
||||||
|
intent.putExtra(EXTRA_OBJECT, genSOSObject(context.getPackageName(), sosService));
|
||||||
|
intent.setPackage(szToPackage);
|
||||||
|
LogUtils.d(TAG, String.format("ACTION_SOS :\nTo Package : %sSOS Service : %s\n", szToPackage, sosService));
|
||||||
|
context.sendBroadcast(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SOSObject parseSOSObject(String szSOSObject) {
|
||||||
|
try {
|
||||||
|
return SOSObject.parseStringToBean(szSOSObject, SOSObject.class);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String sosObjectToString(SOSObject object) {
|
||||||
|
return object.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String genSOSObject(String objectPackageName, String objectServiveName) {
|
||||||
|
return (new SOSObject(objectPackageName, objectServiveName)).toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
package cc.winboll.studio.winboll.sos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2025/02/27 14:00:21
|
||||||
|
* @Describe Simple Operate Signal Service Center.
|
||||||
|
* 简单操作信号服务中心
|
||||||
|
*/
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.IInterface;
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import java.io.FileDescriptor;
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
|
||||||
|
public class SOSCenterService extends Service {
|
||||||
|
|
||||||
|
public static final String TAG = "SOSCenterService";
|
||||||
|
|
||||||
|
private final IBinder binder =(IBinder)new SOSBinder();
|
||||||
|
|
||||||
|
SOSCenterServiceModel mSOSCenterServiceModel;
|
||||||
|
static MainThread _MainThread;
|
||||||
|
public static synchronized MainThread getMainThreadInstance() {
|
||||||
|
if (_MainThread == null) {
|
||||||
|
_MainThread = new MainThread();
|
||||||
|
}
|
||||||
|
return _MainThread;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
return binder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SOSBinder implements IBinder {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dump(FileDescriptor fileDescriptor, String[] string) throws RemoteException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dumpAsync(FileDescriptor fileDescriptor, String[] string) throws RemoteException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getInterfaceDescriptor() throws RemoteException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isBinderAlive() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void linkToDeath(IBinder.DeathRecipient deathRecipient, int p) throws RemoteException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean pingBinder() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IInterface queryLocalInterface(String string) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean transact(int p, Parcel parcel, Parcel parcel1, int p1) throws RemoteException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean unlinkToDeath(IBinder.DeathRecipient deathRecipient, int p) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String TAG = "SOSBinder";
|
||||||
|
SOSCenterService getService() {
|
||||||
|
return SOSCenterService.this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
LogUtils.d(TAG, "onCreate");
|
||||||
|
mSOSCenterServiceModel = SOSCenterServiceModel.loadBean(this, SOSCenterServiceModel.class);
|
||||||
|
if(mSOSCenterServiceModel == null) {
|
||||||
|
mSOSCenterServiceModel = new SOSCenterServiceModel();
|
||||||
|
SOSCenterServiceModel.saveBean(this, mSOSCenterServiceModel);
|
||||||
|
}
|
||||||
|
runMainThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
|
LogUtils.d(TAG, "onStartCommand");
|
||||||
|
|
||||||
|
runMainThread();
|
||||||
|
|
||||||
|
return mSOSCenterServiceModel.isEnable() ? Service.START_STICKY: super.onStartCommand(intent, flags, startId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void runMainThread() {
|
||||||
|
mSOSCenterServiceModel = mSOSCenterServiceModel.loadBean(this, SOSCenterServiceModel.class);
|
||||||
|
if (mSOSCenterServiceModel.isEnable()
|
||||||
|
&& _MainThread == null) {
|
||||||
|
getMainThreadInstance().start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
LogUtils.d(TAG, "onDestroy");
|
||||||
|
mSOSCenterServiceModel = SOSCenterServiceModel.loadBean(this, SOSCenterServiceModel.class);
|
||||||
|
if (mSOSCenterServiceModel.isEnable()) {
|
||||||
|
LogUtils.d(TAG, "mSOSCenterServiceModel.isEnable()");
|
||||||
|
// ISOSAPP iSOSAPP = (ISOSAPP)getApplication();
|
||||||
|
// iSOSAPP.helpISOSService(getISOSServiceIntentWhichAskForHelp());
|
||||||
|
}
|
||||||
|
if (_MainThread != null) {
|
||||||
|
_MainThread.isExist = true;
|
||||||
|
_MainThread = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void stopISOSService(Context context) {
|
||||||
|
LogUtils.d(TAG, "stopISOSService");
|
||||||
|
SOSCenterServiceModel bean = new SOSCenterServiceModel();
|
||||||
|
bean.setIsEnable(false);
|
||||||
|
SOSCenterServiceModel.saveBean(context, bean);
|
||||||
|
context.stopService(new Intent(context, SOSCenterServiceModel.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void startISOSService(Context context) {
|
||||||
|
LogUtils.d(TAG, "startISOSService");
|
||||||
|
SOSCenterServiceModel bean = new SOSCenterServiceModel();
|
||||||
|
bean.setIsEnable(true);
|
||||||
|
SOSCenterServiceModel.saveBean(context, bean);
|
||||||
|
context.startService(new Intent(context, SOSCenterServiceModel.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return "Hello from SOSCenterServiceModel";
|
||||||
|
}
|
||||||
|
|
||||||
|
static class MainThread extends Thread {
|
||||||
|
volatile boolean isExist = false;
|
||||||
|
|
||||||
|
public void setIsExist(boolean isExist) {
|
||||||
|
this.isExist = isExist;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isExist() {
|
||||||
|
return isExist;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
super.run();
|
||||||
|
while (!isExist) {
|
||||||
|
LogUtils.d(TAG, "run");
|
||||||
|
try {
|
||||||
|
sleep(1000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package cc.winboll.studio.winboll.sos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2025/03/02 09:49:45
|
||||||
|
* @Describe SOSCenterServiceModel
|
||||||
|
* Simple Operate Signal Service Model.
|
||||||
|
*/
|
||||||
|
import android.util.JsonReader;
|
||||||
|
import android.util.JsonWriter;
|
||||||
|
import cc.winboll.studio.libappbase.BaseBean;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class SOSCenterServiceModel extends BaseBean {
|
||||||
|
|
||||||
|
public static final String TAG = "SOSCenterServiceModel";
|
||||||
|
|
||||||
|
boolean isEnable;
|
||||||
|
|
||||||
|
public SOSCenterServiceModel() {
|
||||||
|
this.isEnable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsEnable(boolean isEnable) {
|
||||||
|
this.isEnable = isEnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnable() {
|
||||||
|
return isEnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return SOSCenterServiceModel.class.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
|
||||||
|
super.writeThisToJsonWriter(jsonWriter);
|
||||||
|
jsonWriter.name("isEnable").value(isEnable());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
|
||||||
|
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
|
||||||
|
if (name.equals("isEnable")) {
|
||||||
|
setIsEnable(jsonReader.nextBoolean());
|
||||||
|
} 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,29 @@
|
|||||||
|
package cc.winboll.studio.winboll.sos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2025/02/27 14:04:35
|
||||||
|
* @Describe SOSCenterServiceReceiver
|
||||||
|
*/
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
|
||||||
|
public class SOSCenterServiceReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
|
public static final String TAG = "SOSCenterServiceReceiver";
|
||||||
|
|
||||||
|
public static final String ACTION_SOS = SOSCenterServiceReceiver.class.getName() + ".ACTION_SOS";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
String action = intent.getAction();
|
||||||
|
if (action.equals(ACTION_SOS)) {
|
||||||
|
// 处理接收到的广播消息
|
||||||
|
LogUtils.d(TAG, String.format("Action %s \n%s\n%s", action));
|
||||||
|
} else {
|
||||||
|
LogUtils.d(TAG, String.format("%s", action));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package cc.winboll.studio.winboll.sos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2025/02/27 14:12:05
|
||||||
|
* @Describe SOSBean
|
||||||
|
*/
|
||||||
|
import android.util.JsonReader;
|
||||||
|
import android.util.JsonWriter;
|
||||||
|
import cc.winboll.studio.libappbase.BaseBean;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class SOSObject extends BaseBean {
|
||||||
|
|
||||||
|
public static final String TAG = "SOSObject";
|
||||||
|
|
||||||
|
String objectPackageName;
|
||||||
|
String objectServiveName;
|
||||||
|
|
||||||
|
public SOSObject() {
|
||||||
|
this.objectPackageName = "";
|
||||||
|
this.objectServiveName = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public SOSObject(String objectPackageName, String objectServiveName) {
|
||||||
|
this.objectPackageName = objectPackageName;
|
||||||
|
this.objectServiveName = objectServiveName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setObjectPackageName(String objectPackageName) {
|
||||||
|
this.objectPackageName = objectPackageName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getObjectPackageName() {
|
||||||
|
return objectPackageName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setObjectServiveName(String objectServiveName) {
|
||||||
|
this.objectServiveName = objectServiveName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getObjectServiveName() {
|
||||||
|
return objectServiveName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return SOSObject.class.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
|
||||||
|
super.writeThisToJsonWriter(jsonWriter);
|
||||||
|
jsonWriter.name("objectPackageName").value(getObjectPackageName());
|
||||||
|
jsonWriter.name("objectServiveName").value(getObjectServiveName());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
|
||||||
|
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
|
||||||
|
if (name.equals("objectPackageName")) {
|
||||||
|
setObjectPackageName(jsonReader.nextString());
|
||||||
|
} else if (name.equals("objectServiveName")) {
|
||||||
|
setObjectServiveName(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,54 @@
|
|||||||
|
package cc.winboll.studio.winboll.threads;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2025/02/14 03:46:44
|
||||||
|
*/
|
||||||
|
import android.content.Context;
|
||||||
|
import cc.winboll.studio.winboll.handlers.MainServiceHandler;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
|
||||||
|
public class MainServiceThread extends Thread {
|
||||||
|
|
||||||
|
public static final String TAG = "MainServiceThread";
|
||||||
|
|
||||||
|
Context mContext;
|
||||||
|
|
||||||
|
// 控制线程是否退出的标志
|
||||||
|
volatile boolean isExist = false;
|
||||||
|
|
||||||
|
// 服务Handler, 用于线程发送消息使用
|
||||||
|
WeakReference<MainServiceHandler> mwrMainServiceHandler;
|
||||||
|
|
||||||
|
public void setIsExist(boolean isExist) {
|
||||||
|
this.isExist = isExist;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isExist() {
|
||||||
|
return isExist;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MainServiceThread(Context context, MainServiceHandler handler) {
|
||||||
|
mContext = context;
|
||||||
|
mwrMainServiceHandler = new WeakReference<MainServiceHandler>(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
LogUtils.d(TAG, "run()");
|
||||||
|
|
||||||
|
while (!isExist()) {
|
||||||
|
//ToastUtils.show("run()");
|
||||||
|
//LogUtils.d(TAG, "run()");
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LogUtils.d(TAG, "run() exit.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,311 @@
|
|||||||
|
package cc.winboll.studio.winboll.unittest;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.Toast;
|
||||||
|
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import cc.winboll.studio.winboll.R;
|
||||||
|
import cc.winboll.studio.winboll.activities.WinBoLLActivity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2026/01/03 10:52
|
||||||
|
* @Describe 企业微信SDK接口测试(基础调试版)
|
||||||
|
* 包含:SDK初始化、基础接口调用、日志输出、主线程回调处理
|
||||||
|
*/
|
||||||
|
public class TestWeWorkSpecSDK extends WinBoLLActivity implements IWinBoLLActivity, View.OnClickListener {
|
||||||
|
|
||||||
|
public static final String TAG = "TestWeWorkSpecSDK";
|
||||||
|
|
||||||
|
// ------------------- 企业微信SDK配置常量(需替换为实际项目参数) -------------------
|
||||||
|
// 企业微信 CorpID(从企业微信管理后台获取)
|
||||||
|
private static final String CORP_ID = "wwb37c73f34c722852";
|
||||||
|
// 应用 AgentID(从企业微信应用管理后台获取)
|
||||||
|
private static final String AGENT_ID = "your_agent_id_here";
|
||||||
|
// 应用 Secret(从企业微信应用管理后台获取,注意保密)
|
||||||
|
private static final String APP_SECRET = "your_app_secret_here";
|
||||||
|
|
||||||
|
// ------------------- Handler消息标识(主线程处理SDK回调) -------------------
|
||||||
|
private static final int MSG_SDK_INIT_SUCCESS = 1001;
|
||||||
|
private static final int MSG_SDK_INIT_FAILED = 1002;
|
||||||
|
private static final int MSG_GET_CORP_INFO_SUCCESS = 1003;
|
||||||
|
private static final int MSG_GET_CORP_INFO_FAILED = 1004;
|
||||||
|
|
||||||
|
// ------------------- 控件声明 -------------------
|
||||||
|
private Button mBtnInitSDK;
|
||||||
|
private Button mBtnGetCorpInfo;
|
||||||
|
private Button mBtnCheckAuth;
|
||||||
|
|
||||||
|
// ------------------- 主线程Handler(处理SDK异步回调) -------------------
|
||||||
|
private Handler mWeWorkHandler;
|
||||||
|
|
||||||
|
|
||||||
|
@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_test_weworkspecsdk);
|
||||||
|
|
||||||
|
// 初始化控件
|
||||||
|
initViews();
|
||||||
|
// 绑定点击事件
|
||||||
|
initEvents();
|
||||||
|
// 初始化Handler(主线程处理回调,更新UI)
|
||||||
|
initHandler();
|
||||||
|
// 初始化SDK(可选:启动时自动初始化,或点击按钮初始化)
|
||||||
|
// initWeWorkSDK();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化控件(Java 7 显式绑定)
|
||||||
|
*/
|
||||||
|
private void initViews() {
|
||||||
|
mBtnInitSDK = (Button) findViewById(R.id.btn_init_sdk);
|
||||||
|
mBtnGetCorpInfo = (Button) findViewById(R.id.btn_get_corp_info);
|
||||||
|
mBtnCheckAuth = (Button) findViewById(R.id.btn_check_auth);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绑定点击事件(Java 7 匿名内部类)
|
||||||
|
*/
|
||||||
|
private void initEvents() {
|
||||||
|
mBtnInitSDK.setOnClickListener(this);
|
||||||
|
mBtnGetCorpInfo.setOnClickListener(this);
|
||||||
|
mBtnCheckAuth.setOnClickListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化主线程Handler(处理SDK异步回调,安全更新UI)
|
||||||
|
*/
|
||||||
|
private void initHandler() {
|
||||||
|
mWeWorkHandler = new Handler() {
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message msg) {
|
||||||
|
super.handleMessage(msg);
|
||||||
|
switch (msg.what) {
|
||||||
|
case MSG_SDK_INIT_SUCCESS:
|
||||||
|
showToast("企业微信SDK初始化成功");
|
||||||
|
LogUtils.d(TAG, "SDK初始化成功");
|
||||||
|
break;
|
||||||
|
case MSG_SDK_INIT_FAILED:
|
||||||
|
String initError = (String) msg.obj;
|
||||||
|
showToast("SDK初始化失败:" + initError);
|
||||||
|
LogUtils.e(TAG, "SDK初始化失败:" + initError);
|
||||||
|
break;
|
||||||
|
case MSG_GET_CORP_INFO_SUCCESS:
|
||||||
|
String corpInfo = (String) msg.obj;
|
||||||
|
showToast("获取企业信息成功");
|
||||||
|
LogUtils.d(TAG, "企业信息:" + corpInfo);
|
||||||
|
break;
|
||||||
|
case MSG_GET_CORP_INFO_FAILED:
|
||||||
|
String corpError = (String) msg.obj;
|
||||||
|
showToast("获取企业信息失败:" + corpError);
|
||||||
|
LogUtils.e(TAG, "获取企业信息失败:" + corpError);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------- 企业微信SDK核心接口调用 -------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化企业微信SDK(异步操作,通过Handler回调结果)
|
||||||
|
*/
|
||||||
|
private void initWeWorkSDK() {
|
||||||
|
showToast("开始初始化企业微信SDK...");
|
||||||
|
// 模拟SDK异步初始化(实际项目中替换为企业微信SDK的真实初始化接口)
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
// 真实SDK初始化逻辑示例:
|
||||||
|
// WeWorkSDK.init(TestWeWorkSpecSDK.this, CORP_ID, AGENT_ID, new WeWorkSDKCallback() {
|
||||||
|
// @Override
|
||||||
|
// public void onSuccess() {
|
||||||
|
// mWeWorkHandler.sendEmptyMessage(MSG_SDK_INIT_SUCCESS);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Override
|
||||||
|
// public void onFailure(String errorMsg) {
|
||||||
|
// Message msg = Message.obtain();
|
||||||
|
// msg.what = MSG_SDK_INIT_FAILED;
|
||||||
|
// msg.obj = errorMsg;
|
||||||
|
// mWeWorkHandler.sendMessage(msg);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// 调试模拟:休眠1秒,模拟异步初始化
|
||||||
|
Thread.sleep(1000);
|
||||||
|
// 模拟初始化成功(如需测试失败,替换为发送MSG_SDK_INIT_FAILED)
|
||||||
|
mWeWorkHandler.sendEmptyMessage(MSG_SDK_INIT_SUCCESS);
|
||||||
|
// 模拟初始化失败
|
||||||
|
// Message msg = Message.obtain();
|
||||||
|
// msg.what = MSG_SDK_INIT_FAILED;
|
||||||
|
// msg.obj = "CorpID或AgentID错误";
|
||||||
|
// mWeWorkHandler.sendMessage(msg);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
Message msg = Message.obtain();
|
||||||
|
msg.what = MSG_SDK_INIT_FAILED;
|
||||||
|
msg.obj = "线程中断:" + e.getMessage();
|
||||||
|
mWeWorkHandler.sendMessage(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取企业基本信息(异步操作,需先初始化SDK)
|
||||||
|
*/
|
||||||
|
private void getCorpInfo() {
|
||||||
|
if (!isSDKInitialized()) {
|
||||||
|
showToast("请先初始化SDK");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
showToast("开始获取企业信息...");
|
||||||
|
// 模拟SDK异步获取企业信息(实际项目中替换为真实接口)
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
// 真实SDK接口示例:
|
||||||
|
// WeWorkSDK.getCorpInfo(APP_SECRET, new CorpInfoCallback() {
|
||||||
|
// @Override
|
||||||
|
// public void onSuccess(CorpInfo info) {
|
||||||
|
// Message msg = Message.obtain();
|
||||||
|
// msg.what = MSG_GET_CORP_INFO_SUCCESS;
|
||||||
|
// msg.obj = "企业名称:" + info.getCorpName() + ",企业ID:" + info.getCorpId();
|
||||||
|
// mWeWorkHandler.sendMessage(msg);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Override
|
||||||
|
// public void onFailure(String errorMsg) {
|
||||||
|
// Message msg = Message.obtain();
|
||||||
|
// msg.what = MSG_GET_CORP_INFO_FAILED;
|
||||||
|
// msg.obj = errorMsg;
|
||||||
|
// mWeWorkHandler.sendMessage(msg);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// 调试模拟:休眠1秒,模拟异步获取
|
||||||
|
Thread.sleep(1000);
|
||||||
|
// 模拟获取成功
|
||||||
|
Message successMsg = Message.obtain();
|
||||||
|
successMsg.what = MSG_GET_CORP_INFO_SUCCESS;
|
||||||
|
successMsg.obj = "企业名称:WinBoLL Studio,企业ID:" + CORP_ID;
|
||||||
|
mWeWorkHandler.sendMessage(successMsg);
|
||||||
|
// 模拟获取失败
|
||||||
|
// Message failMsg = Message.obtain();
|
||||||
|
// failMsg.what = MSG_GET_CORP_INFO_FAILED;
|
||||||
|
// failMsg.obj = "AppSecret错误或权限不足";
|
||||||
|
// mWeWorkHandler.sendMessage(failMsg);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
Message msg = Message.obtain();
|
||||||
|
msg.what = MSG_GET_CORP_INFO_FAILED;
|
||||||
|
msg.obj = "线程中断:" + e.getMessage();
|
||||||
|
mWeWorkHandler.sendMessage(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查当前用户是否已授权(同步操作,示例)
|
||||||
|
*/
|
||||||
|
private void checkAuthStatus() {
|
||||||
|
if (!isSDKInitialized()) {
|
||||||
|
showToast("请先初始化SDK");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 真实SDK接口示例:
|
||||||
|
// boolean isAuthorized = WeWorkSDK.isAuthorized();
|
||||||
|
// 调试模拟:默认返回true
|
||||||
|
boolean isAuthorized = true;
|
||||||
|
|
||||||
|
if (isAuthorized) {
|
||||||
|
showToast("用户已授权");
|
||||||
|
LogUtils.d(TAG, "当前用户已授权企业微信应用");
|
||||||
|
} else {
|
||||||
|
showToast("用户未授权,请先授权");
|
||||||
|
LogUtils.d(TAG, "当前用户未授权企业微信应用");
|
||||||
|
// 真实项目中可调用授权接口:
|
||||||
|
// WeWorkSDK.requestAuth(TestWeWorkSpecSDK.this, new AuthCallback() {
|
||||||
|
// @Override
|
||||||
|
// public void onSuccess(String code) {
|
||||||
|
// showToast("授权成功,code:" + code);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Override
|
||||||
|
// public void onFailure(String errorMsg) {
|
||||||
|
// showToast("授权失败:" + errorMsg);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------- 工具方法 -------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查SDK是否已初始化(模拟方法,实际项目中替换为SDK的真实状态检查)
|
||||||
|
*/
|
||||||
|
private boolean isSDKInitialized() {
|
||||||
|
// 真实SDK可通过静态方法检查状态:
|
||||||
|
// return WeWorkSDK.isInitialized();
|
||||||
|
// 调试模拟:假设Handler不为空即表示已初始化
|
||||||
|
return mWeWorkHandler != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示Toast提示(Java 7 简化封装)
|
||||||
|
*/
|
||||||
|
private void showToast(String msg) {
|
||||||
|
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------- 点击事件处理 -------------------
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
int id = v.getId();
|
||||||
|
if (id == R.id.btn_init_sdk) {
|
||||||
|
initWeWorkSDK();
|
||||||
|
} else if (id == R.id.btn_get_corp_info) {
|
||||||
|
getCorpInfo();
|
||||||
|
} else if (id == R.id.btn_check_auth) {
|
||||||
|
checkAuthStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------- 生命周期管理 -------------------
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
// 释放Handler资源,避免内存泄漏
|
||||||
|
if (mWeWorkHandler != null) {
|
||||||
|
mWeWorkHandler.removeCallbacksAndMessages(null);
|
||||||
|
mWeWorkHandler = null;
|
||||||
|
}
|
||||||
|
// 真实SDK需调用销毁方法:
|
||||||
|
// WeWorkSDK.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
package cc.winboll.studio.winboll.utils;
|
||||||
|
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.widget.Toast;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import cc.winboll.studio.winboll.App;
|
||||||
|
import cc.winboll.studio.winboll.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||||
|
* @Date 2025/11/27 08:56
|
||||||
|
* @Describe APPPlusUtils
|
||||||
|
*/
|
||||||
|
public class APPPlusUtils {
|
||||||
|
public static final String TAG = "APPPlusUtils";
|
||||||
|
|
||||||
|
// 快捷方式配置(名称+图标,需与实际资源匹配)
|
||||||
|
// private static final String PLUS_SHORTCUT_NAME = "位置服务-Laojun";
|
||||||
|
// private static final int PLUS_SHORTCUT_ICON = R.mipmap.ic_launcher; // Laojun 图标资源
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加Plus组件与图标
|
||||||
|
*/
|
||||||
|
public static boolean switchAppLauncherToComponent(Context context, String componentName) {
|
||||||
|
if (context == null) {
|
||||||
|
LogUtils.d(TAG, "切换失败:上下文为空");
|
||||||
|
Toast.makeText(context, context.getString(R.string.app_name) + "图标切换失败", Toast.LENGTH_SHORT).show();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PackageManager pm = context.getPackageManager();
|
||||||
|
|
||||||
|
ComponentName plusComponentSwitchTo = new ComponentName(context, componentName);
|
||||||
|
ComponentName plusComponentEN1 = new ComponentName(context, App.COMPONENT_EN1);
|
||||||
|
ComponentName plusComponentCN1 = new ComponentName(context, App.COMPONENT_CN1);
|
||||||
|
ComponentName plusComponentCN2 = new ComponentName(context, App.COMPONENT_CN2);
|
||||||
|
|
||||||
|
try {
|
||||||
|
disableComponent(pm, plusComponentEN1);
|
||||||
|
disableComponent(pm, plusComponentCN1);
|
||||||
|
disableComponent(pm, plusComponentCN2);
|
||||||
|
enableComponent(pm, plusComponentSwitchTo);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtils.e(TAG, "图标切换失败:" + e.getMessage());
|
||||||
|
Toast.makeText(context, context.getString(R.string.app_name) + "图标切换失败" + e.getMessage(), Toast.LENGTH_SHORT).show();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建指定组件的桌面快捷方式(自动去重,兼容 Android 8.0+)
|
||||||
|
* @param component 目标组件(如 LAOJUN_ACTIVITY)
|
||||||
|
* @param name 快捷方式名称
|
||||||
|
* @param iconRes 快捷方式图标资源ID
|
||||||
|
* @return 是否创建成功
|
||||||
|
*/
|
||||||
|
private static boolean createComponentShortcut(Context context, ComponentName component, String name, int iconRes) {
|
||||||
|
if (context == null || component == null || name == null || iconRes == 0) {
|
||||||
|
LogUtils.d(TAG, "快捷方式创建失败:参数为空");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Android 8.0+(API 26+):使用 ShortcutManager(系统推荐)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
try {
|
||||||
|
PackageManager pm = context.getPackageManager();
|
||||||
|
android.content.pm.ShortcutManager shortcutManager = context.getSystemService(android.content.pm.ShortcutManager.class);
|
||||||
|
if (shortcutManager == null || !shortcutManager.isRequestPinShortcutSupported()) {
|
||||||
|
LogUtils.d(TAG, "系统不支持创建快捷方式");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否已存在该组件的快捷方式(去重)
|
||||||
|
for (android.content.pm.ShortcutInfo info : shortcutManager.getPinnedShortcuts()) {
|
||||||
|
if (component.getClassName().equals(info.getIntent().getComponent().getClassName())) {
|
||||||
|
LogUtils.d(TAG, "快捷方式已存在:" + component.getClassName());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建启动目标组件的意图
|
||||||
|
Intent launchIntent = new Intent(Intent.ACTION_MAIN)
|
||||||
|
.setComponent(component)
|
||||||
|
.addCategory(Intent.CATEGORY_LAUNCHER)
|
||||||
|
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
|
|
||||||
|
// 构建快捷方式信息
|
||||||
|
android.content.pm.ShortcutInfo shortcutInfo = new android.content.pm.ShortcutInfo.Builder(context, component.getClassName())
|
||||||
|
.setShortLabel(name)
|
||||||
|
.setLongLabel(name)
|
||||||
|
.setIcon(android.graphics.drawable.Icon.createWithResource(context, iconRes))
|
||||||
|
.setIntent(launchIntent)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 请求创建快捷方式(需用户确认)
|
||||||
|
shortcutManager.requestPinShortcut(shortcutInfo, null);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtils.d(TAG, "Android O+ 快捷方式创建失败:" + e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Android 8.0 以下:使用广播(兼容旧机型)
|
||||||
|
try {
|
||||||
|
// 构建启动目标组件的意图
|
||||||
|
Intent launchIntent = new Intent(Intent.ACTION_MAIN)
|
||||||
|
.setComponent(component)
|
||||||
|
.addCategory(Intent.CATEGORY_LAUNCHER)
|
||||||
|
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
|
|
||||||
|
// 构建创建快捷方式的广播意图
|
||||||
|
Intent installIntent = new Intent("com.android.launcher.action.INSTALL_SHORTCUT");
|
||||||
|
installIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, launchIntent);
|
||||||
|
installIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name);
|
||||||
|
installIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
|
||||||
|
Intent.ShortcutIconResource.fromContext(context, iconRes));
|
||||||
|
installIntent.putExtra("duplicate", false); // 禁止重复创建
|
||||||
|
|
||||||
|
context.sendBroadcast(installIntent);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtils.d(TAG, "Android O- 快捷方式创建失败:" + e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启用组件(带状态检查,避免重复操作)
|
||||||
|
*/
|
||||||
|
private static void enableComponent(PackageManager pm, ComponentName component) {
|
||||||
|
if (pm.getComponentEnabledSetting(component) != PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
|
||||||
|
pm.setComponentEnabledSetting(
|
||||||
|
component,
|
||||||
|
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||||
|
PackageManager.DONT_KILL_APP | PackageManager.SYNCHRONOUS
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 禁用组件(带状态检查,避免重复操作)
|
||||||
|
*/
|
||||||
|
private static void disableComponent(PackageManager pm, ComponentName component) {
|
||||||
|
if (pm.getComponentEnabledSetting(component) != PackageManager.COMPONENT_ENABLED_STATE_DISABLED) {
|
||||||
|
pm.setComponentEnabledSetting(
|
||||||
|
component,
|
||||||
|
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
||||||
|
PackageManager.DONT_KILL_APP | PackageManager.SYNCHRONOUS
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package cc.winboll.studio.winboll.utils;
|
||||||
|
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import okhttp3.Call;
|
||||||
|
import okhttp3.Callback;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OkHttp网络请求工具类
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2026/01/07
|
||||||
|
*/
|
||||||
|
public class OkHttpUtil {
|
||||||
|
|
||||||
|
private static OkHttpClient sOkHttpClient;
|
||||||
|
private static Handler sMainHandler = new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
|
static {
|
||||||
|
sOkHttpClient = new OkHttpClient.Builder()
|
||||||
|
.connectTimeout(10, TimeUnit.SECONDS)
|
||||||
|
.readTimeout(10, TimeUnit.SECONDS)
|
||||||
|
.writeTimeout(10, TimeUnit.SECONDS)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET请求
|
||||||
|
* @param url 请求地址
|
||||||
|
* @param callback 回调
|
||||||
|
*/
|
||||||
|
public static void get(String url, final OnResultCallback callback) {
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.get()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
sOkHttpClient.newCall(request).enqueue(new Callback() {
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call call, final IOException e) {
|
||||||
|
sMainHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (callback != null) {
|
||||||
|
callback.onFailure(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call call, final Response response) throws IOException {
|
||||||
|
final String result = response.body().string();
|
||||||
|
sMainHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (callback != null) {
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
callback.onSuccess(result);
|
||||||
|
} else {
|
||||||
|
callback.onFailure("请求失败:" + response.code());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回调接口
|
||||||
|
*/
|
||||||
|
public interface OnResultCallback {
|
||||||
|
void onSuccess(String result);
|
||||||
|
void onFailure(String errorMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
package cc.winboll.studio.winboll.utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.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,35 @@
|
|||||||
|
package cc.winboll.studio.winboll.utils;
|
||||||
|
|
||||||
|
import android.app.ActivityManager;
|
||||||
|
import android.content.Context;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||||
|
* @Date 2025/11/27 10:25
|
||||||
|
* @Describe ServiceUtils
|
||||||
|
*/
|
||||||
|
public class ServiceUtils {
|
||||||
|
|
||||||
|
public static final String TAG = ServiceUtils.class.getSimpleName();
|
||||||
|
|
||||||
|
public static boolean isServiceAlive(Context context, String szServiceName) {
|
||||||
|
// 获取Activity管理者对象
|
||||||
|
ActivityManager manager = (ActivityManager) context
|
||||||
|
.getSystemService(Context.ACTIVITY_SERVICE);
|
||||||
|
// 获取正在运行的服务(此处设置最多取1000个)
|
||||||
|
List<ActivityManager.RunningServiceInfo> runningServices = manager
|
||||||
|
.getRunningServices(1000);
|
||||||
|
if (runningServices.size() <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 遍历,若存在名字和传入的serviceName的一致则说明存在
|
||||||
|
for (ActivityManager.RunningServiceInfo runningServiceInfo : runningServices) {
|
||||||
|
if (runningServiceInfo.service.getClassName().equals(szServiceName)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package cc.winboll.studio.winboll.utils;
|
||||||
|
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志工具类(适配项目规范)
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2026/01/07
|
||||||
|
*/
|
||||||
|
public class SpecUtil {
|
||||||
|
|
||||||
|
private static final boolean isDebug = true;
|
||||||
|
|
||||||
|
public static void WWSpecLogInfo(String tag, String msg) {
|
||||||
|
if (isDebug) {
|
||||||
|
LogUtils.i(tag, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void WWSpecLogError(String tag, String msg) {
|
||||||
|
if (isDebug) {
|
||||||
|
LogUtils.e(tag, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package cc.winboll.studio.winboll.utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@188.com>
|
||||||
|
* @Date 2025/06/08 09:05
|
||||||
|
* @Describe Termux 应用操作工具集
|
||||||
|
*/
|
||||||
|
import android.content.Intent;
|
||||||
|
|
||||||
|
public abstract class TermuxUtils {
|
||||||
|
|
||||||
|
public static final String TAG = "TermuxUtils";
|
||||||
|
|
||||||
|
private void runTermuxCommand(String command) {
|
||||||
|
// 1. 创建 Intent,指定 Termux 的 RunCommandService
|
||||||
|
/*Intent intent = new Intent("com.termux.RUN_COMMAND");
|
||||||
|
intent.setPackage("com.termux"); // Termux 应用的包名
|
||||||
|
|
||||||
|
// 2. 传递命令参数(必填)
|
||||||
|
//intent.putExtra("command", command);
|
||||||
|
intent.putExtra("cd ~/WinBoLL&&echo 'WinBoLL cmd exec at (data", command);
|
||||||
|
|
||||||
|
// 3. 可选:设置工作目录(默认为 Termux 的 home 目录)
|
||||||
|
intent.putExtra("dir", "/data/data/com.termux/files/home/WinBoLL");
|
||||||
|
|
||||||
|
// 4. 发送 Intent(需处理可能的安全异常或 ActivityNotFoundException)
|
||||||
|
try {
|
||||||
|
getApplicationContext().startService(intent);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package cc.winboll.studio.winboll.utils;
|
||||||
|
|
||||||
|
import cc.winboll.studio.winboll.WxPayConfig;
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信支付服务端接口封装
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2026/01/07
|
||||||
|
*/
|
||||||
|
public class WxPayApi {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一下单(生成二维码)
|
||||||
|
* @param callback 回调
|
||||||
|
*/
|
||||||
|
public static void createOrder(final OnCreateOrderCallback callback) {
|
||||||
|
// 拼接请求参数(服务端测试接口需支持GET传参,若为POST需修改为表单/JSON)
|
||||||
|
String url = WxPayConfig.CREATE_ORDER_URL +
|
||||||
|
"?body=" + WxPayConfig.ORDER_BODY +
|
||||||
|
"&totalFee=" + WxPayConfig.TOTAL_FEE +
|
||||||
|
"&tradeType=" + WxPayConfig.TRADE_TYPE;
|
||||||
|
|
||||||
|
OkHttpUtil.get(url, new OkHttpUtil.OnResultCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(String result) {
|
||||||
|
try {
|
||||||
|
JSONObject jsonObject = JSONObject.parseObject(result);
|
||||||
|
String outTradeNo = jsonObject.getString("out_trade_no");
|
||||||
|
String codeUrl = jsonObject.getString("code_url");
|
||||||
|
if (callback != null) {
|
||||||
|
callback.onSuccess(outTradeNo, codeUrl);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (callback != null) {
|
||||||
|
callback.onFailure("解析统一下单结果失败:" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(String errorMsg) {
|
||||||
|
if (callback != null) {
|
||||||
|
callback.onFailure("统一下单请求失败:" + errorMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单查询
|
||||||
|
* @param outTradeNo 商户订单号
|
||||||
|
* @param callback 回调
|
||||||
|
*/
|
||||||
|
public static void queryOrder(String outTradeNo, final OnQueryOrderCallback callback) {
|
||||||
|
String url = WxPayConfig.QUERY_ORDER_URL + "?outTradeNo=" + outTradeNo;
|
||||||
|
|
||||||
|
OkHttpUtil.get(url, new OkHttpUtil.OnResultCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(String result) {
|
||||||
|
try {
|
||||||
|
JSONObject jsonObject = JSONObject.parseObject(result);
|
||||||
|
String tradeState = jsonObject.getString("trade_state");
|
||||||
|
boolean isSuccess = "SUCCESS".equals(tradeState);
|
||||||
|
if (callback != null) {
|
||||||
|
callback.onSuccess(isSuccess, tradeState);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (callback != null) {
|
||||||
|
callback.onFailure("解析订单查询结果失败:" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(String errorMsg) {
|
||||||
|
if (callback != null) {
|
||||||
|
callback.onFailure("订单查询请求失败:" + errorMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一下单回调接口
|
||||||
|
*/
|
||||||
|
public interface OnCreateOrderCallback {
|
||||||
|
void onSuccess(String outTradeNo, String codeUrl);
|
||||||
|
void onFailure(String errorMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单查询回调接口
|
||||||
|
*/
|
||||||
|
public interface OnQueryOrderCallback {
|
||||||
|
void onSuccess(boolean isPaySuccess, String tradeState);
|
||||||
|
void onFailure(String errorMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,282 @@
|
|||||||
|
package cc.winboll.studio.winboll.utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.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.winboll.models.ResponseData;
|
||||||
|
import cc.winboll.studio.winboll.models.UserInfoModel;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
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;
|
||||||
|
|
||||||
|
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.readByteArrayFromFile(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;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package cc.winboll.studio.winboll.utils;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import com.google.zxing.BarcodeFormat;
|
||||||
|
import com.google.zxing.EncodeHintType;
|
||||||
|
import com.google.zxing.WriterException;
|
||||||
|
import com.google.zxing.common.BitMatrix;
|
||||||
|
import com.google.zxing.qrcode.QRCodeWriter;
|
||||||
|
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
|
||||||
|
import com.journeyapps.barcodescanner.BarcodeEncoder;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ZXing二维码生成工具类
|
||||||
|
* 依赖:com.google.zxing:core:3.4.1 + com.journeyapps:zxing-android-embedded:3.6.0
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2026/01/07
|
||||||
|
*/
|
||||||
|
public class ZXingUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成二维码Bitmap(核心方法,使用journeyapps工具类)
|
||||||
|
* @param content 内容(如微信支付的code_url)
|
||||||
|
* @param width 二维码宽度(px)
|
||||||
|
* @param height 二维码高度(px)
|
||||||
|
* @return 二维码Bitmap,失败返回null
|
||||||
|
*/
|
||||||
|
public static Bitmap createQRCodeBitmap(String content, int width, int height) {
|
||||||
|
// 1. 入参合法性校验
|
||||||
|
if (content == null || content.trim().isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (width <= 0 || height <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 配置二维码参数
|
||||||
|
Map<EncodeHintType, Object> hints = new HashMap<>();
|
||||||
|
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); // 字符编码
|
||||||
|
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); // 高容错级别(H级可容忍30%遮挡)
|
||||||
|
hints.put(EncodeHintType.MARGIN, 1); // 边距(值越小,二维码越紧凑,建议1-4)
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 3. 生成BitMatrix(二维码矩阵)
|
||||||
|
QRCodeWriter qrCodeWriter = new QRCodeWriter();
|
||||||
|
BitMatrix bitMatrix = qrCodeWriter.encode(
|
||||||
|
content,
|
||||||
|
BarcodeFormat.QR_CODE,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
hints
|
||||||
|
);
|
||||||
|
|
||||||
|
// 4. 转换BitMatrix为Bitmap(关键:使用journeyapps的BarcodeEncoder)
|
||||||
|
BarcodeEncoder barcodeEncoder = new BarcodeEncoder();
|
||||||
|
return barcodeEncoder.createBitmap(bitMatrix);
|
||||||
|
|
||||||
|
} catch (WriterException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重载方法:生成正方形二维码(宽度=高度)
|
||||||
|
* @param content 内容
|
||||||
|
* @param size 二维码边长(px)
|
||||||
|
* @return 二维码Bitmap
|
||||||
|
*/
|
||||||
|
public static Bitmap createQRCodeBitmap(String content, int size) {
|
||||||
|
return createQRCodeBitmap(content, size, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,232 @@
|
|||||||
|
package cc.winboll.studio.winboll.views;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.webkit.WebChromeClient;
|
||||||
|
import android.webkit.WebResourceError;
|
||||||
|
import android.webkit.WebResourceRequest;
|
||||||
|
import android.webkit.WebSettings;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
import android.webkit.WebViewClient;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||||
|
* @Date 2025/11/27 11:05
|
||||||
|
* @Describe 自定义WebView控件(WinBoLLView)
|
||||||
|
* 集成进度监听、页面加载控制、安全配置等核心能力
|
||||||
|
*/
|
||||||
|
public class WinBoLLView extends WebView {
|
||||||
|
public static final String TAG = "WinBoLLView";
|
||||||
|
|
||||||
|
private ProgressBar mProgressBar; // 页面加载进度条(可选,增强用户体验)
|
||||||
|
private OnPageStatusListener mStatusListener; // 页面状态监听回调
|
||||||
|
private boolean mIsLoading = false; // 自定义加载状态标记(替代系统isLoading())
|
||||||
|
|
||||||
|
// 构造方法(兼容代码创建和XML布局引用)
|
||||||
|
public WinBoLLView(Context context) {
|
||||||
|
super(context);
|
||||||
|
initWebViewSettings();
|
||||||
|
initWebViewClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
public WinBoLLView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
initWebViewSettings();
|
||||||
|
initWebViewClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
public WinBoLLView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
initWebViewSettings();
|
||||||
|
initWebViewClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化WebView基础配置(安全+性能+兼容性)
|
||||||
|
*/
|
||||||
|
private void initWebViewSettings() {
|
||||||
|
WebSettings settings = getSettings();
|
||||||
|
|
||||||
|
// 基础功能配置
|
||||||
|
settings.setJavaScriptEnabled(true); // 启用JS(根据需求决定是否开启)
|
||||||
|
settings.setSupportZoom(true); // 支持缩放
|
||||||
|
settings.setBuiltInZoomControls(true); // 显示缩放控件
|
||||||
|
settings.setDisplayZoomControls(false); // 隐藏系统缩放控件(优化UI)
|
||||||
|
settings.setLoadWithOverviewMode(true); // 自适应屏幕
|
||||||
|
settings.setUseWideViewPort(true); // 支持宽视角
|
||||||
|
|
||||||
|
// 缓存配置(提升加载速度)
|
||||||
|
settings.setCacheMode(WebSettings.LOAD_DEFAULT);
|
||||||
|
settings.setDomStorageEnabled(true); // 启用DOM存储
|
||||||
|
settings.setDatabaseEnabled(true); // 启用数据库存储
|
||||||
|
|
||||||
|
// 安全配置(防止XSS和恶意跳转)
|
||||||
|
settings.setJavaScriptCanOpenWindowsAutomatically(false); // 禁止JS自动打开窗口
|
||||||
|
setBackgroundColor(0x00000000); // 透明背景(避免白屏闪烁)
|
||||||
|
|
||||||
|
// 适配HTTPS和HTTP混合内容(Android 5.0+)
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化WebViewClient和ChromeClient(控制页面加载和进度)
|
||||||
|
*/
|
||||||
|
private void initWebViewClient() {
|
||||||
|
// 控制页面跳转(不打开系统浏览器)
|
||||||
|
setWebViewClient(new WebViewClient() {
|
||||||
|
@Override
|
||||||
|
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
|
||||||
|
// 拦截URL加载,使用当前WebView打开
|
||||||
|
view.loadUrl(request.getUrl().toString());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPageStarted(WebView view, String url, android.graphics.Bitmap favicon) {
|
||||||
|
super.onPageStarted(view, url, favicon);
|
||||||
|
// 页面开始加载:更新自定义加载状态标记
|
||||||
|
mIsLoading = true;
|
||||||
|
// 更新进度条+回调状态
|
||||||
|
if (mProgressBar != null) mProgressBar.setVisibility(VISIBLE);
|
||||||
|
if (mStatusListener != null) mStatusListener.onPageStarted(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPageFinished(WebView view, String url) {
|
||||||
|
super.onPageFinished(view, url);
|
||||||
|
// 页面加载完成:更新自定义加载状态标记
|
||||||
|
mIsLoading = false;
|
||||||
|
// 隐藏进度条+回调状态
|
||||||
|
if (mProgressBar != null) mProgressBar.setVisibility(GONE);
|
||||||
|
if (mStatusListener != null) mStatusListener.onPageFinished(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
|
||||||
|
super.onReceivedError(view, request, error);
|
||||||
|
// 页面加载错误:更新自定义加载状态标记
|
||||||
|
mIsLoading = false;
|
||||||
|
// 回调错误信息
|
||||||
|
if (mStatusListener != null) {
|
||||||
|
String errorMsg = error.getDescription().toString();
|
||||||
|
mStatusListener.onPageError(errorMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听页面加载进度
|
||||||
|
setWebChromeClient(new WebChromeClient() {
|
||||||
|
@Override
|
||||||
|
public void onProgressChanged(WebView view, int newProgress) {
|
||||||
|
super.onProgressChanged(view, newProgress);
|
||||||
|
// 更新进度条进度
|
||||||
|
if (mProgressBar != null) mProgressBar.setProgress(newProgress);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------- 对外暴露的控制方法(供Fragment调用) -------------------
|
||||||
|
/**
|
||||||
|
* 加载URL(增加空值校验+优先HTTPS协议)
|
||||||
|
*/
|
||||||
|
public void loadUrlSafe(String url) {
|
||||||
|
if (url == null || url.trim().isEmpty()) {
|
||||||
|
if (mStatusListener != null) mStatusListener.onPageError("URL不能为空");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 协议补全:优先HTTPS,兼容HTTP(解决ERR_CLEARTEXT_NOT_PERMITTED)
|
||||||
|
if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
||||||
|
url = "https://" + url; // 改为优先HTTPS,而非HTTP
|
||||||
|
}
|
||||||
|
super.loadUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新当前页面(使用自定义mIsLoading判断加载状态)
|
||||||
|
*/
|
||||||
|
public void refreshPage() {
|
||||||
|
if (mIsLoading) { // 替换原isLoading(),使用自定义标记
|
||||||
|
stopLoading(); // 若正在加载,先停止再刷新
|
||||||
|
}
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 停止页面加载(使用自定义mIsLoading判断加载状态)
|
||||||
|
*/
|
||||||
|
public void stopPageLoad() {
|
||||||
|
if (mIsLoading) { // 替换原isLoading(),使用自定义标记
|
||||||
|
stopLoading();
|
||||||
|
mIsLoading = false; // 停止后更新状态标记
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 前进(判断是否有前进历史)
|
||||||
|
*/
|
||||||
|
public boolean goForwardSafe() {
|
||||||
|
if (canGoForward()) {
|
||||||
|
goForward();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 后退(判断是否有后退历史)
|
||||||
|
*/
|
||||||
|
public boolean goBackSafe() {
|
||||||
|
if (canGoBack()) {
|
||||||
|
goBack();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------- 辅助功能:进度条和状态监听 -------------------
|
||||||
|
/**
|
||||||
|
* 设置进度条(绑定Fragment中的进度条控件)
|
||||||
|
*/
|
||||||
|
public void setProgressBar(ProgressBar progressBar) {
|
||||||
|
this.mProgressBar = progressBar;
|
||||||
|
if (mProgressBar != null) {
|
||||||
|
mProgressBar.setMax(100);
|
||||||
|
mProgressBar.setVisibility(GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置页面状态监听(供Fragment接收加载状态)
|
||||||
|
*/
|
||||||
|
public void setOnPageStatusListener(OnPageStatusListener listener) {
|
||||||
|
this.mStatusListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 页面状态监听接口
|
||||||
|
*/
|
||||||
|
public interface OnPageStatusListener {
|
||||||
|
void onPageStarted(String url); // 页面开始加载
|
||||||
|
void onPageFinished(String url); // 页面加载完成
|
||||||
|
void onPageError(String errorMsg); // 页面加载错误
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------- 资源释放(防止内存泄漏) -------------------
|
||||||
|
/**
|
||||||
|
* 销毁WebView(必须在Fragment销毁时调用)
|
||||||
|
*/
|
||||||
|
public void destroyWebView() {
|
||||||
|
// 停止加载并清空历史
|
||||||
|
stopLoading();
|
||||||
|
mIsLoading = false; // 销毁时重置加载状态
|
||||||
|
clearHistory();
|
||||||
|
// 移除所有WebViewClient(避免内存泄漏)
|
||||||
|
setWebViewClient(null);
|
||||||
|
setWebChromeClient(null);
|
||||||
|
// 销毁WebView
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
package cc.winboll.studio.winboll.widgets;
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2025/02/15 14:41:25
|
||||||
|
* @Describe TimeWidget
|
||||||
|
*/
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.appwidget.AppWidgetManager;
|
||||||
|
import android.appwidget.AppWidgetProvider;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.widget.RemoteViews;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import cc.winboll.studio.winboll.R;
|
||||||
|
import cc.winboll.studio.winboll.WinBoLL;
|
||||||
|
import cc.winboll.studio.winboll.models.WinBoLLModel;
|
||||||
|
import cc.winboll.studio.winboll.models.WinBoLLNewsBean;
|
||||||
|
import cc.winboll.studio.winboll.receivers.APPNewsWidgetClickListener;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public class APPNewsWidget extends AppWidgetProvider {
|
||||||
|
|
||||||
|
public static final String TAG = "APPNewsWidget";
|
||||||
|
|
||||||
|
public static final String ACTION_WAKEUP_SERVICE = APPNewsWidget.class.getName() + ".ACTION_WAKEUP_SERVICE";
|
||||||
|
public static final String ACTION_RELOAD_REPORT = APPNewsWidget.class.getName() + ".ACTION_RELOAD_REPORT";
|
||||||
|
|
||||||
|
|
||||||
|
volatile static ArrayList<WinBoLLNewsBean> _WinBoLLNewsBeanList;
|
||||||
|
final static int _MAX_PAGES = 10;
|
||||||
|
final static int _OnePageLinesCount = 5;
|
||||||
|
volatile static int _CurrentPageIndex = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
|
||||||
|
initWinBoLLNewsBeanList(context);
|
||||||
|
for (int appWidgetId : appWidgetIds) {
|
||||||
|
updateAppWidget(context, appWidgetManager, appWidgetId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
super.onReceive(context, intent);
|
||||||
|
initWinBoLLNewsBeanList(context);
|
||||||
|
if (intent.getAction().equals(ACTION_RELOAD_REPORT)) {
|
||||||
|
LogUtils.d(TAG, "ACTION_RELOAD_REPORT");
|
||||||
|
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
|
||||||
|
int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, APPNewsWidget.class));
|
||||||
|
for (int appWidgetId : appWidgetIds) {
|
||||||
|
updateAppWidget(context, appWidgetManager, appWidgetId);
|
||||||
|
}
|
||||||
|
}else if (intent.getAction().equals(ACTION_WAKEUP_SERVICE)) {
|
||||||
|
// LogUtils.d(TAG, "ACTION_WAKEUP_SERVICE");
|
||||||
|
// String szWinBoLLModel = intent.getStringExtra(WinBoLL.EXTRA_WINBOLLMODEL);
|
||||||
|
// LogUtils.d(TAG, String.format("szWinBoLLModel %s", szWinBoLLModel));
|
||||||
|
// if (szWinBoLLModel != null && !szWinBoLLModel.equals("")) {
|
||||||
|
// try {
|
||||||
|
// WinBoLLModel bean = WinBoLLModel.parseStringToBean(szWinBoLLModel, WinBoLLModel.class);
|
||||||
|
// if (bean != null) {
|
||||||
|
// String szAppPackageName = bean.getAppPackageName();
|
||||||
|
// LogUtils.d(TAG, String.format("szAppPackageName %s", szAppPackageName));
|
||||||
|
// String szAppMainServiveName = bean.getAppMainServiveName();
|
||||||
|
// LogUtils.d(TAG, String.format("szAppMainServiveName %s", szAppMainServiveName));
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// String appName = AppUtils.getAppNameByPackageName(context, szAppPackageName);
|
||||||
|
// LogUtils.d(TAG, String.format("appName %s", appName));
|
||||||
|
// WinBoLLNewsBean winBollNewsBean = new WinBoLLNewsBean(appName);
|
||||||
|
// SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
|
||||||
|
// String currentTime = sdf.format(new Date());
|
||||||
|
// StringBuilder sbLine = new StringBuilder();
|
||||||
|
// sbLine.append("[");
|
||||||
|
// sbLine.append(currentTime);
|
||||||
|
// sbLine.append("] Wake up ");
|
||||||
|
// sbLine.append(appName);
|
||||||
|
// winBollNewsBean.setMessage(sbLine.toString());
|
||||||
|
//
|
||||||
|
// addWinBoLLNewsBean(context, winBollNewsBean);
|
||||||
|
//
|
||||||
|
// AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
|
||||||
|
// int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, APPNewsWidget.class));
|
||||||
|
// for (int appWidgetId : appWidgetIds) {
|
||||||
|
// updateAppWidget(context, appWidgetManager, appWidgetId);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// } catch (IOException e) {
|
||||||
|
// LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// 加入新报告信息
|
||||||
|
//
|
||||||
|
public synchronized static void addWinBoLLNewsBean(Context context, WinBoLLNewsBean bean) {
|
||||||
|
initWinBoLLNewsBeanList(context);
|
||||||
|
_WinBoLLNewsBeanList.add(0, bean);
|
||||||
|
// 控制记录总数
|
||||||
|
while (_WinBoLLNewsBeanList.size() > _MAX_PAGES * _OnePageLinesCount) {
|
||||||
|
_WinBoLLNewsBeanList.remove(_WinBoLLNewsBeanList.size() - 1);
|
||||||
|
}
|
||||||
|
WinBoLLNewsBean.saveBeanList(context, _WinBoLLNewsBeanList, WinBoLLNewsBean.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized static void initWinBoLLNewsBeanList(Context context) {
|
||||||
|
if (_WinBoLLNewsBeanList == null) {
|
||||||
|
_WinBoLLNewsBeanList = new ArrayList<WinBoLLNewsBean>();
|
||||||
|
WinBoLLNewsBean.loadBeanList(context, _WinBoLLNewsBeanList, WinBoLLNewsBean.class);
|
||||||
|
}
|
||||||
|
if (_WinBoLLNewsBeanList == null) {
|
||||||
|
_WinBoLLNewsBeanList = new ArrayList<WinBoLLNewsBean>();
|
||||||
|
WinBoLLNewsBean.saveBeanList(context, _WinBoLLNewsBeanList, WinBoLLNewsBean.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
|
||||||
|
LogUtils.d(TAG, "updateAppWidget(...)");
|
||||||
|
|
||||||
|
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_news);
|
||||||
|
//设置按钮点击事件
|
||||||
|
Intent intentPre = new Intent(context, APPNewsWidgetClickListener.class);
|
||||||
|
intentPre.setAction(APPNewsWidgetClickListener.ACTION_PRE);
|
||||||
|
PendingIntent pendingIntentPre = PendingIntent.getBroadcast(context, 0, intentPre, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
views.setOnClickPendingIntent(R.id.widget_button_pre, pendingIntentPre);
|
||||||
|
Intent intentNext = new Intent(context, APPNewsWidgetClickListener.class);
|
||||||
|
intentNext.setAction(APPNewsWidgetClickListener.ACTION_NEXT);
|
||||||
|
PendingIntent pendingIntentNext = PendingIntent.getBroadcast(context, 0, intentNext, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
views.setOnClickPendingIntent(R.id.widget_button_next, pendingIntentNext);
|
||||||
|
|
||||||
|
views.setTextViewText(R.id.tv_msg, getPageInfo());
|
||||||
|
views.setTextViewText(R.id.tv_news, getMessage());
|
||||||
|
appWidgetManager.updateAppWidget(appWidgetId, views);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getMessage() {
|
||||||
|
ArrayList<String> msgTemp = new ArrayList<String>();
|
||||||
|
if (_WinBoLLNewsBeanList != null) {
|
||||||
|
int start = _OnePageLinesCount * _CurrentPageIndex;
|
||||||
|
start = _WinBoLLNewsBeanList.size() > start ? start : _WinBoLLNewsBeanList.size() - 1;
|
||||||
|
for (int i = start, j = 0; i < _WinBoLLNewsBeanList.size() && j < _OnePageLinesCount && start > -1; i++, j++) {
|
||||||
|
msgTemp.add(_WinBoLLNewsBeanList.get(i).getMessage());
|
||||||
|
}
|
||||||
|
String message = String.join("\n", msgTemp);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void prePage(Context context) {
|
||||||
|
if (_WinBoLLNewsBeanList != null) {
|
||||||
|
if (_CurrentPageIndex > 0) {
|
||||||
|
_CurrentPageIndex = _CurrentPageIndex - 1;
|
||||||
|
}
|
||||||
|
Intent intentWidget = new Intent(context, APPNewsWidget.class);
|
||||||
|
intentWidget.setAction(APPNewsWidget.ACTION_RELOAD_REPORT);
|
||||||
|
context.sendBroadcast(intentWidget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void nextPage(Context context) {
|
||||||
|
if (_WinBoLLNewsBeanList != null) {
|
||||||
|
if ((_CurrentPageIndex + 1) * _OnePageLinesCount < _WinBoLLNewsBeanList.size()) {
|
||||||
|
_CurrentPageIndex = _CurrentPageIndex + 1;
|
||||||
|
}
|
||||||
|
Intent intentWidget = new Intent(context, APPNewsWidget.class);
|
||||||
|
intentWidget.setAction(APPNewsWidget.ACTION_RELOAD_REPORT);
|
||||||
|
context.sendBroadcast(intentWidget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String getPageInfo() {
|
||||||
|
if (_WinBoLLNewsBeanList == null) {
|
||||||
|
return "0/0";
|
||||||
|
}
|
||||||
|
int leftCount = _WinBoLLNewsBeanList.size() % _OnePageLinesCount;
|
||||||
|
int currentPageCount = _WinBoLLNewsBeanList.size() / _OnePageLinesCount + (leftCount == 0 ?0: 1);
|
||||||
|
return String.format("%d/%d", _CurrentPageIndex + 1, currentPageCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package cc.winboll.studio.winboll.widgets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2025/02/17 20:32:12
|
||||||
|
*/
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.appwidget.AppWidgetManager;
|
||||||
|
import android.appwidget.AppWidgetProvider;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.widget.RemoteViews;
|
||||||
|
import cc.winboll.studio.winboll.R;
|
||||||
|
import cc.winboll.studio.libappbase.ToastUtils;
|
||||||
|
|
||||||
|
public class StatusWidget extends AppWidgetProvider {
|
||||||
|
|
||||||
|
public static final String TAG = "StatusWidget";
|
||||||
|
|
||||||
|
public static final String ACTION_STATUS_UPDATE = "cc.winboll.studio.libappbase.widgets.APPWidget.ACTION_STATUS_UPDATE";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
|
||||||
|
for (int appWidgetId : appWidgetIds) {
|
||||||
|
updateAppWidget(context, appWidgetManager, appWidgetId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
super.onReceive(context, intent);
|
||||||
|
if (intent.getAction().equals(ACTION_STATUS_UPDATE)) {
|
||||||
|
ToastUtils.show("Test");
|
||||||
|
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
|
||||||
|
int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, StatusWidget.class));
|
||||||
|
for (int appWidgetId : appWidgetIds) {
|
||||||
|
updateAppWidget(context, appWidgetManager, appWidgetId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
|
||||||
|
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_status);
|
||||||
|
//设置按钮点击事件
|
||||||
|
Intent intentAppButton = new Intent(context, StatusWidgetClickListener.class);
|
||||||
|
intentAppButton.setAction(StatusWidgetClickListener.ACTION_IVAPP);
|
||||||
|
PendingIntent pendingIntentAppButton = PendingIntent.getBroadcast(context, 0, intentAppButton, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
views.setOnClickPendingIntent(R.id.ivapp, pendingIntentAppButton);
|
||||||
|
|
||||||
|
// boolean isActive = ServiceUtils.isServiceRunning(context, TestService.class.getName());
|
||||||
|
// if (isActive) {
|
||||||
|
// views.setImageViewResource(R.id.ivapp, cc.winboll.studio.libappbase.R.drawable.ic_launcher);
|
||||||
|
// } else {
|
||||||
|
// views.setImageViewResource(R.id.ivapp, cc.winboll.studio.libappbase.R.drawable.ic_launcher_disable);
|
||||||
|
// }
|
||||||
|
appWidgetManager.updateAppWidget(appWidgetId, views);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package cc.winboll.studio.winboll.widgets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2025/02/17 20:33:53
|
||||||
|
* @Describe APPWidgetClickListener
|
||||||
|
*/
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import cc.winboll.studio.libappbase.ToastUtils;
|
||||||
|
|
||||||
|
public class StatusWidgetClickListener extends BroadcastReceiver {
|
||||||
|
|
||||||
|
public static final String TAG = "APPWidgetClickListener";
|
||||||
|
|
||||||
|
public static final String ACTION_IVAPP = "cc.winboll.studio.libappbase.widgets.StatusWidgetClickListener.ACTION_IVAPP";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
String action = intent.getAction();
|
||||||
|
if (action == null) {
|
||||||
|
LogUtils.d(TAG, String.format("action %s", action));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (action.equals(ACTION_IVAPP)) {
|
||||||
|
ToastUtils.show("ACTION_LAUNCHER");
|
||||||
|
} else {
|
||||||
|
LogUtils.d(TAG, String.format("action %s", action));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
210
winboll/src/main/java/com/tencent/wework/SpecCallbackSDK.java
Normal file
210
winboll/src/main/java/com/tencent/wework/SpecCallbackSDK.java
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
package com.tencent.wework;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.HashMap;;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @warning: 1. 不要修改成员变量名,native方法内有反射调用
|
||||||
|
* 2. 调用本地方法需保持包结构,本工具需放在包com.tencent.wework内
|
||||||
|
* 3. 不允许继承,类名和函数名均不可修改,会影响本地方法的引用,详见:javah生成本地方法头文件
|
||||||
|
*/
|
||||||
|
public final class SpecCallbackSDK {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 调用本地方法后实例化的对象指针
|
||||||
|
*/
|
||||||
|
private long specCallbackSDKptr = 0;
|
||||||
|
|
||||||
|
public long GetPtr() { return specCallbackSDKptr; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 回包的headers
|
||||||
|
*/
|
||||||
|
private Map<String, String> responseHeaders;
|
||||||
|
|
||||||
|
public Map<String, String> GetResponseHeaders() { return responseHeaders; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 回包的加密后的body
|
||||||
|
*/
|
||||||
|
private String responseBody;
|
||||||
|
|
||||||
|
public String GetResponseBody() { return responseBody; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 每个请求构造一个SpecCallbackSDK示例,
|
||||||
|
* SpecCallbackSDK仅持有headers和body的引用,
|
||||||
|
* 因此需保证headers和body的生存期比SpecCallbackSDK长
|
||||||
|
* @param method: 请求方法GET/POST
|
||||||
|
* @param headers: 请求header
|
||||||
|
* @param body: 请求body
|
||||||
|
* @example:
|
||||||
|
* SpecCallbackSDK sdk = new SpecCallbackSDK(method, headers, body);
|
||||||
|
* if (sdk.IsOk()) {
|
||||||
|
* String corpid = sdk.GetCorpId();
|
||||||
|
* String agentid = sdk.GetAgentId();
|
||||||
|
* String call_type = sdk.GetCallType();
|
||||||
|
* String data = sdk.GetData();
|
||||||
|
* //do something...
|
||||||
|
* }
|
||||||
|
* String response = ...;
|
||||||
|
* sdk.BuildResponseHeaderBody(response);
|
||||||
|
* Map<String, String> responseHeaders = sdk.GetResponseHeaders();
|
||||||
|
* String body = sdk.GetResponseBody();
|
||||||
|
* //do response
|
||||||
|
*
|
||||||
|
* @return errorcode 示例如下:
|
||||||
|
* -920001: 未设置请求方法
|
||||||
|
* -920002: 未设置请求header
|
||||||
|
* -920003: 未设置请求body
|
||||||
|
* */
|
||||||
|
public SpecCallbackSDK(String method, Map<String, String> headers, String body) {
|
||||||
|
try {
|
||||||
|
specCallbackSDKptr = NewCallbackSDK(method, headers, body);
|
||||||
|
} catch (Exception e) {
|
||||||
|
SpecUtil.WWSpecLogError("SpecCallbackSDK exception caught", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private native long NewCallbackSDK(String method, Map<String, String> headers, String body);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @usage 在Java对象的内存回收前析构C++对象
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void finalize() throws Throwable {
|
||||||
|
DeleteCPPInstance(specCallbackSDKptr);
|
||||||
|
super.finalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private native void DeleteCPPInstance(long specCallbackSDKptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 判断构造函数中传入的请求是否解析成功
|
||||||
|
* @return: 成功与否
|
||||||
|
* */
|
||||||
|
public boolean IsOk() {
|
||||||
|
return IsOk(specCallbackSDKptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private native boolean IsOk(long specCallbackSDKptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 获取请求的企业
|
||||||
|
* @require: 仅当IsOk() == true可调用
|
||||||
|
* @return: corpid
|
||||||
|
* */
|
||||||
|
public String GetCorpId() {
|
||||||
|
return GetCorpId(specCallbackSDKptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private native String GetCorpId(long specCallbackSDKptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 获取请求的应用
|
||||||
|
* @require: 仅当IsOk() == true可调用
|
||||||
|
* @return: agentid
|
||||||
|
* */
|
||||||
|
public long GetAgentId() {
|
||||||
|
return GetAgentId(specCallbackSDKptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private native long GetAgentId(long specCallbackSDKptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 获取请求的类型
|
||||||
|
* @require: 仅当IsOk() == true可调用
|
||||||
|
* @return: 1 - 来自[应用调用专区]的请求
|
||||||
|
* 2 - 来自企业微信的回调事件
|
||||||
|
* */
|
||||||
|
public long GetCallType() {
|
||||||
|
return GetCallType(specCallbackSDKptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private native long GetCallType(long specCallbackSDKptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 获取请求数据
|
||||||
|
* @require: 仅当IsOk() == true可调用
|
||||||
|
* @return: 请求数据,根据call_type可能是:
|
||||||
|
* - 企业微信回调事件
|
||||||
|
* - [应用调用专区]接口中的request_data
|
||||||
|
* */
|
||||||
|
public String GetData() {
|
||||||
|
return GetData(specCallbackSDKptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private native String GetData(long specCallbackSDKptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 是否异步请求
|
||||||
|
* @require: 仅当IsOk() == true可调用
|
||||||
|
* @return: 是否异步请求
|
||||||
|
* */
|
||||||
|
public boolean GetIsAsync() {
|
||||||
|
return GetIsAsync(specCallbackSDKptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private native boolean GetIsAsync(long specCallbackSDKptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 获取请求的job_info,
|
||||||
|
* @require: 仅当IsOk() == true可调用
|
||||||
|
* @return: job_info,无需理解内容,
|
||||||
|
* 在同一个请求上下文中使用SpecSDK的时候传入
|
||||||
|
* */
|
||||||
|
public String GetJobInfo() {
|
||||||
|
return GetJobInfo(specCallbackSDKptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private native String GetJobInfo(long specCallbackSDKptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 获取请求的ability_id,[应用调用专区]接口时指定
|
||||||
|
* @require: 仅当IsOk() == true可调用
|
||||||
|
* @return: ability_id
|
||||||
|
* */
|
||||||
|
public String GetAbilityId() {
|
||||||
|
return GetAbilityId(specCallbackSDKptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private native String GetAbilityId(long specCallbackSDKptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 获取请求的notify_id,用于[应用同步调用专区程序]接口
|
||||||
|
* @require: 仅当IsOk() == true可调用
|
||||||
|
* @return: notify_id
|
||||||
|
* */
|
||||||
|
public String GetNotifyId() {
|
||||||
|
return GetNotifyId(specCallbackSDKptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private native String GetNotifyId(long specCallbackSDKptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 对返回包计算签名&加密
|
||||||
|
* @param response: 待加密的回包明文.如果IsOk()==false,传入空串即可
|
||||||
|
* @note 本接口的执行问题可查看日志
|
||||||
|
* */
|
||||||
|
public void BuildResponseHeaderBody(String response) {
|
||||||
|
try {
|
||||||
|
responseHeaders = new HashMap<String, String>();
|
||||||
|
responseBody = "";
|
||||||
|
BuildResponseHeaderBody(specCallbackSDKptr, response);
|
||||||
|
} catch (Exception e) {
|
||||||
|
SpecUtil.WWSpecLogError("SpecCallbackSDK exception caught", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private native void BuildResponseHeaderBody(long specCallbackSDKptr, String response);
|
||||||
|
|
||||||
|
// 静态代码块内还无法调用native日志函数,这里的日志在管理系统无法查询
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
Class.forName("com.tencent.wework.SpecUtil");
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
163
winboll/src/main/java/com/tencent/wework/SpecSDK.java
Normal file
163
winboll/src/main/java/com/tencent/wework/SpecSDK.java
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
package com.tencent.wework;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @warning: 1. 不要修改成员变量名,native方法内有反射调用
|
||||||
|
* 2. 调用本地方法需保持包结构,本工具需放在包com.tencent.wework内
|
||||||
|
* 3. 不允许继承,类名和函数名均不可修改,会影响本地方法的引用,详见:javah生成本地方法头文件
|
||||||
|
*/
|
||||||
|
public final class SpecSDK {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 调用本地方法后实例化的对象指针
|
||||||
|
*/
|
||||||
|
private long specSDKptr = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @usage invoke的请求
|
||||||
|
* @example "{\"limit\":1}
|
||||||
|
*/
|
||||||
|
private String request;
|
||||||
|
|
||||||
|
public void SetRequest(String request) {
|
||||||
|
this.request = request;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @usage 访问上一次invoke的结果
|
||||||
|
*/
|
||||||
|
private String response;
|
||||||
|
|
||||||
|
public String GetResponse() {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param corpid: 企业corpid,必选参数
|
||||||
|
* @param agentid: 应用id,必选参数
|
||||||
|
* @param ability_id: 能力ID,可选参数
|
||||||
|
* @param job_info: job_info,可选参数
|
||||||
|
* */
|
||||||
|
public SpecSDK(String corpId, long agentId) {
|
||||||
|
specSDKptr = NewSDK1(corpId, agentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private native long NewSDK1(String corpId, long agentId);
|
||||||
|
|
||||||
|
public SpecSDK(String corpId, long agentId, String abilityId) {
|
||||||
|
specSDKptr = NewSDK2(corpId, agentId, abilityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private native long NewSDK2(String corpId, long agentId, String abilityId);
|
||||||
|
|
||||||
|
public SpecSDK(String corpId, long agentId, String abilityId, String jobInfo) {
|
||||||
|
specSDKptr = NewSDK3(corpId, agentId, abilityId, jobInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private native long NewSDK3(String corpId, long agentId, String abilityId, String jobInfo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 使用callback的请求来初始化
|
||||||
|
* @param callback_sdk: 要求IsOk()==true
|
||||||
|
* @return C++内部指针,创建失败时指针仍为0,并输出错误日志
|
||||||
|
* */
|
||||||
|
public SpecSDK(SpecCallbackSDK callbackSDK) {
|
||||||
|
specSDKptr = NewSDK4(callbackSDK.GetPtr());
|
||||||
|
}
|
||||||
|
|
||||||
|
private native long NewSDK4(long callbackSDK);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @usage 在Java对象的内存回收前析构C++对象
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void finalize() throws Throwable {
|
||||||
|
DeleteCPPInstance(specSDKptr);
|
||||||
|
super.finalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private native void DeleteCPPInstance(long specSDKptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 用于在专区内调用企业微信接口
|
||||||
|
* @param api_name 接口名
|
||||||
|
* @param request json格式的请求数据
|
||||||
|
* @param response json格式的返回数据
|
||||||
|
* @return errorcode 参考如下:
|
||||||
|
* 0: 成功
|
||||||
|
* -910001: SDK没有初始化
|
||||||
|
* -910002: 没有设置请求体
|
||||||
|
* -910003: 没有设置请求的API
|
||||||
|
* -910004: 在SDK成员内找不到成员"response",注意lib内有反射机制,不要修改成员变量名
|
||||||
|
* -910005: 使用未初始化的callback初始化SDK
|
||||||
|
* -910006: invoke调用失败,应检查日志查看具体原因
|
||||||
|
* -910007: 响应体为空
|
||||||
|
* @note 当返回0时,表示没有网络或请求协议层面或调用方法的失败,
|
||||||
|
* 调用方需继续检查response中的errcode字段确保业务层面的成功
|
||||||
|
*
|
||||||
|
* @usage 当前版本sdk支持的接口列表,每个接口的具体协议请查看企业微信文档:
|
||||||
|
* https://developer.work.weixin.qq.com/document/path/91201
|
||||||
|
*
|
||||||
|
* +--------------------------------+--------------------------------+
|
||||||
|
* |接口名 |描述 |
|
||||||
|
* |--------------------------------|--------------------------------|
|
||||||
|
* |program_async_job_call_back |上报异步任务结果 |
|
||||||
|
* |sync_msg |获取会话记录 |
|
||||||
|
* |get_group_chat |获取内部群信息 |
|
||||||
|
* |get_agree_status_single |获取单聊会话同意情况 |
|
||||||
|
* |get_agree_status_room |获取群聊会话同意情况 |
|
||||||
|
* |set_hide_sensitiveinfo_config |设置成员会话组件敏感信息隐藏配置|
|
||||||
|
* |get_hide_sensitiveinfo_config |获取成员会话组件敏感信息隐藏配置|
|
||||||
|
* |search_chat |会话名称搜索 |
|
||||||
|
* |search_msg |会话消息搜索 |
|
||||||
|
* |create_rule |新增关键词规则 |
|
||||||
|
* |get_rule_list |获取关键词列表 |
|
||||||
|
* |get_rule_detail |获取关键词规则详情 |
|
||||||
|
* |update_rule |修改关键词规则 |
|
||||||
|
* |delete_rule |删除关键词规则 |
|
||||||
|
* |get_hit_msg_list |获取命中关键词规则的会话记录 |
|
||||||
|
* |create_sentiment_task |创建情感分析任务 |
|
||||||
|
* |get_sentiment_result |获取情感分析结果 |
|
||||||
|
* |create_summary_task |创建摘要提取任务 |
|
||||||
|
* |get_summary_result |获取摘要提取结果 |
|
||||||
|
* |create_customer_tag_task |创建标签匹配任务 |
|
||||||
|
* |get_customer_tag_result |获取标签任务结果 |
|
||||||
|
* |create_recommend_dialog_task |创建话术推荐任务 |
|
||||||
|
* |get_recommend_dialog_result |获取话术推荐结果 |
|
||||||
|
* |create_private_task |创建自定义模型任务 |
|
||||||
|
* |get_private_task_result |获取自定义模型结果 |
|
||||||
|
* |(废弃)document_list |获取知识集列表 |
|
||||||
|
* |create_spam_task |会话反垃圾创建分析任务 |
|
||||||
|
* |get_spam_result |会话反垃圾获取任务结果 |
|
||||||
|
* |create_chatdata_export_job |创建会话内容导出任务 |
|
||||||
|
* |get_chatdata_export_job_status |获取会话内容导出任务结果 |
|
||||||
|
* |spec_notify_app |专区通知应用 |
|
||||||
|
* |create_program_task |创建自定义程序任务 |
|
||||||
|
* |get_program_task_result |获取自定义程序结果 |
|
||||||
|
* |knowledge_base_list |获取企业授权给应用的知识集列表 |
|
||||||
|
* |knowledge_base_create |创建知识集 |
|
||||||
|
* |knowledge_base_detail |获取知识集详情 |
|
||||||
|
* |knowledge_base_add_doc |添加知识集內容 |
|
||||||
|
* |knowledge_base_remove_doc |删除知识集內容 |
|
||||||
|
* |knowledge_base_modify_name |修改知识集名称 |
|
||||||
|
* |knowledge_base_delete |删除知识集 |
|
||||||
|
* |search_contact_or_customer |员工或者客户名称搜索 |
|
||||||
|
* |create_ww_model_task |创建企微通用模型任务 |
|
||||||
|
* |get_ww_model_result |获取企微通用模型结果 |
|
||||||
|
* |get_msg_list_by_page_id |page_id获取消息列表 |
|
||||||
|
* +-----------------------------------------------------------------+
|
||||||
|
* */
|
||||||
|
public int Invoke(String apiName) {
|
||||||
|
return Invoke(specSDKptr, apiName, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private native int Invoke(long sdk, String apiName, String request);
|
||||||
|
|
||||||
|
// 静态代码块内还无法调用native日志函数,这里的日志在管理系统无法查询
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
Class.forName("com.tencent.wework.SpecUtil");
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
171
winboll/src/main/java/com/tencent/wework/SpecUtil.java
Normal file
171
winboll/src/main/java/com/tencent/wework/SpecUtil.java
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
package com.tencent.wework;
|
||||||
|
|
||||||
|
//import java.lang.management.ManagementFactory;
|
||||||
|
//import java.lang.management.RuntimeMXBean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @warning: 1. 不要修改成员变量名,native方法内有反射调用
|
||||||
|
* 2. 调用本地方法需保持包结构,本工具需放在包com.tencent.wework内
|
||||||
|
* 3. 不允许继承,类名和函数名均不可修改,会影响本地方法的引用,详见:javah生成本地方法头文件
|
||||||
|
* 4. 使用其他工具打印的日志将无法被查询,如需使用SLF4j风格的日志或性能更好的日志框架,
|
||||||
|
* 请自行封装SpecUtil.SpecLog或SpecUtil.SpecLogNative方法
|
||||||
|
*
|
||||||
|
* @usage: 1. 获取SDK的版本号
|
||||||
|
* 2. 打印三个级别的日志
|
||||||
|
* 3. 开启调试模式
|
||||||
|
*/
|
||||||
|
public final class SpecUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description SDK版本号
|
||||||
|
* @usage 可用于校对不同SDK版本,或后续针对不同的SDK版本添加业务逻辑
|
||||||
|
*/
|
||||||
|
private static final String SDK_VERSION = "1.4.0";
|
||||||
|
|
||||||
|
public static String GetSDKVersion() {
|
||||||
|
return SDK_VERSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 正确的包名,SDK必须存放在"com.tencent.wework"下,否则会影响本地方法的调用
|
||||||
|
*/
|
||||||
|
private static final String EXPECTED_PACKAGE_NAME = "com.tencent.wework";
|
||||||
|
|
||||||
|
public static String GetExpectedPackageName() {
|
||||||
|
return EXPECTED_PACKAGE_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String LINE_SEPERATOR = System.getProperty("line.separator");
|
||||||
|
|
||||||
|
public static void WWSpecLogInfo(String... args) {
|
||||||
|
SpecLog('I', args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void WWSpecLogError(String... args) {
|
||||||
|
SpecLog('E', args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void WWSpecLogDebug(String... args) {
|
||||||
|
SpecLog('D', args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void WWSpecLogInfoWithReqId(String reqId, String... args) {
|
||||||
|
SpecLogWithReqId(reqId, 'I', args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void WWSpecLogErrorWithReqId(String reqId, String... args) {
|
||||||
|
SpecLogWithReqId(reqId, 'E', args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void WWSpecLogDebugWithReqId(String reqId, String... args) {
|
||||||
|
SpecLogWithReqId(reqId, 'D', args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @usage 打印标准日志
|
||||||
|
* @note 只有使用SpecLog和SpecLogNative函数打印的日志能被调试平台查询,其他框架的日志仅能本地查看
|
||||||
|
* @param logLevel 日志级别,使用char传递,目前支持I——INFO、E——ERROR、D——DEBUG
|
||||||
|
* @param args 自定义参数
|
||||||
|
*/
|
||||||
|
public static void SpecLog(char logLevel, String... args) {
|
||||||
|
StackTraceElement element = Thread.currentThread().getStackTrace()[3];
|
||||||
|
SpecLogNative(
|
||||||
|
logLevel,
|
||||||
|
element.getFileName(),
|
||||||
|
element.getLineNumber(),
|
||||||
|
String.join(",", args).replace(LINE_SEPERATOR, " ")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @usage 打印标准日志
|
||||||
|
* @note 只有使用SpecLog和SpecLogNative函数打印的日志能被调试平台查询,其他框架的日志仅能本地查看
|
||||||
|
* @param reqid 请求id
|
||||||
|
* @param logLevel 日志级别,使用char传递,目前支持I——INFO、E——ERROR、D——DEBUG
|
||||||
|
* @param args 自定义参数
|
||||||
|
*/
|
||||||
|
public static void SpecLogWithReqId(String reqId, char logLevel, String... args) {
|
||||||
|
StackTraceElement element = Thread.currentThread().getStackTrace()[3];
|
||||||
|
SpecLogNativeWithReqId(
|
||||||
|
reqId,
|
||||||
|
logLevel,
|
||||||
|
element.getFileName(),
|
||||||
|
element.getLineNumber(),
|
||||||
|
String.join(",", args).replace(LINE_SEPERATOR, " ")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @usage 打印标准日志
|
||||||
|
* @note 只有使用SpecLog和SpecLogNative函数打印的日志能被调试平台查询,其他框架的日志仅能本地查看
|
||||||
|
* 如需SLF4J风格的接口或对日志性能有进一步需求,开发者可以自行封装该函数
|
||||||
|
* @param logLevel 日志级别,使用char传递,目前支持I——INFO、E——ERROR、D——DEBUG
|
||||||
|
* @param fileName 文件名(类名)
|
||||||
|
* @param lineNumber 行号
|
||||||
|
* @param argsString 自定义参数
|
||||||
|
*/
|
||||||
|
public static native void SpecLogNative(char logLevel, String fileName, int lineNumber, String argsString);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @usage 打印标准日志
|
||||||
|
* @note 只有使用SpecLog和SpecLogNative函数打印的日志能被调试平台查询,其他框架的日志仅能本地查看
|
||||||
|
* 如需SLF4J风格的接口或对日志性能有进一步需求,开发者可以自行封装该函数
|
||||||
|
* @param reqid 请求id
|
||||||
|
* @param logLevel 日志级别,使用char传递,目前支持I——INFO、E——ERROR、D——DEBUG
|
||||||
|
* @param fileName 文件名(类名)
|
||||||
|
* @param lineNumber 行号
|
||||||
|
* @param argsString 自定义参数
|
||||||
|
*/
|
||||||
|
public static native void SpecLogNativeWithReqId(String reqId, char logLevel, String fileName, int lineNumber, String argsString);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @usage 开启调试模式,进程级别开关
|
||||||
|
* @param debugToken 调试凭证,在管理端获取
|
||||||
|
* @param accessToken 应用access token
|
||||||
|
* @return 是否开启成功
|
||||||
|
*/
|
||||||
|
public static boolean SpecOpenDebugMode(String debugToken, String accessToken) {
|
||||||
|
return SpecOpenDebugModeNative(debugToken, accessToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static native boolean SpecOpenDebugModeNative(String debugToken, String accessToken);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @usage 生成notify id。用户可调用本接口生成notify id,也可完全自定义生成
|
||||||
|
* @return 新的notify id,支持纳秒级隔离,内部异常时会输出日志并返回空串
|
||||||
|
* @note 1. 用户可先生成notify id,将其与回调数据关联存储后,再使用该notify id通知应用,
|
||||||
|
* 从而保证回调数据被请求时已存储完毕
|
||||||
|
*/
|
||||||
|
public static String GenerateNotifyId() {
|
||||||
|
return GenerateNotifyIdNative();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static native String GenerateNotifyIdNative();
|
||||||
|
|
||||||
|
static {
|
||||||
|
// 检查包名
|
||||||
|
String packageName = SpecUtil.class.getPackage().getName();
|
||||||
|
if (!EXPECTED_PACKAGE_NAME.equals(packageName)) {
|
||||||
|
// 静态代码块内还无法调用native日志函数,这里的日志在管理系统无法查询
|
||||||
|
System.out.println("SpecUtil class must be in package com.tencent.wework");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载so库
|
||||||
|
try {
|
||||||
|
System.loadLibrary("WeWorkSpecSDK");
|
||||||
|
} catch (UnsatisfiedLinkError e) {
|
||||||
|
System.out.println("libWeWorkSpecSDK.so not found in java.library.path");
|
||||||
|
e.printStackTrace();
|
||||||
|
System.exit(1);
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println("unexpected exception: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SpecUtil.WWSpecLogInfo("SDK init done", "packageName=" + packageName, "SDK_VERSION=" + SDK_VERSION);
|
||||||
|
}
|
||||||
|
}
|
||||||
7
winboll/src/main/res/drawable/bg_browser_top.xml
Normal file
7
winboll/src/main/res/drawable/bg_browser_top.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="@android:color/white"/>
|
||||||
|
<corners android:radius="8dp"/>
|
||||||
|
<stroke android:color="@android:color/darker_gray" android:width="1dp"/>
|
||||||
|
</shape>
|
||||||
7
winboll/src/main/res/drawable/bg_edittext.xml
Normal file
7
winboll/src/main/res/drawable/bg_edittext.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="@android:color/white"/>
|
||||||
|
<corners android:radius="24dp"/>
|
||||||
|
<stroke android:color="@android:color/darker_gray" android:width="1dp"/>
|
||||||
|
</shape>
|
||||||
41
winboll/src/main/res/drawable/bg_shadow.xml
Normal file
41
winboll/src/main/res/drawable/bg_shadow.xml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||||
|
<!-- 阴影部分 -->
|
||||||
|
<!-- 个人觉得更形象的表达:top代表下边的阴影高度,left代表右边的阴影宽度。其实也就是相对应的offset,solid中的颜色是阴影的颜色,也可以设置角度等等 -->
|
||||||
|
<item
|
||||||
|
android:left="2dp"
|
||||||
|
android:top="2dp"
|
||||||
|
android:right="2dp"
|
||||||
|
android:bottom="2dp">
|
||||||
|
<shape android:shape="rectangle" >
|
||||||
|
<gradient
|
||||||
|
android:angle="270"
|
||||||
|
android:endColor="#0F000000"
|
||||||
|
android:startColor="#0F000000" />
|
||||||
|
<corners
|
||||||
|
android:bottomLeftRadius="6dip"
|
||||||
|
android:bottomRightRadius="6dip"
|
||||||
|
android:topLeftRadius="6dip"
|
||||||
|
android:topRightRadius="6dip" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<!-- 背景部分 -->
|
||||||
|
<!-- 形象的表达:bottom代表背景部分在上边缘超出阴影的高度,right代表背景部分在左边超出阴影的宽度(相对应的offset) -->
|
||||||
|
<item
|
||||||
|
android:left="3dp"
|
||||||
|
android:top="3dp"
|
||||||
|
android:right="3dp"
|
||||||
|
android:bottom="5dp">
|
||||||
|
<shape android:shape="rectangle" >
|
||||||
|
<gradient
|
||||||
|
android:angle="270"
|
||||||
|
android:endColor="@color/colorAccent"
|
||||||
|
android:startColor="@color/colorAccent" />
|
||||||
|
<corners
|
||||||
|
android:bottomLeftRadius="6dip"
|
||||||
|
android:bottomRightRadius="6dip"
|
||||||
|
android:topLeftRadius="6dip"
|
||||||
|
android:topRightRadius="6dip" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
||||||
11
winboll/src/main/res/drawable/ic_cloud.xml
Normal file
11
winboll/src/main/res/drawable/ic_cloud.xml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:viewportWidth="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#ff000000"
|
||||||
|
android:pathData="M6.5,20Q4.22,20 2.61,18.43 1,16.85 1,14.58 1,12.63 2.17,11.1 3.35,9.57 5.25,9.15 5.88,6.85 7.75,5.43 9.63,4 12,4 14.93,4 16.96,6.04 19,8.07 19,11 20.73,11.2 21.86,12.5 23,13.78 23,15.5 23,17.38 21.69,18.69 20.38,20 18.5,20Z"/>
|
||||||
|
|
||||||
|
</vector>
|
||||||
11
winboll/src/main/res/drawable/ic_cloud_outline.xml
Normal file
11
winboll/src/main/res/drawable/ic_cloud_outline.xml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:viewportWidth="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#ff000000"
|
||||||
|
android:pathData="M6.5,20Q4.22,20 2.61,18.43 1,16.85 1,14.58 1,12.63 2.17,11.1 3.35,9.57 5.25,9.15 5.88,6.85 7.75,5.43 9.63,4 12,4 14.93,4 16.96,6.04 19,8.07 19,11 20.73,11.2 21.86,12.5 23,13.78 23,15.5 23,17.38 21.69,18.69 20.38,20 18.5,20M6.5,18H18.5Q19.55,18 20.27,17.27 21,16.55 21,15.5 21,14.45 20.27,13.73 19.55,13 18.5,13H17V11Q17,8.93 15.54,7.46 14.08,6 12,6 9.93,6 8.46,7.46 7,8.93 7,11H6.5Q5.05,11 4.03,12.03 3,13.05 3,14.5 3,15.95 4.03,17 5.05,18 6.5,18M12,12Z"/>
|
||||||
|
|
||||||
|
</vector>
|
||||||
13
winboll/src/main/res/drawable/ic_launcher.xml
Normal file
13
winboll/src/main/res/drawable/ic_launcher.xml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:clickable="true">
|
||||||
|
<item
|
||||||
|
android:width="256dp"
|
||||||
|
android:height="256dp"
|
||||||
|
android:left="0dp"
|
||||||
|
android:top="0dp"
|
||||||
|
android:right="0dp"
|
||||||
|
android:bottom="0dp"
|
||||||
|
android:drawable="@drawable/ic_winboll_logo">
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
||||||
170
winboll/src/main/res/drawable/ic_launcher_background.xml
Normal file
170
winboll/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="#26A69A"
|
||||||
|
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>
|
||||||
13
winboll/src/main/res/drawable/ic_launcher_beta.xml
Normal file
13
winboll/src/main/res/drawable/ic_launcher_beta.xml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:clickable="true"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp">
|
||||||
|
<item android:drawable="@drawable/ic_launcher_background"/>
|
||||||
|
<item
|
||||||
|
android:left="0dp"
|
||||||
|
android:top="0dp"
|
||||||
|
android:right="0dp"
|
||||||
|
android:bottom="0dp"
|
||||||
|
android:drawable="@drawable/ic_launcher"/>
|
||||||
|
</layer-list>
|
||||||
27
winboll/src/main/res/drawable/ic_winboll_help.xml
Normal file
27
winboll/src/main/res/drawable/ic_winboll_help.xml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<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="#FF1E9B54"
|
||||||
|
android:strokeColor="#FFF8E733"
|
||||||
|
android:strokeWidth="20.0"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeMiterLimit="10"
|
||||||
|
android:pathData="M254.63 35.45C374.95 35.45 473.38 133.89 473.38 254.2 473.38 374.51 374.95 472.95 254.63 472.95 134.32 472.95 35.88 374.51 35.88 254.2 35.88 133.89 134.32 35.45 254.63 35.45"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:strokeColor="#FF000000"
|
||||||
|
android:strokeWidth="1.0"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeMiterLimit="10"
|
||||||
|
android:pathData="M257.28 361.25C266.56 361.25 274.14 368.84 274.14 378.11 274.14 387.39 266.56 394.98 257.28 394.98 248.01 394.98 240.42 387.39 240.42 378.11 240.42 368.84 248.01 361.25 257.28 361.25"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#FF000000"
|
||||||
|
android:strokeWidth="30.0"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeMiterLimit="10"
|
||||||
|
android:pathData="M182.16 214.09C181.42 199.71 182.42 177.87 207.64 155.49 213.64 150.16 220.13 146.12 226.28 143.08 238.64 136.97 249.62 134.91 252.55 134.56 252.7 134.54 252.83 134.53 252.94 134.52 253.05 134.51 253.14 134.5 253.2 134.5 255.01 134.48 294.9 136.66 313.05 160.43 332.29 185.63 344.82 221.3 300.07 263.56 263.08 298.49 258.36 318 258.54 317.72"/>
|
||||||
|
</vector>
|
||||||
BIN
winboll/src/main/res/drawable/ic_winboll_jindouyun1.png
Normal file
BIN
winboll/src/main/res/drawable/ic_winboll_jindouyun1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 MiB |
BIN
winboll/src/main/res/drawable/ic_winboll_jindouyun2.png
Normal file
BIN
winboll/src/main/res/drawable/ic_winboll_jindouyun2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 MiB |
41
winboll/src/main/res/drawable/ic_winboll_log.xml
Normal file
41
winboll/src/main/res/drawable/ic_winboll_log.xml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<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="#FF1E9B54"
|
||||||
|
android:strokeColor="#FFF8E733"
|
||||||
|
android:strokeWidth="20.0"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeMiterLimit="10"
|
||||||
|
android:pathData="M254.63 35.45C374.95 35.45 473.38 133.89 473.38 254.2 473.38 374.51 374.95 472.95 254.63 472.95 134.32 472.95 35.88 374.51 35.88 254.2 35.88 133.89 134.32 35.45 254.63 35.45"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:strokeColor="#FF000000"
|
||||||
|
android:strokeWidth="20.0"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeMiterLimit="10"
|
||||||
|
android:pathData="M151.49 130.96C151.49 130.96 348.53 130.96 348.53 130.96 348.53 130.96 348.53 393.46 348.53 393.46 348.53 393.46 151.49 393.46 151.49 393.46 151.49 393.46 151.49 130.96 151.49 130.96"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:strokeColor="#FF000000"
|
||||||
|
android:strokeWidth="20.0"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeMiterLimit="10"
|
||||||
|
android:pathData="M186.28 207.75C186.28 207.75 304.95 207.75 304.95 207.75 304.95 207.75 304.95 205.97 304.95 205.97 304.95 205.97 186.28 205.97 186.28 205.97 186.28 205.97 186.28 207.75 186.28 207.75"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:strokeColor="#FF000000"
|
||||||
|
android:strokeWidth="20.0"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeMiterLimit="10"
|
||||||
|
android:pathData="M186.28 263.52C186.28 263.52 304.95 263.52 304.95 263.52 304.95 263.52 304.95 264.41 304.95 264.41 304.95 264.41 186.28 264.41 186.28 264.41 186.28 264.41 186.28 263.52 186.28 263.52"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:strokeColor="#FF000000"
|
||||||
|
android:strokeWidth="20.0"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeMiterLimit="10"
|
||||||
|
android:pathData="M186.28 323.62C186.28 323.62 304.95 323.62 304.95 323.62 304.95 323.62 304.95 320.62 304.95 320.62 304.95 320.62 186.28 320.62 186.28 320.62 186.28 320.62 186.28 323.62 186.28 323.62"/>
|
||||||
|
</vector>
|
||||||
48
winboll/src/main/res/drawable/ic_winboll_logo.xml
Normal file
48
winboll/src/main/res/drawable/ic_winboll_logo.xml
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<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="#FF1E9B54"
|
||||||
|
android:strokeColor="#FFF8E733"
|
||||||
|
android:strokeWidth="20.0"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeMiterLimit="10"
|
||||||
|
android:pathData="M254.63 35.45C374.95 35.45 473.38 133.89 473.38 254.2 473.38 374.51 374.95 472.95 254.63 472.95 134.32 472.95 35.88 374.51 35.88 254.2 35.88 133.89 134.32 35.45 254.63 35.45"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:strokeColor="#FFFFFFFF"
|
||||||
|
android:strokeWidth="1.0"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeMiterLimit="10"
|
||||||
|
android:pathData="M257.28 361.25C266.56 361.25 274.14 368.84 274.14 378.11 274.14 387.39 266.56 394.98 257.28 394.98 248.01 394.98 240.42 387.39 240.42 378.11 240.42 368.84 248.01 361.25 257.28 361.25"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#FF000000"
|
||||||
|
android:strokeWidth="30.0"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeMiterLimit="10"
|
||||||
|
android:pathData="M182.16 214.09C181.42 199.71 182.42 177.87 207.64 155.49 213.64 150.16 220.13 146.12 226.28 143.08 238.64 136.97 249.62 134.91 252.55 134.56 252.7 134.54 252.83 134.53 252.94 134.52 253.05 134.51 253.14 134.5 253.2 134.5 255.01 134.48 294.9 136.66 313.05 160.43 332.29 185.63 344.82 221.3 300.07 263.56 263.08 298.49 258.36 318 258.54 317.72"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#FFFFFFFF"
|
||||||
|
android:strokeWidth="30.0"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeMiterLimit="10"
|
||||||
|
android:pathData="M103.77 307.45C103.02 293.07 104.03 271.24 129.24 248.85 135.25 243.52 141.74 239.48 147.89 236.44 160.24 230.34 171.23 228.28 174.15 227.92 174.31 227.9 174.44 227.89 174.55 227.88 174.66 227.87 174.75 227.86 174.81 227.86 176.62 227.85 216.5 230.02 234.65 253.79 253.9 278.99 266.43 314.66 221.67 356.93 184.69 391.85 179.97 411.36 180.15 411.08"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#FFFFFFFF"
|
||||||
|
android:strokeWidth="30.0"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeMiterLimit="10"
|
||||||
|
android:pathData="M248.17 309.83C247.43 295.45 248.43 273.62 273.64 251.23 279.65 245.9 286.14 241.86 292.29 238.82 304.65 232.72 315.63 230.65 318.55 230.3 318.71 230.28 318.84 230.27 318.95 230.26 319.06 230.25 319.15 230.24 319.21 230.24 321.02 230.22 360.9 232.4 379.06 256.17 398.3 281.37 410.83 317.04 366.08 359.31 329.09 394.23 324.37 413.74 324.55 413.46"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#FFFFFFFF"
|
||||||
|
android:strokeWidth="30.0"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeMiterLimit="10"
|
||||||
|
android:pathData="M182.16 214.09C181.42 199.71 182.42 177.87 207.64 155.49 213.64 150.16 220.13 146.12 226.28 143.08 238.64 136.97 249.62 134.91 252.55 134.56 252.7 134.54 252.83 134.53 252.94 134.52 253.05 134.51 253.14 134.5 253.2 134.5 255.01 134.48 294.9 136.66 313.05 160.43 332.29 185.63 344.82 221.3 300.07 263.56 263.08 298.49 258.36 318 258.54 317.72"/>
|
||||||
|
</vector>
|
||||||
20
winboll/src/main/res/drawable/ic_winboll_point.xml
Normal file
20
winboll/src/main/res/drawable/ic_winboll_point.xml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="512dp"
|
||||||
|
android:height="512dp"
|
||||||
|
android:viewportWidth="512"
|
||||||
|
android:viewportHeight="512">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF1E9B54"
|
||||||
|
android:strokeColor="#FFF8E733"
|
||||||
|
android:strokeWidth="20.0"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeMiterLimit="10"
|
||||||
|
android:pathData="M254.63 35.45C374.95 35.45 473.38 133.89 473.38 254.2 473.38 374.51 374.95 472.95 254.63 472.95 134.32 472.95 35.88 374.51 35.88 254.2 35.88 133.89 134.32 35.45 254.63 35.45"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:strokeColor="#FFFFFFFF"
|
||||||
|
android:strokeWidth="1.0"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeMiterLimit="10"
|
||||||
|
android:pathData="M257.28 361.25C266.56 361.25 274.14 368.84 274.14 378.11 274.14 387.39 266.56 394.98 257.28 394.98 248.01 394.98 240.42 387.39 240.42 378.11 240.42 368.84 248.01 361.25 257.28 361.25"/>
|
||||||
|
</vector>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user