This commit is contained in:
37
winboll/README.md
Normal file
37
winboll/README.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# WinBoLL
|
||||
|
||||
#### 介绍
|
||||
WinBoLL 网站浏览器。
|
||||
|
||||
#### 软件架构
|
||||
适配安卓应用 [AIDE Pro] 的 Gradle 编译结构。
|
||||
也适配安卓应用 [AndroidIDE] 的 Gradle 编译结构。
|
||||
|
||||
|
||||
#### Gradle 编译说明
|
||||
调试版编译命令 :gradle assembleBetaDebug
|
||||
阶段版编译命令 :bash .winboll/bashPublishAPKAddTag.sh winboll
|
||||
|
||||
#### 使用说明
|
||||
3. Termux应用配置:
|
||||
- 已安装Termux(包名 com.termux );
|
||||
- 执行 echo "allow-external-apps = true" > ~/.termux/termux.properties
|
||||
|
||||
#### 参与贡献
|
||||
|
||||
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 @@
|
||||
|
||||
108
winboll/build.gradle
Normal file
108
winboll/build.gradle
Normal file
@@ -0,0 +1,108 @@
|
||||
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 26
|
||||
// 适配MIUI12
|
||||
targetSdkVersion 30
|
||||
versionCode 1
|
||||
// versionName 更新后需要手动设置
|
||||
// .winboll/winbollBuildProps.properties 文件的 stageCount=0
|
||||
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
|
||||
versionName "15.20"
|
||||
if(true) {
|
||||
versionName = genVersionName("${versionName}")
|
||||
}
|
||||
}
|
||||
|
||||
// 米盟 SDK
|
||||
packagingOptions {
|
||||
doNotStrip "*/*/libmimo_1011.so"
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
jniLibs.srcDirs = ['libs'] // 若SO库放在libs目录下
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api project(':libwinboll')
|
||||
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'
|
||||
// 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.github.bumptech.glide:glide:4.9.0'
|
||||
//annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
|
||||
|
||||
implementation "androidx.annotation:annotation:1.3.0"
|
||||
implementation "androidx.core:core:1.6.0"
|
||||
implementation "androidx.drawerlayout:drawerlayout:1.1.1"
|
||||
implementation "androidx.preference:preference:1.1.1"
|
||||
implementation "androidx.viewpager:viewpager:1.0.0"
|
||||
implementation "com.google.android.material:material:1.4.0"
|
||||
implementation "com.google.guava:guava:24.1-jre"
|
||||
/*
|
||||
implementation "io.noties.markwon:core:$markwonVersion"
|
||||
implementation "io.noties.markwon:ext-strikethrough:$markwonVersion"
|
||||
implementation "io.noties.markwon:linkify:$markwonVersion"
|
||||
implementation "io.noties.markwon:recycler:$markwonVersion"
|
||||
*/
|
||||
implementation 'com.termux:terminal-emulator:0.118.0'
|
||||
implementation 'com.termux:terminal-view:0.118.0'
|
||||
implementation 'com.termux:termux-shared:0.118.0'
|
||||
|
||||
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
|
||||
#Wed May 13 13:48:00 CST 2026
|
||||
stageCount=2
|
||||
libraryProject=libwinboll
|
||||
baseVersion=15.20
|
||||
publishVersion=15.20.1
|
||||
buildCount=2
|
||||
baseBetaVersion=15.20.2
|
||||
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>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package cc.winboll.studio.winboll.activities;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import cc.winboll.studio.winboll.R;
|
||||
|
||||
/**
|
||||
* Ollama 模型对话窗口
|
||||
*/
|
||||
public class OllamaWindowActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_ollama_window);
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
getSupportActionBar().setTitle("Ollama 窗口");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.menu_ollama_window, menu);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
140
winboll/src/beta/res/layout/activity_ollama_window.xml
Normal file
140
winboll/src/beta/res/layout/activity_ollama_window.xml
Normal file
@@ -0,0 +1,140 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#1E1E1E"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="#333333"
|
||||
android:elevation="4dp"
|
||||
android:theme="@style/ThemeOverlay.MaterialComponents.ActionBar" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="56dp"
|
||||
android:layout_marginBottom="48dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ollama 模型对话"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="系统提示:"
|
||||
android:textColor="#B0B0B0" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etSystemPrompt"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:background="#2D2D2D"
|
||||
android:hint="请输入系统提示"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textColorHint="#666666"
|
||||
android:gravity="top|start"
|
||||
android:inputType="textMultiLine" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="模型名称:"
|
||||
android:textColor="#B0B0B0" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etModelName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:background="#2D2D2D"
|
||||
android:hint="llama3"
|
||||
android:text="llama3"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textColorHint="#666666"
|
||||
android:singleLine="true" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="系统消息:"
|
||||
android:textColor="#B0B0B0" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etUserMessage"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="120dp"
|
||||
android:background="#2D2D2D"
|
||||
android:ems="10"
|
||||
android:gravity="top|start"
|
||||
android:hint="输入你要发送的消息..."
|
||||
android:inputType="textMultiLine" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etAssistantMessage"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="120dp"
|
||||
android:background="#2D2D2D"
|
||||
android:gravity="top|start"
|
||||
android:hint="模型响应将显示在这里..."
|
||||
android:inputType="textMultiLine" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSend"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="3"
|
||||
android:text="发送"
|
||||
android:textColor="#FFFFFF" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnStop"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_weight="1"
|
||||
android:text="停止"
|
||||
android:textColor="#FF5252" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
11
winboll/src/beta/res/menu/menu_ollama_window.xml
Normal file
11
winboll/src/beta/res/menu/menu_ollama_window.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_config"
|
||||
android:title="配置"
|
||||
android:orderInCategory="100"
|
||||
app:showAsAction="always" />
|
||||
|
||||
</menu>
|
||||
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>
|
||||
339
winboll/src/main/AndroidManifest.xml
Normal file
339
winboll/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,339 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="cc.winboll.studio.winboll"
|
||||
android:sharedUserId="com.termux">
|
||||
|
||||
<!-- 拥有完全的网络访问权限 -->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
|
||||
<!-- 发送持久广播 -->
|
||||
<uses-permission android:name="android.permission.BROADCAST_STICKY"/>
|
||||
|
||||
<!-- 对正在运行的应用重新排序 -->
|
||||
<uses-permission android:name="android.permission.REORDER_TASKS"/>
|
||||
|
||||
<!-- 计算应用存储空间 -->
|
||||
<uses-permission
|
||||
android:name="android.permission.GET_PACKAGE_SIZE"/>
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||
tools:ignore="QueryAllPackagesPermission"/>
|
||||
|
||||
<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"
|
||||
android:launchMode="singleTask">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:scheme="http"/>
|
||||
<data android:scheme="https"/>
|
||||
</intent-filter>
|
||||
|
||||
</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"/>
|
||||
|
||||
<activity android:name="cc.winboll.studio.winboll.activities.PatternLockActivity"
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
|
||||
<action android:name="cc.winboll.studio.winboll.activities.PatternLockActivity.ACTION_OPEN_PATTERN"/>
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
<activity android:name="cc.winboll.studio.winboll.unittest.TermuxEnvTestActivity"/>
|
||||
|
||||
<activity
|
||||
android:name=".termux.NfcTermuxBridgeActivity"
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
|
||||
<action android:name="cc.winboll.studio.winboll.termux.NfcTermuxBridgeActivity.ACTION_BUILD"/>
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
<activity android:name="cc.winboll.studio.winboll.applications.MyTermuxActivity"
|
||||
android:label="@string/my_termux_activity"
|
||||
android:exported="true"/>
|
||||
|
||||
</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,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,221 @@
|
||||
package cc.winboll.studio.winboll;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
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.DrawerFragmentActivity;
|
||||
import cc.winboll.studio.libaes.models.DrawerMenuBean;
|
||||
import cc.winboll.studio.libaes.utils.AESThemeUtil;
|
||||
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.AboutActivity;
|
||||
import cc.winboll.studio.winboll.activities.SettingsActivity;
|
||||
import cc.winboll.studio.winboll.applications.MyTermuxActivity;
|
||||
import cc.winboll.studio.winboll.fragments.BrowserFragment;
|
||||
import cc.winboll.studio.winboll.unittest.TermuxEnvTestActivity;
|
||||
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) {
|
||||
setTheme(cc.winboll.studio.winboll.theme.WinBoLLThemeUtil.getThemeTypeID(this));
|
||||
super.onCreate(savedInstanceState);
|
||||
initMainHandler();
|
||||
if (mBrowserFragment == null) {
|
||||
String externalUrl = extractExternalUrl(getIntent());
|
||||
if (externalUrl != null) {
|
||||
mBrowserFragment = BrowserFragment.newInstance(externalUrl);
|
||||
} else {
|
||||
mBrowserFragment = BrowserFragment.newInstance();
|
||||
}
|
||||
addFragment(mBrowserFragment);
|
||||
}
|
||||
showFragment(mBrowserFragment);
|
||||
}
|
||||
|
||||
// @Override
|
||||
// protected void onNewIntent(Intent intent) {
|
||||
// super.onNewIntent(intent);
|
||||
// String externalUrl = extractExternalUrl(intent);
|
||||
// if (externalUrl != null && mBrowserFragment != null && mBrowserFragment.getBrowserHandler() != null) {
|
||||
// Message msg = Message.obtain();
|
||||
// msg.what = BrowserFragment.MSG_OPEN_URL;
|
||||
// msg.obj = externalUrl;
|
||||
// mBrowserFragment.getBrowserHandler().sendMessage(msg);
|
||||
// }
|
||||
// }
|
||||
|
||||
private String extractExternalUrl(Intent intent) {
|
||||
if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) {
|
||||
Uri uri = intent.getData();
|
||||
if (uri != null) {
|
||||
return uri.toString();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
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);
|
||||
if (App.isDebugging()) {
|
||||
getMenuInflater().inflate(R.menu.toolbar_test, 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) {
|
||||
if (AESThemeUtil.onWinBoLLThemeItemSelected(this, item)) {
|
||||
recreate();
|
||||
} else {
|
||||
int nItemId = item.getItemId();
|
||||
if (nItemId == R.id.item_home) {
|
||||
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_about) {
|
||||
Intent intent = new Intent(getApplicationContext(), AboutActivity.class);
|
||||
WinBoLLActivityManager.getInstance().startWinBoLLActivity(getApplicationContext(), intent, AboutActivity.class);
|
||||
} else if (nItemId == R.id.item_mytermux) {
|
||||
Intent intent = new Intent(getApplicationContext(), MyTermuxActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
} else if (nItemId == R.id.item_termux_env_test) {
|
||||
Intent intent = new Intent(getApplicationContext(), TermuxEnvTestActivity.class);
|
||||
WinBoLLActivityManager.getInstance().startWinBoLLActivity(getApplicationContext(), intent, AboutActivity.class);
|
||||
} else if (nItemId == R.id.item_library_activity) {
|
||||
Intent intent = new Intent(getApplicationContext(), cc.winboll.studio.libwinboll.WinBoLLLibraryActivity.class);
|
||||
WinBoLLActivityManager.getInstance().startWinBoLLActivity(getApplicationContext(), intent, AboutActivity.class);
|
||||
} else {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ------------------- 新增:对外提供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,86 @@
|
||||
package cc.winboll.studio.winboll.activities;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.models.APPInfo;
|
||||
import cc.winboll.studio.libappbase.views.AboutView;
|
||||
import cc.winboll.studio.winboll.MainActivity;
|
||||
import cc.winboll.studio.winboll.R;
|
||||
import android.app.Activity;
|
||||
|
||||
/**
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2026/01/13 11:54
|
||||
* @Describe 应用介绍窗口
|
||||
*/
|
||||
public class AboutActivity extends BaseWinBoLLActivity {
|
||||
|
||||
@Override
|
||||
public Activity getActivity() {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public static final String TAG = "AboutActivity";
|
||||
|
||||
private Toolbar mToolbar;
|
||||
|
||||
@Override
|
||||
public String getTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_about);
|
||||
|
||||
// 设置工具栏
|
||||
initToolbar();
|
||||
|
||||
AboutView aboutView = getActivity().findViewById(R.id.aboutview);
|
||||
aboutView.setAPPInfo(genDefaultAppInfo());
|
||||
}
|
||||
|
||||
private void initToolbar() {
|
||||
LogUtils.d(TAG, "initToolbar() 开始初始化");
|
||||
mToolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
if (mToolbar == null) {
|
||||
LogUtils.e(TAG, "initToolbar() | Toolbar未找到");
|
||||
return;
|
||||
}
|
||||
setSupportActionBar(mToolbar);
|
||||
mToolbar.setSubtitle(getTag());
|
||||
((AppCompatActivity)getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d(TAG, "导航栏 点击返回按钮");
|
||||
getActivity().finish();
|
||||
}
|
||||
});
|
||||
LogUtils.d(TAG, "initToolbar() 配置完成");
|
||||
}
|
||||
|
||||
private APPInfo genDefaultAppInfo() {
|
||||
LogUtils.d(TAG, "genDefaultAppInfo() 调用");
|
||||
String branchName = "winboll";
|
||||
APPInfo appInfo = new APPInfo();
|
||||
appInfo.setAppName(getActivity().getString(R.string.app_name));
|
||||
appInfo.setAppIcon(R.drawable.ic_winboll);
|
||||
appInfo.setAppDescription(getActivity().getString(R.string.app_description));
|
||||
appInfo.setAppGitName("WinBoLL");
|
||||
appInfo.setAppGitOwner("Studio");
|
||||
appInfo.setAppGitAPPBranch(branchName);
|
||||
appInfo.setAppGitAPPSubProjectFolder(branchName);
|
||||
appInfo.setAppHomePage("https://www.winboll.cc/apks/index.php?project=WinBoLL");
|
||||
appInfo.setAppAPKName("WinBoLL");
|
||||
appInfo.setAppAPKFolderName("WinBoLL");
|
||||
LogUtils.d(TAG, "genDefaultAppInfo: 应用信息已生成");
|
||||
return appInfo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package cc.winboll.studio.winboll.activities;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
|
||||
import cc.winboll.studio.libaes.models.AESThemeBean;
|
||||
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
|
||||
import cc.winboll.studio.winboll.theme.WinBoLLThemeUtil;
|
||||
|
||||
/**
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2026/01/13 16:33
|
||||
* BaseWinBollActivity 【继承AppCompatActivity,保留核心能力,不额外暴露方法】
|
||||
* 继承链路:BaseWinBoLLActivity → AppCompatActivity → FragmentActivity,AppCompat能力天然继承可用
|
||||
*/
|
||||
public abstract class BaseWinBoLLActivity extends AppCompatActivity implements IWinBoLLActivity {
|
||||
public static final String TAG = "BaseWinBoLLActivity";
|
||||
|
||||
protected volatile AESThemeBean.ThemeType mThemeType;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
mThemeType = getThemeType();
|
||||
setThemeStyle();
|
||||
super.onCreate(savedInstanceState);
|
||||
WinBoLLActivityManager.getInstance().add(this);
|
||||
//ToastUtils.show(getTag() + ": onCreate");
|
||||
}
|
||||
|
||||
AESThemeBean.ThemeType getThemeType() {
|
||||
return WinBoLLThemeUtil.getThemeStyleType(WinBoLLThemeUtil.getThemeTypeID(getApplicationContext()));
|
||||
}
|
||||
|
||||
void setThemeStyle() {
|
||||
setTheme(WinBoLLThemeUtil.getThemeTypeID(getApplicationContext()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
WinBoLLActivityManager.getInstance().registeRemove(this);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
// 子类必须实现getTag(),确保唯一标识
|
||||
@Override
|
||||
public abstract String getTag();
|
||||
|
||||
public abstract Activity getActivity();
|
||||
}
|
||||
|
||||
@@ -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,59 @@
|
||||
package cc.winboll.studio.winboll.activities;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/03/25 11:46:40
|
||||
* @Describe 测试窗口2
|
||||
*/
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import cc.winboll.studio.winboll.R;
|
||||
import android.app.Activity;
|
||||
|
||||
public class New2Activity extends BaseWinBoLLActivity {
|
||||
|
||||
@Override
|
||||
public Activity getActivity() {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public static final String TAG = "New2Activity";
|
||||
|
||||
Toolbar mToolbar;
|
||||
|
||||
@Override
|
||||
public String getTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
//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);
|
||||
setSupportActionBar(mToolbar);
|
||||
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package cc.winboll.studio.winboll.activities;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/03/25 05:04:22
|
||||
* @Describe
|
||||
*/
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
|
||||
import cc.winboll.studio.winboll.R;
|
||||
import android.app.Activity;
|
||||
|
||||
public class NewActivity extends BaseWinBoLLActivity {
|
||||
|
||||
@Override
|
||||
public Activity getActivity() {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public static final String TAG = "NewActivity";
|
||||
|
||||
Toolbar mToolbar;
|
||||
//LogView mLogView;
|
||||
|
||||
@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);
|
||||
setSupportActionBar(mToolbar);
|
||||
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
package cc.winboll.studio.winboll.activities;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.MotionEvent;
|
||||
import android.widget.FrameLayout;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import cc.winboll.studio.libaes.utils.AESThemeUtil;
|
||||
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
|
||||
import cc.winboll.studio.winboll.R;
|
||||
|
||||
public class PatternLockActivity extends BaseWinBoLLActivity {
|
||||
|
||||
public static final String TAG = "PatternLockActivity";
|
||||
|
||||
private static final int DOT_RADIUS = 8;
|
||||
private static final int PATTERN_ERROR_DURATION = 1500;
|
||||
|
||||
private static final String PREFS_NAME = "pattern_lock_prefs";
|
||||
static final String KEY_LOCK_PATTERN = "lock_pattern";
|
||||
static final String KEY_ERROR_STATE = "error_state";
|
||||
private static final String KEY_ERROR_REPEAT_PATTERN = "error_repeat_pattern";
|
||||
|
||||
private boolean mIsInErrorState;
|
||||
private boolean mNeedRestart;
|
||||
private Handler mHandler;
|
||||
|
||||
private FrameLayout mContainer;
|
||||
private PatternView mPatternView;
|
||||
|
||||
public PatternLockActivity() {
|
||||
mHandler = new Handler(Looper.getMainLooper());
|
||||
}
|
||||
|
||||
PatternLockActivity(Context context) {
|
||||
mHandler = new Handler(Looper.getMainLooper());
|
||||
}
|
||||
|
||||
@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_pattern_lock);
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
getSupportActionBar().setSubtitle(TAG);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
toolbar.setNavigationOnClickListener(new android.view.View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(android.view.View v) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
mContainer = findViewById(R.id.container);
|
||||
mPatternView = new PatternView(this);
|
||||
mContainer.addView(mPatternView);
|
||||
mPatternView.invalidate();
|
||||
|
||||
mNeedRestart = false;
|
||||
boolean isEnoughPoints = savedInstanceIsEnoughPoints();
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
mIsInErrorState = savedInstanceState.getBoolean(KEY_ERROR_STATE, false);
|
||||
mNeedRestart = savedInstanceState.getBoolean(KEY_ERROR_REPEAT_PATTERN, false);
|
||||
}
|
||||
|
||||
if (mIsInErrorState) {
|
||||
mPatternView.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
boolean savedInstanceIsEnoughPoints() {
|
||||
int count = 0;
|
||||
if (mPatternView != null) {
|
||||
for (int i = 0; i < 9; i++) {
|
||||
if (mPatternView.mDotState[i] == 1) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return count >= 4 || count == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
if (mIsInErrorState) {
|
||||
outState.putBoolean(KEY_ERROR_STATE, mIsInErrorState);
|
||||
}
|
||||
if (mNeedRestart) {
|
||||
outState.putBoolean(KEY_ERROR_REPEAT_PATTERN, mNeedRestart);
|
||||
}
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
private void showErrorState() {
|
||||
mIsInErrorState = true;
|
||||
invalidatePattern();
|
||||
mHandler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mIsInErrorState = false;
|
||||
SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
|
||||
prefs.edit().putBoolean(KEY_ERROR_STATE, false).apply();
|
||||
invalidatePattern();
|
||||
if (mPatternView != null) mPatternView.invalidate();
|
||||
}
|
||||
}, PATTERN_ERROR_DURATION);
|
||||
}
|
||||
|
||||
private void clearErrorState() {
|
||||
mIsInErrorState = false;
|
||||
SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
|
||||
prefs.edit().putBoolean(KEY_ERROR_STATE, false).apply();
|
||||
invalidatePattern();
|
||||
if (mPatternView != null) mPatternView.invalidate();
|
||||
}
|
||||
|
||||
private void showErrorToast() {
|
||||
android.widget.Toast.makeText(this, "图案点数不足,请重新绘制",
|
||||
android.widget.Toast.LENGTH_SHORT).show();
|
||||
mNeedRestart = true;
|
||||
}
|
||||
|
||||
private void showSuccessDialog() {
|
||||
android.app.AlertDialog alertDialog = new android.app.AlertDialog.Builder(this)
|
||||
.setTitle("设置成功")
|
||||
.setMessage("图案密码已设置成功")
|
||||
.setPositiveButton("确定", new android.content.DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(android.content.DialogInterface dialog, int which) {
|
||||
finish();
|
||||
}
|
||||
})
|
||||
.setCancelable(false)
|
||||
.create();
|
||||
alertDialog.show();
|
||||
}
|
||||
|
||||
void finishWithRestart() {
|
||||
finish();
|
||||
}
|
||||
|
||||
private void invalidatePattern() {
|
||||
if (mPatternView != null) {
|
||||
mPatternView.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
class PatternView extends FrameLayout {
|
||||
int mPatternSize = 0;
|
||||
int MAX_DOT_COUNT = 9;
|
||||
int[] mDotX = new int[MAX_DOT_COUNT];
|
||||
int[] mDotY = new int[MAX_DOT_COUNT];
|
||||
int[] mDotState = new int[MAX_DOT_COUNT];
|
||||
Bitmap mDotBitmap;
|
||||
Paint mPaintConnector;
|
||||
Paint mPaintErrorBackground;
|
||||
int mDotCount = 0;
|
||||
|
||||
PatternView(Context context) {
|
||||
super(context);
|
||||
setBackgroundColor(Color.WHITE);
|
||||
for (int i = 0; i < MAX_DOT_COUNT; i++) {
|
||||
mDotX[i] = -1;
|
||||
mDotY[i] = -1;
|
||||
mDotState[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
if (w == 0 || h == 0) return;
|
||||
mPatternSize = w > h ? h : w;
|
||||
int grid = 3;
|
||||
int cell = mPatternSize / grid;
|
||||
|
||||
for (int i = 0; i < MAX_DOT_COUNT; i++) {
|
||||
mDotX[i] = (i % grid) * cell + cell / 2 - cell / 24;
|
||||
mDotY[i] = (i / grid) * cell + cell / 2 - cell / 24;
|
||||
mDotState[i] = 0;
|
||||
}
|
||||
|
||||
if (mDotBitmap == null) {
|
||||
mDotBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.dot_darkgreen_dark);
|
||||
}
|
||||
if (mPaintConnector == null) {
|
||||
mPaintConnector = new Paint(Paint.FILTER_BITMAP_FLAG);
|
||||
mPaintConnector.setColor(-0xFF006400);
|
||||
}
|
||||
if (mPaintErrorBackground == null) {
|
||||
mPaintErrorBackground = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mPaintErrorBackground.setColor(Color.RED);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if (mDotCount > 0) return false;
|
||||
|
||||
switch (event.getActionMasked()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
invalidate();
|
||||
return true;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
float x = event.getX();
|
||||
float y = event.getY();
|
||||
|
||||
for (int i = 0; i < MAX_DOT_COUNT; i++) {
|
||||
int dx = (int) Math.abs(x - mDotX[i]);
|
||||
int dy = (int) Math.abs(y - mDotY[i]);
|
||||
if (dx <= DOT_RADIUS && dy <= DOT_RADIUS && mDotState[i] == 0) {
|
||||
mDotState[i] = 1;
|
||||
mDotCount++;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < mDotCount - 1; i++) {
|
||||
int a = -1, b = -1;
|
||||
for (int k = 0; k < MAX_DOT_COUNT; k++) {
|
||||
if (mDotState[k] == 1) {
|
||||
if (a < 0) a = k;
|
||||
else b = k;
|
||||
}
|
||||
}
|
||||
if (a >= 0 && b >= 0) {
|
||||
a = Math.min(a, b);
|
||||
b = Math.max(a, b);
|
||||
}
|
||||
if (mDotState[a] == 1 && mDotState[b] == 1) {
|
||||
int dx = mDotX[b] - mDotX[a];
|
||||
int dy = mDotY[b] - mDotY[a];
|
||||
if ((Math.abs(dx) <= 1 && Math.abs(dy) <= 1) ||
|
||||
(Math.abs(dx) <= 2 && Math.abs(dy) <= 1)) {
|
||||
if (mDotState[b] == 1) {
|
||||
for (int k = a + 1; k < b; k++) {
|
||||
if (mDotState[k] == 0) {
|
||||
mDotState[k] = 1;
|
||||
}
|
||||
}
|
||||
mDotCount += (b - a - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
invalidate();
|
||||
return true;
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
if (mDotCount < 4) {
|
||||
showErrorState();
|
||||
showErrorToast();
|
||||
}
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDraw(Canvas canvas) {
|
||||
if (mPatternSize == 0) return;
|
||||
|
||||
int activeCount = 0;
|
||||
for (int i = 0; i < MAX_DOT_COUNT; i++) {
|
||||
if (mDotState[i] == 1) activeCount++;
|
||||
}
|
||||
|
||||
if (activeCount == 0) {
|
||||
mPaintErrorBackground.setAlpha(50);
|
||||
} else {
|
||||
mPaintErrorBackground.setAlpha(mIsInErrorState ? 80 : 60);
|
||||
}
|
||||
|
||||
canvas.clipRect(0, 0, mPatternSize * 80 / 100, mPatternSize * 80 / 100);
|
||||
|
||||
canvas.drawRect(0, 0, mPatternSize, mPatternSize, mPaintErrorBackground);
|
||||
|
||||
if (mDotBitmap != null) {
|
||||
for (int i = 0; i < MAX_DOT_COUNT; i++) {
|
||||
if (mDotState[i] == 1) {
|
||||
canvas.drawBitmap(mDotBitmap, mDotX[i], mDotY[i], mPaintConnector);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package cc.winboll.studio.winboll.activities;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import cc.winboll.studio.libaes.utils.AESThemeUtil;
|
||||
import cc.winboll.studio.winboll.R;
|
||||
import android.app.Activity;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/12/05 18:48
|
||||
* @Describe Settings Activity
|
||||
*/
|
||||
public class SettingsActivity extends BaseWinBoLLActivity {
|
||||
|
||||
@Override
|
||||
public Activity getActivity() {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public static final String TAG = "SettingsActivity";
|
||||
|
||||
@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,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,237 @@
|
||||
package cc.winboll.studio.winboll.applications;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.winboll.R;
|
||||
import cc.winboll.studio.winboll.models.TermuxButtonManager;
|
||||
import cc.winboll.studio.winboll.models.TermuxButtonModel;
|
||||
import cc.winboll.studio.winboll.termux.TermuxCommandExecutor;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class MyTermuxActivity extends AppCompatActivity {
|
||||
|
||||
public static final String TAG = "MyTermuxActivity";
|
||||
|
||||
private Toolbar mToolbar;
|
||||
private ListView mListView;
|
||||
private Button mBtnAdd;
|
||||
private ButtonAdapter mAdapter;
|
||||
private ArrayList<TermuxButtonModel> mButtonList;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_my_termux);
|
||||
|
||||
initToolbar();
|
||||
initListView();
|
||||
initAddButton();
|
||||
refreshList();
|
||||
}
|
||||
|
||||
private void initToolbar() {
|
||||
mToolbar = findViewById(R.id.toolbar);
|
||||
if (mToolbar != null) {
|
||||
setSupportActionBar(mToolbar);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void initListView() {
|
||||
mListView = findViewById(R.id.list_termux_buttons);
|
||||
mButtonList = new ArrayList<TermuxButtonModel>();
|
||||
mAdapter = new ButtonAdapter();
|
||||
mListView.setAdapter(mAdapter);
|
||||
|
||||
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
TermuxButtonModel model = mButtonList.get(position);
|
||||
TermuxCommandExecutor.openTermuxBash(MyTermuxActivity.this,
|
||||
model.getExeCommand(), model.getWorkDir());
|
||||
}
|
||||
});
|
||||
|
||||
mListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
|
||||
@Override
|
||||
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
showContextMenu(position);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initAddButton() {
|
||||
mBtnAdd = findViewById(R.id.btn_add_termux_button);
|
||||
mBtnAdd.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
showButtonDialog(-1, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void refreshList() {
|
||||
mButtonList.clear();
|
||||
ArrayList<TermuxButtonModel> loaded = TermuxButtonManager.loadButtons(this);
|
||||
if (loaded != null) {
|
||||
mButtonList.addAll(loaded);
|
||||
}
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void showContextMenu(final int position) {
|
||||
final TermuxButtonModel model = mButtonList.get(position);
|
||||
String[] items = new String[]{
|
||||
getString(R.string.menu_execute),
|
||||
getString(R.string.menu_edit),
|
||||
getString(R.string.menu_delete),
|
||||
getString(R.string.menu_cancel)
|
||||
};
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(model.getButtonName())
|
||||
.setItems(items, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (which == 0) {
|
||||
TermuxCommandExecutor.openTermuxBash(MyTermuxActivity.this,
|
||||
model.getExeCommand(), model.getWorkDir());
|
||||
} else if (which == 1) {
|
||||
showButtonDialog(position, model);
|
||||
} else if (which == 2) {
|
||||
showDeleteConfirmDialog(position);
|
||||
}
|
||||
}
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showDeleteConfirmDialog(final int position) {
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(getString(R.string.dialog_delete_title))
|
||||
.setMessage(getString(R.string.dialog_delete_message) + mButtonList.get(position).getButtonName())
|
||||
.setPositiveButton(getString(R.string.dialog_confirm), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
TermuxButtonManager.deleteButton(MyTermuxActivity.this, mButtonList, position);
|
||||
refreshList();
|
||||
Toast.makeText(MyTermuxActivity.this, R.string.toast_deleted, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(getString(R.string.dialog_cancel), null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showButtonDialog(final int index, final TermuxButtonModel model) {
|
||||
final boolean isEdit = (model != null);
|
||||
|
||||
LinearLayout layout = new LinearLayout(this);
|
||||
layout.setOrientation(LinearLayout.VERTICAL);
|
||||
layout.setPadding(40, 20, 40, 20);
|
||||
|
||||
final EditText etName = new EditText(this);
|
||||
etName.setHint(R.string.hint_button_name);
|
||||
if (model != null) {
|
||||
etName.setText(model.getButtonName());
|
||||
}
|
||||
layout.addView(etName);
|
||||
|
||||
final EditText etCommand = new EditText(this);
|
||||
etCommand.setHint(R.string.hint_exe_command);
|
||||
if (model != null) {
|
||||
etCommand.setText(model.getExeCommand());
|
||||
}
|
||||
layout.addView(etCommand);
|
||||
|
||||
final EditText etWorkDir = new EditText(this);
|
||||
etWorkDir.setHint(R.string.hint_work_dir);
|
||||
if (model != null) {
|
||||
etWorkDir.setText(model.getWorkDir());
|
||||
}
|
||||
layout.addView(etWorkDir);
|
||||
|
||||
int titleResId = isEdit ? R.string.dialog_edit_title : R.string.dialog_add_title;
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(titleResId)
|
||||
.setView(layout)
|
||||
.setPositiveButton(getString(R.string.dialog_save), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
String name = etName.getText().toString().trim();
|
||||
String command = etCommand.getText().toString().trim();
|
||||
String workDir = etWorkDir.getText().toString().trim();
|
||||
if (name.isEmpty() || command.isEmpty()) {
|
||||
Toast.makeText(MyTermuxActivity.this, R.string.toast_fields_required, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
TermuxButtonModel newModel = new TermuxButtonModel();
|
||||
newModel.setButtonName(name);
|
||||
newModel.setExeCommand(command);
|
||||
newModel.setWorkDir(workDir);
|
||||
if (isEdit) {
|
||||
TermuxButtonManager.updateButton(MyTermuxActivity.this, mButtonList, index, newModel);
|
||||
} else {
|
||||
TermuxButtonManager.addButton(MyTermuxActivity.this, mButtonList, newModel);
|
||||
}
|
||||
refreshList();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(getString(R.string.dialog_cancel), null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private class ButtonAdapter extends BaseAdapter {
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mButtonList.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
return mButtonList.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
TextView tv;
|
||||
if (convertView == null) {
|
||||
tv = new TextView(MyTermuxActivity.this);
|
||||
tv.setPadding(30, 20, 30, 20);
|
||||
tv.setTextSize(16);
|
||||
tv.setMinHeight(80);
|
||||
} else {
|
||||
tv = (TextView) convertView;
|
||||
}
|
||||
|
||||
TermuxButtonModel model = mButtonList.get(position);
|
||||
tv.setText(model.getButtonName() + "\n" + model.getExeCommand());
|
||||
tv.setTextColor(getResources().getColor(android.R.color.white));
|
||||
return tv;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,350 @@
|
||||
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;
|
||||
// 打开外部应用传入的 URL
|
||||
public static final int MSG_OPEN_URL = 1003;
|
||||
// 自定义Handler(接收应用内其他页面发送的消息)
|
||||
private Handler mBrowserHandler;
|
||||
|
||||
// 单例创建方法(Java 7 显式工厂模式)
|
||||
public static BrowserFragment newInstance() {
|
||||
return new BrowserFragment();
|
||||
}
|
||||
|
||||
// 创建带初始URL的实例(供外部应用调用时使用)
|
||||
public static BrowserFragment newInstance(String initialUrl) {
|
||||
BrowserFragment fragment = new BrowserFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putString("initial_url", initialUrl);
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
// 检查是否有外部传入的初始 URL
|
||||
String initialUrl = null;
|
||||
if (getArguments() != null) {
|
||||
initialUrl = getArguments().getString("initial_url");
|
||||
}
|
||||
|
||||
if (initialUrl != null && !initialUrl.isEmpty()) {
|
||||
// 使用外部传入的 URL
|
||||
mWinBoLLView.loadUrlSafe(initialUrl);
|
||||
mEtUrl.setText(initialUrl);
|
||||
} else {
|
||||
// 预加载默认页面(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;
|
||||
case MSG_OPEN_URL:
|
||||
String openUrl = (String) msg.obj;
|
||||
if (openUrl != null && !openUrl.isEmpty()) {
|
||||
mWinBoLLView.loadUrlSafe(openUrl);
|
||||
mEtUrl.setText(openUrl);
|
||||
}
|
||||
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,14 @@
|
||||
package cc.winboll.studio.winboll.models;
|
||||
|
||||
/**
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2026/03/15 08:46
|
||||
*/
|
||||
public class NfcTermuxCmd {
|
||||
public String script; // 要执行的预制脚本名(如 auth.sh)
|
||||
public String[] args; // 脚本参数
|
||||
public String workDir; // 工作目录
|
||||
public boolean background; // 是否后台执行
|
||||
public String resultDir; // 结果输出目录(可为 null)
|
||||
}
|
||||
|
||||
@@ -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,37 @@
|
||||
package cc.winboll.studio.winboll.models;
|
||||
|
||||
import android.content.Context;
|
||||
import cc.winboll.studio.libappbase.BaseBean;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class TermuxButtonManager {
|
||||
|
||||
public static ArrayList<TermuxButtonModel> loadButtons(Context context) {
|
||||
ArrayList<TermuxButtonModel> list = new ArrayList<TermuxButtonModel>();
|
||||
BaseBean.loadBeanList(context, list, TermuxButtonModel.class);
|
||||
return list;
|
||||
}
|
||||
|
||||
public static boolean saveButtons(Context context, ArrayList<TermuxButtonModel> list) {
|
||||
return BaseBean.saveBeanList(context, list, TermuxButtonModel.class);
|
||||
}
|
||||
|
||||
public static void addButton(Context context, ArrayList<TermuxButtonModel> list, TermuxButtonModel button) {
|
||||
list.add(button);
|
||||
saveButtons(context, list);
|
||||
}
|
||||
|
||||
public static void updateButton(Context context, ArrayList<TermuxButtonModel> list, int index, TermuxButtonModel button) {
|
||||
if (index >= 0 && index < list.size()) {
|
||||
list.set(index, button);
|
||||
saveButtons(context, list);
|
||||
}
|
||||
}
|
||||
|
||||
public static void deleteButton(Context context, ArrayList<TermuxButtonModel> list, int index) {
|
||||
if (index >= 0 && index < list.size()) {
|
||||
list.remove(index);
|
||||
saveButtons(context, list);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package cc.winboll.studio.winboll.models;
|
||||
|
||||
import android.util.JsonReader;
|
||||
import android.util.JsonWriter;
|
||||
import cc.winboll.studio.libappbase.BaseBean;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2026/04/30 10:47
|
||||
*/
|
||||
public class TermuxButtonModel extends BaseBean {
|
||||
|
||||
public static final String TAG = "TermuxButtonModel";
|
||||
|
||||
String buttonName;
|
||||
String exeCommand;
|
||||
String workDir;
|
||||
|
||||
// 已修改:isCommit 改为规范过去式命名 isCommitted
|
||||
boolean isCommitted;
|
||||
String commitTitle;
|
||||
String commitInfo;
|
||||
|
||||
public TermuxButtonModel() {
|
||||
this.buttonName = "";
|
||||
this.exeCommand = "";
|
||||
this.workDir = "";
|
||||
// 默认初始化
|
||||
this.isCommitted = false;
|
||||
this.commitTitle = "";
|
||||
this.commitInfo = "";
|
||||
}
|
||||
|
||||
public void setButtonName(String buttonName) {
|
||||
this.buttonName = buttonName;
|
||||
}
|
||||
|
||||
public String getButtonName() {
|
||||
return buttonName;
|
||||
}
|
||||
|
||||
public void setExeCommand(String exeCommand) {
|
||||
this.exeCommand = exeCommand;
|
||||
}
|
||||
|
||||
public String getExeCommand() {
|
||||
return exeCommand;
|
||||
}
|
||||
|
||||
public void setWorkDir(String workDir) {
|
||||
this.workDir = workDir;
|
||||
}
|
||||
|
||||
public String getWorkDir() {
|
||||
return workDir;
|
||||
}
|
||||
|
||||
// ========== 已修改 对应 isCommitted 完整 Get & Set ==========
|
||||
public boolean isCommitted() {
|
||||
return isCommitted;
|
||||
}
|
||||
|
||||
public void setCommitted(boolean committed) {
|
||||
isCommitted = committed;
|
||||
}
|
||||
|
||||
public String getCommitTitle() {
|
||||
return commitTitle;
|
||||
}
|
||||
|
||||
public void setCommitTitle(String commitTitle) {
|
||||
this.commitTitle = commitTitle;
|
||||
}
|
||||
|
||||
public String getCommitInfo() {
|
||||
return commitInfo;
|
||||
}
|
||||
|
||||
public void setCommitInfo(String commitInfo) {
|
||||
this.commitInfo = commitInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return TermuxButtonModel.class.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
|
||||
super.writeThisToJsonWriter(jsonWriter);
|
||||
jsonWriter.name("buttonName").value(getButtonName());
|
||||
jsonWriter.name("exeCommand").value(getExeCommand());
|
||||
jsonWriter.name("workDir").value(getWorkDir());
|
||||
|
||||
// JSON写入同步修改
|
||||
jsonWriter.name("isCommitted").value(isCommitted());
|
||||
jsonWriter.name("commitTitle").value(getCommitTitle());
|
||||
jsonWriter.name("commitInfo").value(getCommitInfo());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
|
||||
if (super.initObjectsFromJsonReader(jsonReader, name)) {
|
||||
return true;
|
||||
} else {
|
||||
if (name.equals("buttonName")) {
|
||||
setButtonName(jsonReader.nextString());
|
||||
} else if (name.equals("exeCommand")) {
|
||||
setExeCommand(jsonReader.nextString());
|
||||
} else if (name.equals("workDir")) {
|
||||
setWorkDir(jsonReader.nextString());
|
||||
}
|
||||
// JSON解析字段同步修改
|
||||
else if (name.equals("isCommitted")) {
|
||||
setCommitted(jsonReader.nextBoolean());
|
||||
} else if (name.equals("commitTitle")) {
|
||||
setCommitTitle(jsonReader.nextString());
|
||||
} else if (name.equals("commitInfo")) {
|
||||
setCommitInfo(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();
|
||||
}
|
||||
}
|
||||
jsonReader.endObject();
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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,241 @@
|
||||
/*
|
||||
* 源码说明与描述:
|
||||
* NFC 与 Termux 桥接活动,用于接收外部应用(包调用)传递的 JSON 指令并执行 Termux 脚本命令。
|
||||
* 支持 ACTION_BUILD(后台执行)与 ACTION_BUILD_VIEW(终端窗口唤起)两种动作。
|
||||
*
|
||||
* 作者:豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* 创建时间:2025-03-15 14:00:00
|
||||
* 最后编辑时间:2026-03-16 10:00:00
|
||||
*/
|
||||
package cc.winboll.studio.winboll.termux;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.widget.Toast;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import cc.winboll.studio.winboll.models.NfcTermuxCmd;
|
||||
import com.google.gson.Gson;
|
||||
|
||||
public class NfcTermuxBridgeActivity extends Activity {
|
||||
|
||||
// ========================= 常量与静态属性 =========================
|
||||
public static final String TAG = "NfcTermuxBridgeActivity";
|
||||
|
||||
// 外部应用调用时使用的 Action 常量
|
||||
public static final String ACTION_BUILD = "cc.winboll.studio.winboll.termux.NfcTermuxBridgeActivity.ACTION_BUILD";
|
||||
|
||||
private static final Gson GSON = new Gson();
|
||||
|
||||
// ========================= 生命周期方法 =========================
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
LogUtils.d(TAG, "onCreate() 调用,savedInstanceState: " + (savedInstanceState != null ? "非空" : "空"));
|
||||
dispatchIntent(getIntent());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
LogUtils.d(TAG, "onNewIntent() 调用,intent: " + (intent != null ? intent.toString() : "null"));
|
||||
if (intent != null) {
|
||||
LogUtils.d(TAG, "onNewIntent() action: " + intent.getAction());
|
||||
LogUtils.d(TAG, "onNewIntent() data: " + intent.getDataString());
|
||||
LogUtils.d(TAG, "onNewIntent() extras: " + intent.getExtras());
|
||||
LogUtils.d(TAG, "onNewIntent() flags: " + intent.getFlags());
|
||||
LogUtils.d(TAG, "onNewIntent() component: " + intent.getComponent());
|
||||
} else {
|
||||
LogUtils.w(TAG, "onNewIntent() intent is null");
|
||||
}
|
||||
dispatchIntent(intent);
|
||||
}
|
||||
|
||||
// ========================= 统一 Intent 分发(合并去重) =========================
|
||||
/**
|
||||
* 统一分发 Intent,根据 Action 路由到不同业务逻辑
|
||||
* @param intent 外部传入的 Intent
|
||||
*/
|
||||
private void dispatchIntent(Intent intent) {
|
||||
LogUtils.d(TAG, "dispatchIntent() 分发 intent");
|
||||
if (intent == null) {
|
||||
LogUtils.w(TAG, "dispatchIntent() intent is null");
|
||||
return;
|
||||
}
|
||||
|
||||
String action = intent.getAction();
|
||||
if (ACTION_BUILD.equals(action)) {
|
||||
ToastUtils.show("ACTION_BUILD 命中");
|
||||
onOpenTermuxProjectBuild(intent);
|
||||
} else {
|
||||
LogUtils.w(TAG, "dispatchIntent() 未知 Action: " + action);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
// ========================= 核心业务方法 =========================
|
||||
/**
|
||||
* 处理 ACTION_BUILD 动作:后台执行 NFC 指令
|
||||
*/
|
||||
// private void handleNfcIntent(Intent intent) {
|
||||
// LogUtils.d(TAG, "handleNfcIntent() 调用");
|
||||
// if (intent == null) {
|
||||
// LogUtils.w(TAG, "handleNfcIntent() intent 为空");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// try {
|
||||
// String json = intent.getStringExtra(Intent.EXTRA_TEXT);
|
||||
// LogUtils.d(TAG, "handleNfcIntent() json: " + json);
|
||||
//
|
||||
// if (json == null || json.isEmpty()) {
|
||||
// LogUtils.e(TAG, "handleNfcIntent() 指令为空");
|
||||
// showToast("指令为空");
|
||||
// finish();
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// NfcTermuxCmd cmd = GSON.fromJson(json, NfcTermuxCmd.class);
|
||||
// LogUtils.d(TAG, "handleNfcIntent() cmd: " + cmd);
|
||||
//
|
||||
// if (cmd.script == null || cmd.script.isEmpty()) {
|
||||
// LogUtils.e(TAG, "handleNfcIntent() script 为空");
|
||||
// showToast("script 不能为空");
|
||||
// finish();
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// //String scriptPath = "/data/data/com.termux/files/home/TermuxWorkSpaces/BashShells/AutoNFC/" + cmd.script;
|
||||
// String scriptPath = "/data/data/com.termux/files/home/TermuxWorkSpaces/BashShells/AutoNFC/" + "BuildWinBoLLProject.sh";
|
||||
// LogUtils.d(TAG, "handleNfcIntent() 脚本路径: " + scriptPath);
|
||||
//
|
||||
// boolean success = TermuxCommandExecutor.executeCommand(
|
||||
// this, scriptPath, cmd.args, cmd.workDir, cmd.background, cmd.resultDir
|
||||
// );
|
||||
// LogUtils.d(TAG, "handleNfcIntent() 执行结果: " + success);
|
||||
//
|
||||
// if (success) {
|
||||
// showToast("指令已发送: " + cmd.script);
|
||||
// LogUtils.i(TAG, "执行成功: " + scriptPath);
|
||||
// } else {
|
||||
// showToast("指令发送失败");
|
||||
// LogUtils.e(TAG, "执行失败");
|
||||
// }
|
||||
//
|
||||
// } catch (Exception e) {
|
||||
// LogUtils.e(TAG, "handleNfcIntent() 异常: " + e.getMessage(), e);
|
||||
// showToast("解析失败");
|
||||
// } finally {
|
||||
// finish();
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* 处理 ACTION_BUILD_VIEW 动作:唤起 Termux 窗口执行命令
|
||||
*/
|
||||
public void onOpenTermuxProjectBuild(Intent intent) {
|
||||
LogUtils.d(TAG, "onOpenTermuxProjectBuildView() 调用");
|
||||
if (intent == null) {
|
||||
LogUtils.w(TAG, "onOpenTermuxProjectBuildView() intent 为空");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
String json = intent.getStringExtra(Intent.EXTRA_TEXT);
|
||||
LogUtils.d(TAG, "onOpenTermuxProjectBuildView() json: " + json);
|
||||
|
||||
if (json == null || json.isEmpty()) {
|
||||
LogUtils.e(TAG, "onOpenTermuxProjectBuildView() 指令为空");
|
||||
showToast("指令为空");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
NfcTermuxCmd cmd = GSON.fromJson(json, NfcTermuxCmd.class);
|
||||
LogUtils.d(TAG, "onOpenTermuxProjectBuildView() cmd: " + cmd);
|
||||
|
||||
if (cmd.script == null || cmd.script.isEmpty()) {
|
||||
LogUtils.e(TAG, "onOpenTermuxProjectBuildView() script 为空");
|
||||
showToast("script 不能为空");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
StringBuilder targetCmd = new StringBuilder();
|
||||
String nfcScriptFolder = "/data/data/com.termux/files/home/TermuxWorkSpaces/BashShells/AutoNFC/";
|
||||
targetCmd.append("cd " + nfcScriptFolder + " && ");
|
||||
//targetCmd.append("stdbuf -o0 -e0 -i0 bash ").append(cmd.script).append(" ");
|
||||
targetCmd.append("stdbuf -o0 -e0 -i0 bash ").append("BuildWinBoLLProject.sh").append(" ");
|
||||
if (cmd.args != null) {
|
||||
for (String arg : cmd.args) {
|
||||
targetCmd.append(arg).append(" ");
|
||||
}
|
||||
}
|
||||
|
||||
LogUtils.d(TAG, "onOpenTermuxProjectBuildView() 命令: " + targetCmd);
|
||||
boolean cmdSuccess = TermuxCommandExecutor.executeTerminalCommand(this, targetCmd.toString());
|
||||
LogUtils.d(TAG, "onOpenTermuxProjectBuildView() 执行结果: " + cmdSuccess);
|
||||
|
||||
if (cmdSuccess) {
|
||||
showToast("指令已发送: " + cmd.script);
|
||||
} else {
|
||||
showToast("指令发送失败");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "onOpenTermuxProjectBuildView() 异常: " + e.getMessage(), e);
|
||||
showToast("解析失败");
|
||||
} finally {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
// ========================= 公共静态测试方法 =========================
|
||||
/**
|
||||
* 内部测试方法:发送 ACTION_BUILD 指令
|
||||
*/
|
||||
public static void testCommand(Context context) {
|
||||
LogUtils.d(TAG, "testCommand()");
|
||||
try {
|
||||
String testJson = "{\"script\":\"BuildWinBoLLProject.sh\",\"args\":[\"DebugTemp\"],\"workDir\":null,\"background\":true,\"resultDir\":null}";
|
||||
Intent intent = new Intent(context, NfcTermuxBridgeActivity.class);
|
||||
intent.setAction(ACTION_BUILD); // 指定 Action
|
||||
intent.putExtra(Intent.EXTRA_TEXT, testJson);
|
||||
context.startActivity(intent);
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "testCommand() 失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部测试方法:发送 ACTION_BUILD_VIEW 指令
|
||||
*/
|
||||
// public static void testViewCommand(Context context) {
|
||||
// LogUtils.d(TAG, "testViewCommand()");
|
||||
// try {
|
||||
// String testJson = "{\"script\":\"BuildWinBoLLProjectView.sh\",\"args\":[\"DebugTemp\"],\"workDir\":null,\"background\":true,\"resultDir\":null}";
|
||||
// Intent intent = new Intent(context, NfcTermuxBridgeActivity.class);
|
||||
// intent.setAction(ACTION_BUILD_VIEW); // 指定 Action
|
||||
// intent.putExtra(Intent.EXTRA_TEXT, testJson);
|
||||
// context.startActivity(intent);
|
||||
// } catch (Exception e) {
|
||||
// LogUtils.e(TAG, "testViewCommand() 失败: " + e.getMessage());
|
||||
// }
|
||||
// }
|
||||
|
||||
// ========================= 工具方法 =========================
|
||||
/**
|
||||
* 统一显示 Toast,确保在主线程调用
|
||||
*/
|
||||
private void showToast(final String message) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(NfcTermuxBridgeActivity.this, message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
package cc.winboll.studio.winboll.termux;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import cc.winboll.studio.libappbase.LogUtils; // 替换 Log 为 LogUtils(与 Activity 一致)
|
||||
import com.termux.shared.termux.TermuxConstants;
|
||||
import com.termux.shared.shell.command.ExecutionCommand.Runner;
|
||||
|
||||
/**
|
||||
* Termux 命令调用工具类(基于 RunCommandService 原型封装)
|
||||
* 用于向 Termux 发送命令执行请求,兼容 Termux RUN_COMMAND Intent 规范
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2026/01/19 16:30:00
|
||||
* @LastEditTime 2026/01/20 10:15:00
|
||||
*/
|
||||
public class TermuxCommandExecutor {
|
||||
private static final String TAG = "TermuxCommandExecutor";
|
||||
// 核心修复:Termux 官方包名(无 .app 后缀)
|
||||
private static final String TERMUX_PACKAGE_NAME = "com.termux";
|
||||
// Termux RunCommandService 完整类名(包名+类名)
|
||||
private static final String TERMUX_RUN_CMD_SERVICE_CLASS = "com.termux.app.RunCommandService";
|
||||
private static final String TERMUX_RUN_CMD_ACTION = TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.ACTION_RUN_COMMAND;
|
||||
private static final String TERMUX_HOME_PATH = "/data/data/com.termux/files/home";
|
||||
|
||||
/**
|
||||
* 执行 Termux 命令(核心方法)
|
||||
* @param context 上下文(如 Activity、Service)
|
||||
* @param command 要执行的命令路径(如 "/bin/ls"、"/data/data/com.termux/files/usr/bin/bash")
|
||||
* @param args 命令参数(如 ["-l", "/data/data/com.termux/files/home"])
|
||||
* @param workDir 工作目录(可为 null,默认 Termux 主目录)
|
||||
* @param isBackground 是否后台执行(true=后台,false=终端会话执行)
|
||||
* @param resultDir 命令结果输出目录(可为 null,不输出到文件)
|
||||
* @return 是否成功发送命令请求
|
||||
*/
|
||||
public static boolean executeCommand(Context context, String command, String[] args, String workDir, boolean isBackground, String resultDir) {
|
||||
// 1. 校验上下文和命令合法性
|
||||
if (context == null || command == null || command.isEmpty()) {
|
||||
LogUtils.e(TAG, "执行命令失败:上下文或命令为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 校验 Termux 是否安装(新增:提前校验,避免白跑流程)
|
||||
if (!isTermuxInstalled(context)) {
|
||||
LogUtils.e(TAG, "执行命令失败:Termux 未安装");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 创建 Intent 并设置目标 Service
|
||||
Intent intent = new Intent(TERMUX_RUN_CMD_ACTION);
|
||||
intent.setClassName(TERMUX_PACKAGE_NAME, TERMUX_RUN_CMD_SERVICE_CLASS); // 用正确包名
|
||||
intent.setPackage(TERMUX_PACKAGE_NAME); // 明确包名,避免歧义
|
||||
|
||||
// 4. 设置核心命令参数(遵循 Termux RunCommandService 规范)
|
||||
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_COMMAND_PATH, command);
|
||||
if (args != null && args.length > 0) {
|
||||
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_ARGUMENTS, args);
|
||||
LogUtils.d(TAG, "命令参数:" + String.join(",", args));
|
||||
}
|
||||
if (workDir != null && !workDir.isEmpty()) {
|
||||
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_WORKDIR, workDir);
|
||||
LogUtils.d(TAG, "工作目录:" + workDir);
|
||||
}
|
||||
|
||||
// 5. 设置执行模式(后台/终端会话)
|
||||
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_BACKGROUND, isBackground);
|
||||
String runner = isBackground ? Runner.APP_SHELL.getName() : Runner.TERMINAL_SESSION.getName();
|
||||
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_RUNNER, runner);
|
||||
LogUtils.d(TAG, "执行模式:" + (isBackground ? "后台" : "终端会话") + ",Runner:" + runner);
|
||||
|
||||
// 6. 设置命令结果输出(可选,输出到文件)
|
||||
if (resultDir != null && !resultDir.isEmpty()) {
|
||||
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_RESULT_DIRECTORY, resultDir);
|
||||
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_RESULT_SINGLE_FILE, true);
|
||||
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_RESULT_FILE_BASENAME, "authcenter_cmd_result");
|
||||
LogUtils.d(TAG, "结果输出目录:" + resultDir);
|
||||
}
|
||||
|
||||
// 7. 允许替换参数中的逗号替代字符
|
||||
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_REPLACE_COMMA_ALTERNATIVE_CHARS_IN_ARGUMENTS, true);
|
||||
|
||||
// 8. 发送请求(区分 Android O 及以上的前台服务)
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
context.startForegroundService(intent);
|
||||
LogUtils.d(TAG, "Android O+ 启动前台服务发送命令");
|
||||
} else {
|
||||
context.startService(intent);
|
||||
LogUtils.d(TAG, "启动普通服务发送命令");
|
||||
}
|
||||
LogUtils.i(TAG, "命令发送成功:command=" + command);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "命令发送失败:" + e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 简化方法:执行 Termux 终端命令(默认工作目录,终端会话执行)
|
||||
* @param context 上下文
|
||||
* @param command 命令(如 "ls -l /home"、"echo 'hello termux'")
|
||||
* @return 是否成功发送
|
||||
*/
|
||||
public static boolean executeTerminalCommand(Context context, String command) {
|
||||
LogUtils.d(TAG, "调用 executeTerminalCommand,命令:" + command);
|
||||
if (command == null || command.isEmpty()) {
|
||||
LogUtils.e(TAG, "命令为空,执行失败");
|
||||
return false;
|
||||
}
|
||||
// 通过 bash 执行任意终端命令
|
||||
String[] args = {"-c", command};
|
||||
return executeCommand(
|
||||
context,
|
||||
"/data/data/com.termux/files/usr/bin/bash", // Termux 默认 bash 路径(正确)
|
||||
args,
|
||||
TERMUX_HOME_PATH, // 默认工作目录
|
||||
false, // 终端会话执行(可见)
|
||||
null // 不输出到文件
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 简化方法:后台执行 Termux 命令(无输出文件)
|
||||
* @param context 上下文
|
||||
* @param command 命令路径
|
||||
* @param args 命令参数
|
||||
* @return 是否成功发送
|
||||
*/
|
||||
public static boolean executeBackgroundCommand(Context context, String command, String[] args) {
|
||||
LogUtils.d(TAG, "调用 executeBackgroundCommand,command=" + command);
|
||||
return executeCommand(
|
||||
context,
|
||||
command,
|
||||
args,
|
||||
null,
|
||||
true, // 后台执行
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验 Termux 是否安装(修复核心错误)
|
||||
* @param context 上下文
|
||||
* @return Termux 是否已安装
|
||||
*/
|
||||
public static boolean isTermuxInstalled(Context context) {
|
||||
LogUtils.d(TAG, "校验 Termux 是否安装,包名:" + TERMUX_PACKAGE_NAME);
|
||||
if (context == null) {
|
||||
LogUtils.e(TAG, "校验失败:上下文为空");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
// 用正确的 Termux 包名查询(com.termux)
|
||||
context.getPackageManager().getPackageInfo(TERMUX_PACKAGE_NAME, PackageManager.GET_ACTIVITIES);
|
||||
LogUtils.d(TAG, "Termux 已安装");
|
||||
return true;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
LogUtils.w(TAG, "Termux 未安装:" + e.getMessage());
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "校验 Termux 安装状态异常:" + e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验 Termux 是否允许外部应用调用
|
||||
* @return 校验提示信息
|
||||
*/
|
||||
public static String checkTermuxExternalAppPermission() {
|
||||
String tip = "请确保 Termux 已开启「允许外部应用调用」权限:\n1. 打开 Termux 输入:termux-setup-storage\n2. 编辑配置文件:echo \"allow-external-apps = true\" > ~/.termux/termux.properties\n3. 重启 Termux 生效";
|
||||
LogUtils.d(TAG, "外部应用调用权限提示:" + tip);
|
||||
return tip;
|
||||
}
|
||||
|
||||
public static boolean openTermuxBash(Context context, String command) {
|
||||
return openTermuxBash(context, command, "~");
|
||||
}
|
||||
|
||||
public static boolean openTermuxBash(Context context, String command, String workDir) {
|
||||
LogUtils.d(TAG, "openTermuxBash() 按钮点击,执行Gradle命令(实时输出)");
|
||||
|
||||
// 1. 校验Termux是否安装
|
||||
if (!TermuxCommandExecutor.isTermuxInstalled(context)) {
|
||||
LogUtils.e(TAG, "openTermuxBash() 错误:未安装Termux应用");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 定义核心路径(确保路径与Termux中一致)
|
||||
String projectPath = TERMUX_HOME_PATH;
|
||||
if (workDir.startsWith("~") || workDir.startsWith(".")) {
|
||||
projectPath = TERMUX_HOME_PATH + "/" + workDir.substring(1);
|
||||
}
|
||||
|
||||
// 3. 构造命令(核心:用stdbuf禁用缓冲,实现实时输出)
|
||||
String targetCmd = "";
|
||||
// 步骤1:进入项目目录(不存在则创建)
|
||||
targetCmd += "cd " + projectPath + " && ";
|
||||
// 步骤2:加载环境变量
|
||||
targetCmd += "source ~/.bashrc && ";
|
||||
// 步骤3:显式配置PATH
|
||||
targetCmd += "export PATH=/data/data/com.termux/files/usr/bin:$PATH && ";
|
||||
// 步骤4:用stdbuf禁用stdout/stderr缓冲(关键!),执行Gradle命令
|
||||
// -o0:stdout无缓冲;-e0:stderr无缓冲;-i0:stdin无缓冲
|
||||
//targetCmd += "stdbuf -o0 -e0 -i0 " + gradleFullPath + " task --all | grep assemble && ";
|
||||
//targetCmd += "stdbuf -o0 -e0 -i0 " + gradleFullPath + " -Pandroid.aapt2FromMavenOverride=/data/data/com.termux/files/home/android-sdk/build-tools/34.0.4/aapt2 assembleBetaDebug && ";
|
||||
targetCmd += "stdbuf -o0 -e0 -i0 bash && ";
|
||||
// 步骤5:执行成功提示
|
||||
targetCmd += "echo '\n✅ 命令执行完成!' && echo '\n📌 当前目录:" + projectPath + "' && read -p '按回车键关闭终端...'";
|
||||
|
||||
|
||||
// 4. 执行命令(终端会话模式,唤起Termux窗口)
|
||||
boolean cmdSuccess = TermuxCommandExecutor.executeTerminalCommand(context, targetCmd);
|
||||
if (!cmdSuccess) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package cc.winboll.studio.winboll.theme;
|
||||
|
||||
import cc.winboll.studio.libaes.models.AESThemeBean;
|
||||
import cc.winboll.studio.winboll.R;
|
||||
|
||||
public class WinBoLLThemeBean {
|
||||
|
||||
public static final String TAG = "WinBoLLThemeBean";
|
||||
|
||||
public static int getDefaultThemeStyleID() {
|
||||
return R.style.MyAppTheme;
|
||||
}
|
||||
|
||||
public static int getThemeStyleID(AESThemeBean.ThemeType themeType) {
|
||||
int themeStyleID = getDefaultThemeStyleID();
|
||||
if (AESThemeBean.ThemeType.DEPTH == themeType) {
|
||||
themeStyleID = R.style.MyDepthAppTheme;
|
||||
} else if (AESThemeBean.ThemeType.SKY == themeType) {
|
||||
themeStyleID = R.style.MySkyAppTheme;
|
||||
} else if (AESThemeBean.ThemeType.GOLDEN == themeType) {
|
||||
themeStyleID = R.style.MyGoldenAppTheme;
|
||||
} else if (AESThemeBean.ThemeType.BEARING == themeType) {
|
||||
themeStyleID = R.style.MyBearingAppTheme;
|
||||
} else if (AESThemeBean.ThemeType.MEMOR == themeType) {
|
||||
themeStyleID = R.style.MyMemorAppTheme;
|
||||
} else if (AESThemeBean.ThemeType.TAO == themeType) {
|
||||
themeStyleID = R.style.MyTaoAppTheme;
|
||||
}
|
||||
return themeStyleID;
|
||||
}
|
||||
|
||||
public static AESThemeBean.ThemeType getThemeStyleType(int nThemeStyleID) {
|
||||
AESThemeBean.ThemeType themeStyle = AESThemeBean.ThemeType.AES;
|
||||
if (R.style.MyDepthAppTheme == nThemeStyleID) {
|
||||
themeStyle = AESThemeBean.ThemeType.DEPTH;
|
||||
} else if (R.style.MySkyAppTheme == nThemeStyleID) {
|
||||
themeStyle = AESThemeBean.ThemeType.SKY;
|
||||
} else if (R.style.MyGoldenAppTheme == nThemeStyleID) {
|
||||
themeStyle = AESThemeBean.ThemeType.GOLDEN;
|
||||
} else if (R.style.MyBearingAppTheme == nThemeStyleID) {
|
||||
themeStyle = AESThemeBean.ThemeType.BEARING;
|
||||
} else if (R.style.MyMemorAppTheme == nThemeStyleID) {
|
||||
themeStyle = AESThemeBean.ThemeType.MEMOR;
|
||||
} else if (R.style.MyTaoAppTheme == nThemeStyleID) {
|
||||
themeStyle = AESThemeBean.ThemeType.TAO;
|
||||
}
|
||||
return themeStyle;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package cc.winboll.studio.winboll.theme;
|
||||
|
||||
import android.content.Context;
|
||||
import cc.winboll.studio.libaes.models.AESThemeBean;
|
||||
import cc.winboll.studio.libaes.utils.AESThemeUtil;
|
||||
|
||||
public class WinBoLLThemeUtil {
|
||||
|
||||
public static final String TAG = "WinBoLLThemeUtil";
|
||||
|
||||
public static int getThemeTypeID(Context context) {
|
||||
AESThemeBean bean = AESThemeBean.loadBean(context, AESThemeBean.class);
|
||||
int themeTypeID;
|
||||
if (bean == null) {
|
||||
themeTypeID = WinBoLLThemeBean.getDefaultThemeStyleID();
|
||||
} else {
|
||||
int aesStyleID = bean.getCurrentThemeTypeID();
|
||||
AESThemeBean.ThemeType themeType = WinBoLLThemeBean.getThemeStyleType(aesStyleID);
|
||||
themeTypeID = WinBoLLThemeBean.getThemeStyleID(themeType);
|
||||
}
|
||||
return themeTypeID;
|
||||
}
|
||||
|
||||
public static void saveThemeStyleID(Context context, int nThemeTypeID) {
|
||||
AESThemeBean bean = new AESThemeBean(nThemeTypeID);
|
||||
AESThemeBean.saveBean(context, bean);
|
||||
}
|
||||
|
||||
public static AESThemeBean.ThemeType getThemeStyleType(int nThemeStyleID) {
|
||||
return WinBoLLThemeBean.getThemeStyleType(nThemeStyleID);
|
||||
}
|
||||
|
||||
public static int getThemeStyleID(AESThemeBean.ThemeType themeType) {
|
||||
return WinBoLLThemeBean.getThemeStyleID(themeType);
|
||||
}
|
||||
}
|
||||
@@ -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,454 @@
|
||||
package cc.winboll.studio.winboll.unittest;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import cc.winboll.studio.winboll.R;
|
||||
import cc.winboll.studio.winboll.activities.BaseWinBoLLActivity;
|
||||
import cc.winboll.studio.winboll.termux.NfcTermuxBridgeActivity;
|
||||
import cc.winboll.studio.winboll.termux.TermuxCommandExecutor;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2026/01/19 11:11:00
|
||||
* @LastEditTime 2026/01/21 17:45:00
|
||||
* @Describe Termux环境测试工具(跨包+sharedUserId模式)
|
||||
* 适配不同应用包名(当前包:cc.winboll.studio.winboll.beta / Termux包:com.termux)
|
||||
* 支持Termux目录读取、Gradle命令实时输出执行(唤起窗口),基于sharedUserId实现跨包权限适配
|
||||
*/
|
||||
public class TermuxEnvTestActivity extends BaseWinBoLLActivity {
|
||||
|
||||
@Override
|
||||
public Activity getActivity() {
|
||||
return this;
|
||||
}
|
||||
|
||||
// 常量属性(置顶排列)
|
||||
public static final String TAG = "TermuxEnvTestActivity";
|
||||
private static final String TERMUX_HOME_PATH = "/data/data/com.termux/files/home/TermuxWorkSpaces";
|
||||
private static final String CMD_RESULT_FILE = TERMUX_HOME_PATH + "/CMD_RESULT_FILE.log";
|
||||
|
||||
// 成员属性(常量后排列)
|
||||
private Toolbar mToolbar;
|
||||
private TextView tvMessage;
|
||||
private Handler mainHandler;
|
||||
|
||||
@Override
|
||||
public String getTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
LogUtils.d(TAG, "onCreate() 调用,初始化Activity");
|
||||
setContentView(R.layout.activity_termux_env_test);
|
||||
initView();
|
||||
initToolbar();
|
||||
initTermuxDirectory();
|
||||
LogUtils.d(TAG, "onCreate() 执行完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化视图组件
|
||||
*/
|
||||
private void initView() {
|
||||
LogUtils.d(TAG, "initView() 开始初始化视图");
|
||||
tvMessage = (TextView) findViewById(R.id.tv_message);
|
||||
mainHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
// 初始化提示信息
|
||||
StringBuilder initMsg = new StringBuilder();
|
||||
initMsg.append("Termux 测试工具(跨包+sharedUserId模式)\n");
|
||||
initMsg.append("-------------------------\n");
|
||||
initMsg.append("当前应用包名:");
|
||||
initMsg.append(getPackageName());
|
||||
initMsg.append("\n");
|
||||
initMsg.append("Termux应用包名:com.termux\n");
|
||||
initMsg.append("sharedUserId:com.termux(需一致)\n");
|
||||
initMsg.append("支持功能:目录读取、Gradle命令实时输出(唤起Termux窗口)\n");
|
||||
initMsg.append("-------------------------\n");
|
||||
tvMessage.setText(initMsg.toString());
|
||||
|
||||
LogUtils.d(TAG, "initView() 初始化完成,tvMessage初始值:" + initMsg.toString().trim());
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化Toolbar组件
|
||||
*/
|
||||
private void initToolbar() {
|
||||
LogUtils.d(TAG, "initToolbar() 开始初始化Toolbar");
|
||||
mToolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
|
||||
if (mToolbar == null) {
|
||||
LogUtils.e(TAG, "initToolbar() 错误:未找到Toolbar组件");
|
||||
return;
|
||||
}
|
||||
|
||||
setSupportActionBar(mToolbar);
|
||||
mToolbar.setSubtitle(getTag());
|
||||
((AppCompatActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d(TAG, "initToolbar() 导航栏返回按钮点击");
|
||||
getActivity().finish();
|
||||
}
|
||||
});
|
||||
|
||||
LogUtils.d(TAG, "initToolbar() 初始化完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化Termux目标目录(跨包+sharedUserId模式)
|
||||
*/
|
||||
private void initTermuxDirectory() {
|
||||
LogUtils.d(TAG, "initTermuxDirectory() 开始初始化Termux目录,路径:" + TERMUX_HOME_PATH);
|
||||
File termuxDir = new File(TERMUX_HOME_PATH);
|
||||
|
||||
if (termuxDir.exists()) {
|
||||
LogUtils.d(TAG, "initTermuxDirectory() Termux目录已存在,无需创建");
|
||||
return;
|
||||
}
|
||||
|
||||
tvMessage.append("正在创建Termux目标目录...\n");
|
||||
boolean createSuccess = termuxDir.mkdirs();
|
||||
if (createSuccess) {
|
||||
LogUtils.d(TAG, "initTermuxDirectory() Termux目录创建成功:" + TERMUX_HOME_PATH);
|
||||
tvMessage.append("Termux目录创建成功:");
|
||||
tvMessage.append(TERMUX_HOME_PATH);
|
||||
tvMessage.append("\n");
|
||||
} else {
|
||||
LogUtils.e(TAG, "initTermuxDirectory() 错误:Termux目录创建失败");
|
||||
tvMessage.append("警告:Termux目录创建失败!\n");
|
||||
tvMessage.append("请检查:\n");
|
||||
tvMessage.append("1.AndroidManifest.xml中sharedUserId是否为com.termux\n");
|
||||
tvMessage.append("2.设备是否已root(部分机型需root才能跨包写私有目录)\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试读取Termux目录(按钮点击事件)
|
||||
*/
|
||||
public void onTestTermuxEnv(View view) {
|
||||
LogUtils.d(TAG, "onTestTermuxEnv() 按钮点击,开始读取Termux目录");
|
||||
tvMessage.append("\n【测试:读取Termux目录】\n");
|
||||
|
||||
String fileListStr = readTermuxHomeFileList();
|
||||
tvMessage.append(fileListStr);
|
||||
tvMessage.append("\n-------------------------\n");
|
||||
|
||||
LogUtils.d(TAG, "onTestTermuxEnv() 执行完成,读取结果长度:" + fileListStr.length() + "字符");
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试执行Gradle命令(实时输出版,唤起Termux窗口)
|
||||
*/
|
||||
public void onTestTermuxGradleBuildCMD(View view) {
|
||||
LogUtils.d(TAG, "onTestTermuxCMD() 按钮点击,执行Gradle命令(实时输出)");
|
||||
tvMessage.append("\n【测试:执行Gradle命令(实时输出)】\n");
|
||||
|
||||
// 1. 校验Termux是否安装
|
||||
if (!TermuxCommandExecutor.isTermuxInstalled(this)) {
|
||||
LogUtils.e(TAG, "onTestTermuxCMD() 错误:未安装Termux应用");
|
||||
tvMessage.append("错误:未安装Termux应用(包名:com.termux)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 定义核心路径(确保路径与Termux中一致)
|
||||
String gradleFullPath = "/data/data/com.termux/files/home/gradle/gradle-7.5.1/bin/gradle";
|
||||
String projectPath = TERMUX_HOME_PATH + "/Sources/DebugTemp"; // 项目目录
|
||||
|
||||
// 3. 构造命令(核心:用stdbuf禁用缓冲,实现实时输出)
|
||||
String targetCmd = "";
|
||||
// 步骤1:进入项目目录(不存在则创建)
|
||||
//targetCmd += "cd " + projectPath + " || (mkdir -p " + projectPath + " && cd " + projectPath + ") && ";
|
||||
targetCmd += "cd " + projectPath + " && ";
|
||||
// 步骤2:加载环境变量
|
||||
targetCmd += "source ~/.bashrc && ";
|
||||
// 步骤3:显式配置PATH
|
||||
targetCmd += "export PATH=/data/data/com.termux/files/usr/bin:/data/data/com.termux/files/home/gradle/gradle-7.5.1/bin:$PATH && ";
|
||||
// 步骤4:用stdbuf禁用stdout/stderr缓冲(关键!),执行Gradle命令
|
||||
// -o0:stdout无缓冲;-e0:stderr无缓冲;-i0:stdin无缓冲
|
||||
//targetCmd += "stdbuf -o0 -e0 -i0 " + gradleFullPath + " task --all | grep assemble && ";
|
||||
targetCmd += "stdbuf -o0 -e0 -i0 " + gradleFullPath + " -Pandroid.aapt2FromMavenOverride=/data/data/com.termux/files/home/android-sdk/build-tools/34.0.4/aapt2 assembleBetaDebug && ";
|
||||
// 步骤5:执行成功提示
|
||||
targetCmd += "echo '\n✅ 命令执行完成!' && echo '\n📌 项目目录:" + projectPath + "' && read -p '按回车键关闭终端...'";
|
||||
|
||||
LogUtils.d(TAG, "onTestTermuxCMD() 执行命令:" + targetCmd);
|
||||
|
||||
// 4. 执行命令(终端会话模式,唤起Termux窗口)
|
||||
boolean cmdSuccess = TermuxCommandExecutor.executeTerminalCommand(this, targetCmd);
|
||||
if (!cmdSuccess) {
|
||||
LogUtils.e(TAG, "onTestTermuxCMD() 错误:命令发送失败");
|
||||
tvMessage.append("命令发送失败!\n");
|
||||
tvMessage.append("可能原因:\n");
|
||||
tvMessage.append("1.Termux未开启外部应用调用权限(执行termux-setup-storage后配置)\n");
|
||||
tvMessage.append("2.sharedUserId配置不一致\n");
|
||||
tvMessage.append("3.Termux未安装stdbuf(执行pkg install coreutils)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// 5. 应用内提示(说明实时输出特性)
|
||||
tvMessage.append("已唤起Termux窗口,执行说明:\n");
|
||||
tvMessage.append("1. 自动创建/进入项目目录:");
|
||||
tvMessage.append(projectPath);
|
||||
tvMessage.append("\n");
|
||||
tvMessage.append("2. 禁用输出缓冲(stdbuf),实现Gradle实时输出\n");
|
||||
tvMessage.append("3. 筛选assemble相关任务(grep assemble)\n");
|
||||
tvMessage.append("⚙️ Gradle路径:");
|
||||
tvMessage.append(gradleFullPath);
|
||||
tvMessage.append("\n");
|
||||
tvMessage.append("💡 若未实时输出,请在Termux中执行:pkg install coreutils(安装stdbuf)\n");
|
||||
tvMessage.append("-------------------------\n");
|
||||
}
|
||||
|
||||
public void onTestWinBoLLProjectBuild(View view) {
|
||||
ToastUtils.show("onTestWinBoLLProjectBuild");
|
||||
NfcTermuxBridgeActivity.testCommand(this);
|
||||
}
|
||||
|
||||
// public void onTestWinBoLLProjectBuildView(View view) {
|
||||
// ToastUtils.show("onTestWinBoLLProjectBuildView");
|
||||
// NfcTermuxBridgeActivity.testViewCommand(this);
|
||||
// }
|
||||
|
||||
public void onOpenTermuxBash(View view) {
|
||||
LogUtils.d(TAG, "onTestTermuxCMD() 按钮点击,执行Gradle命令(实时输出)");
|
||||
tvMessage.append("\n【测试:执行Gradle命令(实时输出)】\n");
|
||||
|
||||
// 1. 校验Termux是否安装
|
||||
if (!TermuxCommandExecutor.isTermuxInstalled(this)) {
|
||||
LogUtils.e(TAG, "onTestTermuxCMD() 错误:未安装Termux应用");
|
||||
tvMessage.append("错误:未安装Termux应用(包名:com.termux)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 定义核心路径(确保路径与Termux中一致)
|
||||
String gradleFullPath = "/data/data/com.termux/files/home/gradle/gradle-7.5.1/bin/gradle";
|
||||
String projectPath = TERMUX_HOME_PATH + "/"; // 项目目录
|
||||
|
||||
// 3. 构造命令(核心:用stdbuf禁用缓冲,实现实时输出)
|
||||
String targetCmd = "";
|
||||
// 步骤1:进入项目目录(不存在则创建)
|
||||
targetCmd += "cd " + projectPath + " && ";
|
||||
// 步骤2:加载环境变量
|
||||
targetCmd += "source ~/.bashrc && ";
|
||||
// 步骤3:显式配置PATH
|
||||
targetCmd += "export PATH=/data/data/com.termux/files/usr/bin:/data/data/com.termux/files/home/gradle/gradle-7.5.1/bin:$PATH && ";
|
||||
// 步骤4:用stdbuf禁用stdout/stderr缓冲(关键!),执行Gradle命令
|
||||
// -o0:stdout无缓冲;-e0:stderr无缓冲;-i0:stdin无缓冲
|
||||
//targetCmd += "stdbuf -o0 -e0 -i0 " + gradleFullPath + " task --all | grep assemble && ";
|
||||
//targetCmd += "stdbuf -o0 -e0 -i0 " + gradleFullPath + " -Pandroid.aapt2FromMavenOverride=/data/data/com.termux/files/home/android-sdk/build-tools/34.0.4/aapt2 assembleBetaDebug && ";
|
||||
targetCmd += "stdbuf -o0 -e0 -i0 bash && ";
|
||||
// 步骤5:执行成功提示
|
||||
targetCmd += "echo '\n✅ 命令执行完成!' && echo '\n📌 项目目录:" + projectPath + "' && read -p '按回车键关闭终端...'";
|
||||
|
||||
LogUtils.d(TAG, "onTestTermuxCMD() 执行命令:" + targetCmd);
|
||||
|
||||
// 4. 执行命令(终端会话模式,唤起Termux窗口)
|
||||
boolean cmdSuccess = TermuxCommandExecutor.executeTerminalCommand(this, targetCmd);
|
||||
if (!cmdSuccess) {
|
||||
LogUtils.e(TAG, "onTestTermuxCMD() 错误:命令发送失败");
|
||||
tvMessage.append("命令发送失败!\n");
|
||||
tvMessage.append("可能原因:\n");
|
||||
tvMessage.append("1.Termux未开启外部应用调用权限(执行termux-setup-storage后配置)\n");
|
||||
tvMessage.append("2.sharedUserId配置不一致\n");
|
||||
tvMessage.append("3.Termux未安装stdbuf(执行pkg install coreutils)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// 5. 应用内提示(说明实时输出特性)
|
||||
tvMessage.append("已唤起Termux窗口,执行说明:\n");
|
||||
tvMessage.append("1. 自动创建/进入项目目录:");
|
||||
tvMessage.append(projectPath);
|
||||
tvMessage.append("\n");
|
||||
tvMessage.append("2. 禁用输出缓冲(stdbuf),实现Gradle实时输出\n");
|
||||
tvMessage.append("3. 筛选assemble相关任务(grep assemble)\n");
|
||||
tvMessage.append("⚙️ Gradle路径:");
|
||||
tvMessage.append(gradleFullPath);
|
||||
tvMessage.append("\n");
|
||||
tvMessage.append("💡 若未实时输出,请在Termux中执行:pkg install coreutils(安装stdbuf)\n");
|
||||
tvMessage.append("-------------------------\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* 跨包读取Termux命令结果文件(保留原功能,兼容其他场景)
|
||||
*/
|
||||
private String readCmdResultDirectly() {
|
||||
LogUtils.d(TAG, "readCmdResultDirectly() 开始读取命令结果,文件路径:" + CMD_RESULT_FILE);
|
||||
File resultFile = new File(CMD_RESULT_FILE);
|
||||
|
||||
// 校验文件存在性
|
||||
if (!resultFile.exists()) {
|
||||
LogUtils.e(TAG, "readCmdResultDirectly() 错误:结果文件不存在");
|
||||
return "错误:结果文件不存在\n可能原因:\n1.命令执行失败\n2.延迟时间不足\n3.跨包写权限被拒绝(需root)";
|
||||
}
|
||||
|
||||
// 校验文件可读性
|
||||
if (!resultFile.canRead()) {
|
||||
LogUtils.e(TAG, "readCmdResultDirectly() 错误:无结果文件读取权限");
|
||||
return "错误:无结果文件读取权限(sharedUserId配置错误或未root)";
|
||||
}
|
||||
|
||||
// 读取文件内容
|
||||
StringBuilder result = new StringBuilder();
|
||||
BufferedReader br = null;
|
||||
try {
|
||||
br = new BufferedReader(new InputStreamReader(new FileInputStream(resultFile), StandardCharsets.UTF_8));
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
result.append(line);
|
||||
result.append("\n");
|
||||
}
|
||||
LogUtils.d(TAG, "readCmdResultDirectly() 文件读取完成,结果长度:" + result.length() + "字符");
|
||||
} catch (IOException e) {
|
||||
LogUtils.e(TAG, "readCmdResultDirectly() 错误:文件读取异常", e);
|
||||
return "错误:读取文件异常 → " + e.getMessage();
|
||||
} finally {
|
||||
if (br != null) {
|
||||
try {
|
||||
br.close();
|
||||
LogUtils.d(TAG, "readCmdResultDirectly() BufferedReader资源已关闭");
|
||||
} catch (IOException e) {
|
||||
LogUtils.e(TAG, "readCmdResultDirectly() 错误:BufferedReader关闭异常", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String resultStr = result.toString().trim();
|
||||
return resultStr.isEmpty() ? "命令执行成功,但无输出" : resultStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除命令结果临时文件(保留原功能)
|
||||
*/
|
||||
private void deleteCmdResultFile() {
|
||||
LogUtils.d(TAG, "deleteCmdResultFile() 开始删除临时文件,路径:" + CMD_RESULT_FILE);
|
||||
File resultFile = new File(CMD_RESULT_FILE);
|
||||
|
||||
if (!resultFile.exists()) {
|
||||
LogUtils.d(TAG, "deleteCmdResultFile() 临时文件不存在,无需删除");
|
||||
return;
|
||||
}
|
||||
|
||||
if (resultFile.canWrite()) {
|
||||
boolean deleteSuccess = resultFile.delete();
|
||||
if (deleteSuccess) {
|
||||
LogUtils.d(TAG, "deleteCmdResultFile() 临时文件删除成功");
|
||||
} else {
|
||||
LogUtils.w(TAG, "deleteCmdResultFile() 警告:临时文件删除失败");
|
||||
tvMessage.append("警告:临时结果文件删除失败\n");
|
||||
}
|
||||
} else {
|
||||
LogUtils.w(TAG, "deleteCmdResultFile() 警告:无临时文件删除权限");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 跨包读取Termux目录文件列表(保留原功能)
|
||||
*/
|
||||
private String readTermuxHomeFileList() {
|
||||
LogUtils.d(TAG, "readTermuxHomeFileList() 开始读取目录,路径:" + TERMUX_HOME_PATH);
|
||||
File termuxHomeDir = new File(TERMUX_HOME_PATH);
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
// 基础校验
|
||||
if (!termuxHomeDir.exists()) {
|
||||
LogUtils.e(TAG, "readTermuxHomeFileList() 错误:目录不存在");
|
||||
return "错误:Termux目录不存在 → " + TERMUX_HOME_PATH;
|
||||
}
|
||||
if (!termuxHomeDir.isDirectory()) {
|
||||
LogUtils.e(TAG, "readTermuxHomeFileList() 错误:指定路径不是目录");
|
||||
return "错误:指定路径不是目录 → " + TERMUX_HOME_PATH;
|
||||
}
|
||||
if (!termuxHomeDir.canRead()) {
|
||||
LogUtils.e(TAG, "readTermuxHomeFileList() 错误:无目录读取权限");
|
||||
return "错误:无目录读取权限(需满足:1.sharedUserId=com.termux 2.设备root)";
|
||||
}
|
||||
|
||||
// 获取文件列表
|
||||
File[] files = termuxHomeDir.listFiles();
|
||||
if (files == null || files.length == 0) {
|
||||
LogUtils.d(TAG, "readTermuxHomeFileList() 目录为空");
|
||||
return "Termux目录为空 → " + TERMUX_HOME_PATH;
|
||||
}
|
||||
|
||||
// 拼接结果字符串
|
||||
result.append("Termux主目录:");
|
||||
result.append(TERMUX_HOME_PATH);
|
||||
result.append("\n");
|
||||
result.append("文件/子目录总数:");
|
||||
result.append(files.length);
|
||||
result.append("\n");
|
||||
result.append("目录权限:");
|
||||
result.append("读:");
|
||||
result.append(termuxHomeDir.canRead());
|
||||
result.append(" | 写:");
|
||||
result.append(termuxHomeDir.canWrite());
|
||||
result.append("\n");
|
||||
result.append("-------------------------\n");
|
||||
|
||||
for (File file : files) {
|
||||
String fileType = file.isDirectory() ? "[目录]" : "[文件]";
|
||||
String fileName = file.getName();
|
||||
String filePath = file.getAbsolutePath();
|
||||
String fileSize = file.isFile() ? " | 大小:" + formatFileSize(file.length()) : "";
|
||||
String filePerm = " | 权限:r:" + file.canRead() + "/w:" + file.canWrite();
|
||||
|
||||
result.append(fileType);
|
||||
result.append(" ");
|
||||
result.append(fileName);
|
||||
result.append(fileSize);
|
||||
result.append(filePerm);
|
||||
result.append(" → ");
|
||||
result.append(filePath);
|
||||
result.append("\n");
|
||||
}
|
||||
|
||||
LogUtils.d(TAG, "readTermuxHomeFileList() 读取完成,结果长度:" + result.length() + "字符");
|
||||
return result.toString().trim();
|
||||
}
|
||||
/**
|
||||
* 格式化文件大小(B→KB→MB)
|
||||
*/
|
||||
private String formatFileSize(long length) {
|
||||
LogUtils.d(TAG, "formatFileSize() 调用,文件长度:" + length + "B");
|
||||
String sizeStr;
|
||||
if (length < 1024) {
|
||||
sizeStr = length + "B";
|
||||
} else if (length < 1024 * 1024) {
|
||||
sizeStr = String.format("%.1fKB", length / 1024.0);
|
||||
} else {
|
||||
sizeStr = String.format("%.1fMB", length / (1024.0 * 1024));
|
||||
}
|
||||
LogUtils.d(TAG, "formatFileSize() 格式化结果:" + sizeStr);
|
||||
return sizeStr;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
LogUtils.d(TAG, "onDestroy() 调用,清理资源");
|
||||
deleteCmdResultFile();
|
||||
if (mainHandler != null) {
|
||||
mainHandler.removeCallbacksAndMessages(null);
|
||||
LogUtils.d(TAG, "onDestroy() Handler资源已释放");
|
||||
}
|
||||
LogUtils.d(TAG, "onDestroy() 执行完成");
|
||||
}
|
||||
}
|
||||
@@ -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 androidx.appcompat.app.AppCompatActivity;
|
||||
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.winboll.R;
|
||||
|
||||
/**
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2026/01/03 10:52
|
||||
* @Describe 企业微信SDK接口测试(基础调试版)
|
||||
* 包含:SDK初始化、基础接口调用、日志输出、主线程回调处理
|
||||
*/
|
||||
public class TestWeWorkSpecSDK extends AppCompatActivity 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,264 @@
|
||||
package cc.winboll.studio.winboll.views;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.winboll.models.TermuxButtonModel;
|
||||
|
||||
/**
|
||||
* 自定义Termux功能按钮控件
|
||||
* 绑定TermuxButtonModel实体数据,拦截点击事件做确认弹窗逻辑判断
|
||||
* isCommitted为true直接执行点击事件,为false弹出确认对话框二次确认
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @CreateTime 2026/04/30 10:57:00
|
||||
* @EditTime 2026/04/30 13:52:15
|
||||
*/
|
||||
public class TermuxButton extends Button {
|
||||
|
||||
public static final String TAG = "TermuxButton";
|
||||
|
||||
/** 绑定按钮对应数据实体 */
|
||||
private TermuxButtonModel buttonModel;
|
||||
/** 保存外部设置的原始点击监听 */
|
||||
private OnClickListener originClickListener;
|
||||
|
||||
//==================== 构造方法 ====================
|
||||
/**
|
||||
* 代码动态创建控件构造
|
||||
* @param context 上下文
|
||||
*/
|
||||
public TermuxButton(Context context) {
|
||||
super(context);
|
||||
LogUtils.d(TAG, "TermuxButton 无参构造执行,上下文:" + context);
|
||||
initView(null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* XML布局引用控件基础构造
|
||||
* @param context 上下文
|
||||
* @param attrs XML属性集
|
||||
*/
|
||||
public TermuxButton(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
LogUtils.d(TAG, "TermuxButton XML构造执行");
|
||||
initView(attrs, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* XML布局带自定义属性构造
|
||||
* @param context 上下文
|
||||
* @param attrs XML属性集
|
||||
* @param defStyleAttr 默认样式属性
|
||||
*/
|
||||
public TermuxButton(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
LogUtils.d(TAG, "TermuxButton 带样式属性构造执行");
|
||||
initView(attrs, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 高版本Android完整全参构造
|
||||
* @param context 上下文
|
||||
* @param attrs XML属性集
|
||||
* @param defStyleAttr 默认样式属性
|
||||
* @param defStyleRes 默认样式资源
|
||||
*/
|
||||
public TermuxButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
LogUtils.d(TAG, "TermuxButton 全参构造执行");
|
||||
initView(attrs, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接传入Model初始化控件构造
|
||||
* @param context 上下文
|
||||
* @param model 按钮数据实体
|
||||
*/
|
||||
public TermuxButton(Context context, TermuxButtonModel model) {
|
||||
super(context);
|
||||
LogUtils.d(TAG, "TermuxButton Model入参构造执行");
|
||||
initView(null, model);
|
||||
}
|
||||
|
||||
//==================== 核心初始化 ====================
|
||||
/**
|
||||
* 控件统一初始化方法
|
||||
* @param attrs XML属性集合
|
||||
* @param model 绑定数据实体
|
||||
*/
|
||||
private void initView(AttributeSet attrs, TermuxButtonModel model) {
|
||||
this.buttonModel = model;
|
||||
|
||||
// 按钮基础默认配置
|
||||
setClickable(true);
|
||||
setFocusable(true);
|
||||
|
||||
// 解析XML布局自定义属性
|
||||
if (attrs != null) {
|
||||
parseXmlCustomAttr(attrs);
|
||||
}
|
||||
|
||||
// 同步Model内按钮名称到控件展示文本
|
||||
refreshButtonText();
|
||||
// 绑定自定义拦截点击事件
|
||||
setCustomClickEvent();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析XML布局属性,读取原生android:text与自定义属性赋值到Model
|
||||
* @param attrs XML属性集
|
||||
*/
|
||||
private void parseXmlCustomAttr(AttributeSet attrs) {
|
||||
if (buttonModel == null) {
|
||||
buttonModel = new TermuxButtonModel();
|
||||
LogUtils.d(TAG, "自动初始化空的TermuxButtonModel实体");
|
||||
}
|
||||
|
||||
// 读取原生android:text作为按钮名称
|
||||
String androidText = attrs.getAttributeValue("http://schemas.android.com/apk/res/android", "text");
|
||||
// 读取自定义扩展属性
|
||||
String exeCommand = attrs.getAttributeValue("http://schemas.android.com/apk/res-auto", "exeCommand");
|
||||
String workDir = attrs.getAttributeValue("http://schemas.android.com/apk/res-auto", "workDir");
|
||||
String isCommittedStr = attrs.getAttributeValue("http://schemas.android.com/apk/res-auto", "isCommitted");
|
||||
String commitTitle = attrs.getAttributeValue("http://schemas.android.com/apk/res-auto", "commitTitle");
|
||||
String commitInfo = attrs.getAttributeValue("http://schemas.android.com/apk/res-auto", "commitInfo");
|
||||
|
||||
// 属性赋值绑定
|
||||
if (androidText != null) {
|
||||
buttonModel.setButtonName(androidText);
|
||||
}
|
||||
if (exeCommand != null) {
|
||||
buttonModel.setExeCommand(exeCommand);
|
||||
}
|
||||
if (workDir != null) {
|
||||
buttonModel.setWorkDir(workDir);
|
||||
}
|
||||
if (isCommittedStr != null) {
|
||||
buttonModel.setCommitted(Boolean.parseBoolean(isCommittedStr));
|
||||
}
|
||||
if (commitTitle != null) {
|
||||
buttonModel.setCommitTitle(commitTitle);
|
||||
}
|
||||
if (commitInfo != null) {
|
||||
buttonModel.setCommitInfo(commitInfo);
|
||||
}
|
||||
|
||||
LogUtils.d(TAG, "XML属性解析完成,按钮名称:" + androidText);
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步Model中buttonName,更新按钮展示文字
|
||||
*/
|
||||
private void refreshButtonText() {
|
||||
if (buttonModel != null) {
|
||||
setText(buttonModel.getButtonName());
|
||||
}
|
||||
}
|
||||
|
||||
//==================== 点击事件相关 ====================
|
||||
/**
|
||||
* 重写点击监听设置,保存外部原始点击事件
|
||||
* @param l 外部传入点击监听
|
||||
*/
|
||||
@Override
|
||||
public void setOnClickListener(OnClickListener l) {
|
||||
this.originClickListener = l;
|
||||
LogUtils.d(TAG, "保存外部原始按钮点击监听");
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义拦截按钮点击逻辑
|
||||
* isCommitted=true 直接执行原始点击事件
|
||||
* isCommitted=false 弹出确认二次弹窗
|
||||
*/
|
||||
private void setCustomClickEvent() {
|
||||
super.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (buttonModel == null) {
|
||||
LogUtils.d(TAG, "无绑定Model,直接执行原始点击事件");
|
||||
if (originClickListener != null) {
|
||||
originClickListener.onClick(view);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
boolean commitState = buttonModel.isCommitted();
|
||||
LogUtils.d(TAG, "按钮点击触发,isCommitted状态:" + commitState);
|
||||
if (commitState) {
|
||||
// 无需确认,直接执行原有点击任务
|
||||
if (originClickListener != null) {
|
||||
originClickListener.onClick(view);
|
||||
}
|
||||
} else {
|
||||
// 需要二次确认,弹出提示对话框
|
||||
showCommitDialog();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹出操作确认对话框
|
||||
* 标题:commitTitle 内容:commitInfo
|
||||
* 取消:关闭弹窗无操作 确定:执行原始点击事件
|
||||
*/
|
||||
private void showCommitDialog() {
|
||||
Context context = getContext();
|
||||
String dialogTitle = buttonModel.getCommitTitle();
|
||||
String dialogMsg = buttonModel.getCommitInfo();
|
||||
|
||||
// 空值默认兜底处理
|
||||
if (dialogTitle == null || "".equals(dialogTitle)) {
|
||||
dialogTitle = "温馨提示";
|
||||
}
|
||||
if (dialogMsg == null || "".equals(dialogMsg)) {
|
||||
dialogMsg = "确定要执行该操作吗?";
|
||||
}
|
||||
|
||||
LogUtils.d(TAG, "弹出确认对话框,标题:" + dialogTitle);
|
||||
new AlertDialog.Builder(context)
|
||||
.setTitle(dialogTitle)
|
||||
.setMessage(dialogMsg)
|
||||
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
LogUtils.d(TAG, "对话框点击取消,终止操作");
|
||||
}
|
||||
})
|
||||
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
LogUtils.d(TAG, "对话框点击确定,继续执行操作");
|
||||
if (originClickListener != null) {
|
||||
originClickListener.onClick(TermuxButton.this);
|
||||
}
|
||||
}
|
||||
})
|
||||
.setCancelable(false)
|
||||
.show();
|
||||
}
|
||||
|
||||
//==================== Getter & Setter ====================
|
||||
public TermuxButtonModel getButtonModel() {
|
||||
return buttonModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置绑定按钮数据实体,自动刷新按钮展示文字
|
||||
* @param buttonModel 数据实体类
|
||||
*/
|
||||
public void setButtonModel(TermuxButtonModel buttonModel) {
|
||||
this.buttonModel = buttonModel;
|
||||
LogUtils.d(TAG, "外部设置ButtonModel,自动刷新按钮文本");
|
||||
refreshButtonText();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
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>
|
||||
6
winboll/src/main/res/drawable/bg_container_border.xml
Normal file
6
winboll/src/main/res/drawable/bg_container_border.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?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/transparent" />
|
||||
<stroke android:width="1dp" android:color="#FFB0B0B0" />
|
||||
<corners android:radius="?attr/borderCornerRadius" />
|
||||
</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>
|
||||
8
winboll/src/main/res/drawable/dot_background.xml
Normal file
8
winboll/src/main/res/drawable/dot_background.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="ring"
|
||||
android:innerRadiusRatio="3"
|
||||
android:thicknessRatio="8"
|
||||
android:useLevel="false">
|
||||
<solid android:color="#00000000"/>
|
||||
</shape>
|
||||
14
winboll/src/main/res/drawable/dot_darkgreen_dark.xml
Normal file
14
winboll/src/main/res/drawable/dot_darkgreen_dark.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<inset xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:insetLeft="2dp"
|
||||
android:insetTop="2dp"
|
||||
android:insetRight="2dp"
|
||||
android:insetBottom="2dp">
|
||||
<shape android:shape="ring"
|
||||
android:innerRadiusRatio="2"
|
||||
android:thicknessRatio="2.5"
|
||||
android:useLevel="false">
|
||||
<solid android:color="#006400"/>
|
||||
<stroke android:width="1dp" android:color="#006400"/>
|
||||
</shape>
|
||||
</inset>
|
||||
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>
|
||||
8
winboll/src/main/res/drawable/progress_bar_style.xml
Normal file
8
winboll/src/main/res/drawable/progress_bar_style.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@android:id/background" android:drawable="@android:color/darker_gray"/>
|
||||
<item android:id="@android:id/progress">
|
||||
<clip android:drawable="@android:color/holo_blue_light"/>
|
||||
</item>
|
||||
</layer-list>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user