Compare commits
28 Commits
winboll
...
powerbell-
| Author | SHA1 | Date | |
|---|---|---|---|
| ea7e2f8366 | |||
| d073a86b9b | |||
| e76427eac8 | |||
| af53216af3 | |||
| 3ae56bb202 | |||
| 09f1974c8e | |||
| 721c93c4b2 | |||
| 5a9a138463 | |||
| ae601c1445 | |||
| c5cd274b0f | |||
| 07a53da918 | |||
| 2aaf18f29f | |||
| 9892f3de2d | |||
|
|
c06a325c42 | ||
|
|
7897100659 | ||
|
|
51793077bd | ||
| ada29fb2b4 | |||
|
|
306f62f7ca | ||
|
|
50e2bd375d | ||
| 2480c8c1f0 | |||
|
|
81950699b3 | ||
| 47ea47cddc | |||
|
|
2404a9c532 | ||
|
|
82518af2d6 | ||
|
|
bb98d6bb1b | ||
|
|
230038f6f3 | ||
|
|
f8980446a8 | ||
|
|
643b84aece |
@@ -3,11 +3,8 @@
|
||||
|
||||
########
|
||||
## ☁ ☁ ☁ WinBoLL APP ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁
|
||||
# ☁ ☁ WinBoLL Studio Android 应用开源项目。☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁
|
||||
# ☁ ☁ WinBoLL Studio Android 应用开源项目。☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁
|
||||
# ☁ ☁ ☁ WinBoLL 网站地址 https://www.winboll.cc/ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁
|
||||
# ☁ ☁ ☁ WinBoLL 源码地址 <https://gitea.winboll.cc/Studio/APPBase> ☁ ☁ ☁ ☁ ☁ ☁ ☁
|
||||
# ☁ ☁ ☁ GitHub 源码地址 <https://github.com/ZhanGSKen/APPBase.git> ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁
|
||||
# ☁ ☁ ☁ 码云 源码地址 <https://gitee.com/zhangsken/appbase.git> ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁
|
||||
|
||||
## WinBoLL 提问
|
||||
同样是 /sdcard 目录,在开发 Android 应用时,
|
||||
|
||||
@@ -33,6 +33,9 @@ buildscript {
|
||||
//println mavenLocal().url
|
||||
//println "mavenLocal : ==========="
|
||||
//mavenLocal()
|
||||
|
||||
// WinBoLL.CC 紧急备用 Maven 仓库
|
||||
maven { url 'https://spare-maven.winboll.cc/repository/' }
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.2.1' // 对应 compileSdkVersion 32
|
||||
@@ -56,6 +59,7 @@ allprojects {
|
||||
password 'AKCp8ih1PFG9tV8qaLyws67dLGZi8udFM39SfsHgihN15cgsiRvHuxj8JzFmuZjaViVeNawaA'
|
||||
}
|
||||
}
|
||||
|
||||
// Nexus Maven 库地址
|
||||
// "WinBoLL Release"
|
||||
maven { url "https://nexus.winboll.cc/repository/maven-public/" }
|
||||
@@ -74,6 +78,9 @@ allprojects {
|
||||
//println mavenLocal().url
|
||||
//println "mavenLocal : ==========="
|
||||
//mavenLocal()
|
||||
|
||||
// WinBoLL.CC 紧急备用 Maven 仓库
|
||||
maven { url 'https://spare-maven.winboll.cc/repository/' }
|
||||
}
|
||||
ext {
|
||||
// 定义全局变量,常用于版本管理
|
||||
|
||||
@@ -39,14 +39,7 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
// 米盟
|
||||
// 米盟 SDK
|
||||
packagingOptions {
|
||||
doNotStrip "*/*/libmimo_1011.so"
|
||||
}
|
||||
@@ -55,14 +48,15 @@ android {
|
||||
dependencies {
|
||||
|
||||
// 米盟
|
||||
implementation 'com.miui.zeus:mimo-ad-sdk:5.3.+'//请使用最新版sdk
|
||||
api 'com.miui.zeus:mimo-ad-sdk:5.3.+'//请使用最新版sdk
|
||||
//注意:以下5个库必须要引入
|
||||
//implementation 'androidx.appcompat:appcompat:1.4.1'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.0.0'
|
||||
implementation 'com.google.code.gson:gson:2.8.5'
|
||||
implementation 'com.github.bumptech.glide:glide:4.9.0'
|
||||
//api 'androidx.appcompat:appcompat:1.4.1'
|
||||
api 'androidx.recyclerview:recyclerview:1.0.0'
|
||||
api 'com.google.code.gson:gson:2.8.5'
|
||||
api 'com.github.bumptech.glide:glide:4.9.0'
|
||||
//annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
|
||||
|
||||
|
||||
// 应用介绍页类库
|
||||
api 'io.github.medyo:android-about-page:2.0.0'
|
||||
// SSH
|
||||
@@ -83,7 +77,7 @@ dependencies {
|
||||
//api 'androidx.vectordrawable:vectordrawable-animated:1.1.0'
|
||||
//api 'androidx.fragment:fragment:1.1.0'
|
||||
|
||||
implementation 'cc.winboll.studio:libaes:15.11.4'
|
||||
implementation 'cc.winboll.studio:libaes:15.11.7'
|
||||
implementation 'cc.winboll.studio:libappbase:15.11.0'
|
||||
|
||||
//api fileTree(dir: 'libs', include: ['*.aar'])
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Wed Nov 19 09:09:24 HKT 2025
|
||||
stageCount=4
|
||||
#Thu Nov 27 14:46:03 HKT 2025
|
||||
stageCount=10
|
||||
libraryProject=
|
||||
baseVersion=15.11
|
||||
publishVersion=15.11.3
|
||||
publishVersion=15.11.9
|
||||
buildCount=0
|
||||
baseBetaVersion=15.11.4
|
||||
baseBetaVersion=15.11.10
|
||||
|
||||
137
powerbell/proguard-rules.pro
vendored
137
powerbell/proguard-rules.pro
vendored
@@ -9,12 +9,135 @@
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
# ============================== 基础通用规则 ==============================
|
||||
# 保留系统组件
|
||||
-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.**
|
||||
# ============================== 必要补充规则 ==============================
|
||||
# OkHttp 4.4.1 补充规则(Java 7 兼容)
|
||||
-keep class okhttp3.internal.concurrent.** { *; }
|
||||
-keep class okhttp3.internal.connection.** { *; }
|
||||
-dontwarn okhttp3.internal.concurrent.TaskRunner
|
||||
-dontwarn okhttp3.internal.connection.RealCall
|
||||
|
||||
# Glide 4.9.0(米盟广告图片加载依赖)
|
||||
-keep public class * implements com.bumptech.glide.module.GlideModule
|
||||
-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
|
||||
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
tools:replace="android:icon"
|
||||
android:icon="@drawable/ic_launcher_beta">
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="cc.winboll.studio.powerbell.beta.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_provider"/>
|
||||
|
||||
</provider>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="app_name">PowerBell☆</string>
|
||||
<string name="app_name_cn1">能源钟★</string>
|
||||
<string name="app_name_cn2">泡额呗额☆</string>
|
||||
|
||||
</resources>
|
||||
|
||||
46
powerbell/src/beta/res/xml/shortcutsmaincn1.xml
Normal file
46
powerbell/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.powerbell.App.ACTION_SWITCHTO_EN1"
|
||||
android:targetPackage="cc.winboll.studio.powerbell.beta"
|
||||
android:targetClass="cc.winboll.studio.powerbell.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.powerbell.App.ACTION_SWITCHTO_CN1"
|
||||
android:targetPackage="cc.winboll.studio.powerbell.beta"
|
||||
android:targetClass="cc.winboll.studio.powerbell.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.powerbell.App.ACTION_SWITCHTO_CN2"
|
||||
android:targetPackage="cc.winboll.studio.powerbell.beta"
|
||||
android:targetClass="cc.winboll.studio.powerbell.activities.ShortcutActionActivity"
|
||||
android:data="switchto_cn2" />
|
||||
<categories android:name="android.shortcut.conversation" />
|
||||
</shortcut>
|
||||
</shortcuts>
|
||||
46
powerbell/src/beta/res/xml/shortcutsmaincn2.xml
Normal file
46
powerbell/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.powerbell.App.ACTION_SWITCHTO_EN1"
|
||||
android:targetPackage="cc.winboll.studio.powerbell.beta"
|
||||
android:targetClass="cc.winboll.studio.powerbell.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.powerbell.App.ACTION_SWITCHTO_CN1"
|
||||
android:targetPackage="cc.winboll.studio.powerbell.beta"
|
||||
android:targetClass="cc.winboll.studio.powerbell.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.powerbell.App.ACTION_SWITCHTO_CN2"
|
||||
android:targetPackage="cc.winboll.studio.powerbell.beta"
|
||||
android:targetClass="cc.winboll.studio.powerbell.activities.ShortcutActionActivity"
|
||||
android:data="switchto_cn2" />
|
||||
<categories android:name="android.shortcut.conversation" />
|
||||
</shortcut>-->
|
||||
</shortcuts>
|
||||
46
powerbell/src/beta/res/xml/shortcutsmainen1.xml
Normal file
46
powerbell/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.powerbell.App.ACTION_SWITCHTO_EN1"
|
||||
android:targetPackage="cc.winboll.studio.powerbell.beta"
|
||||
android:targetClass="cc.winboll.studio.powerbell.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.powerbell.App.ACTION_SWITCHTO_CN1"
|
||||
android:targetPackage="cc.winboll.studio.powerbell.beta"
|
||||
android:targetClass="cc.winboll.studio.powerbell.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.powerbell.App.ACTION_SWITCHTO_CN2"
|
||||
android:targetPackage="cc.winboll.studio.powerbell.beta"
|
||||
android:targetClass="cc.winboll.studio.powerbell.activities.ShortcutActionActivity"
|
||||
android:data="switchto_cn2" />
|
||||
<categories android:name="android.shortcut.conversation" />
|
||||
</shortcut>
|
||||
</shortcuts>
|
||||
@@ -1,13 +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"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="cc.winboll.studio.powerbell">
|
||||
|
||||
<!-- 通过GPS得到精确位置 -->
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<!-- 通过网络得到粗略位置 -->
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
|
||||
<!-- 只能在前台获取精确的位置信息 -->
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||
|
||||
<!-- 只有在前台运行时才能获取大致位置信息 -->
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||
|
||||
<!-- 拍摄照片和视频 -->
|
||||
<uses-permission android:name="android.permission.CAMERA"/>
|
||||
@@ -36,22 +37,22 @@
|
||||
<!-- BATTERY_STATS -->
|
||||
<uses-permission android:name="android.permission.BATTERY_STATS"/>
|
||||
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
<!-- 计算应用存储空间 -->
|
||||
<uses-permission android:name="android.permission.GET_PACKAGE_SIZE"/>
|
||||
|
||||
<uses-feature android:name="android.hardware.camera"/>
|
||||
|
||||
<uses-feature android:name="android.hardware.camera.autofocus"/>
|
||||
|
||||
<!-- 1. 基础应用信息读取权限(Android 11 及以下) -->
|
||||
<uses-permission android:name="android.permission.GET_PACKAGE_SIZE" />
|
||||
<uses-permission android:name="android.permission.GET_PACKAGE_SIZE"/>
|
||||
|
||||
<!-- 2. Android 11+ 应用列表读取权限(必须声明,否则无法获取全部应用) -->
|
||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||
tools:ignore="QueryAllPackagesPermission" />
|
||||
<uses-permission
|
||||
android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||
tools:ignore="QueryAllPackagesPermission"/>
|
||||
|
||||
<!-- 3. 可选:若需读取系统应用,添加此权限(部分机型需要) -->
|
||||
<uses-permission android:name="android.permission.ACCESS_PACKAGE_USAGE_STATS"
|
||||
tools:ignore="ProtectedPermissions" />
|
||||
<uses-permission
|
||||
android:name="android.permission.ACCESS_PACKAGE_USAGE_STATS"
|
||||
tools:ignore="ProtectedPermissions"/>
|
||||
|
||||
<application
|
||||
android:name=".App"
|
||||
@@ -67,8 +68,21 @@
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:exported="true">
|
||||
android:label="@string/app_name"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTask">
|
||||
|
||||
</activity>
|
||||
|
||||
<activity android:name=".activities.CrashActivity"/>
|
||||
|
||||
<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>
|
||||
|
||||
@@ -78,7 +92,55 @@
|
||||
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
<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_launcher"
|
||||
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_launcher"
|
||||
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="cc.winboll.studio.powerbell.activities.ClearRecordActivity"
|
||||
@@ -152,6 +214,24 @@
|
||||
|
||||
<activity android:name="cc.winboll.studio.powerbell.activities.BatteryReportActivity"/>
|
||||
|
||||
<activity android:name="cc.winboll.studio.powerbell.unittest.MainUnitTestActivity"/>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_provider"/>
|
||||
|
||||
</provider>
|
||||
|
||||
<activity android:name="cc.winboll.studio.powerbell.activities.ShortcutActionActivity"/>
|
||||
|
||||
<activity android:name="cc.winboll.studio.powerbell.activities.SettingsActivity"/>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
||||
@@ -12,8 +12,15 @@ import java.io.File;
|
||||
|
||||
public class App extends GlobalApplication {
|
||||
|
||||
public static final String TAG = "GlobalApplication";
|
||||
|
||||
public static final String TAG = "App";
|
||||
|
||||
public static final String COMPONENT_EN1 = "cc.winboll.studio.powerbell.MainActivityEN1";
|
||||
public static final String COMPONENT_CN1 = "cc.winboll.studio.powerbell.MainActivityCN1";
|
||||
public static final String COMPONENT_CN2 = "cc.winboll.studio.powerbell.MainActivityCN2";
|
||||
public static final String ACTION_SWITCHTO_EN1 = "cc.winboll.studio.powerbell.App.ACTION_SWITCHTO_EN1";
|
||||
public static final String ACTION_SWITCHTO_CN1 = "cc.winboll.studio.powerbell.App.ACTION_SWITCHTO_CN1";
|
||||
public static final String ACTION_SWITCHTO_CN2 = "cc.winboll.studio.powerbell.App.ACTION_SWITCHTO_CN2";
|
||||
|
||||
// 数据配置存储工具
|
||||
static AppConfigUtils _mAppConfigUtils;
|
||||
static AppCacheUtils _mAppCacheUtils;
|
||||
|
||||
@@ -20,9 +20,11 @@ import cc.winboll.studio.powerbell.activities.AboutActivity;
|
||||
import cc.winboll.studio.powerbell.activities.BackgroundPictureActivity;
|
||||
import cc.winboll.studio.powerbell.activities.BatteryReportActivity;
|
||||
import cc.winboll.studio.powerbell.activities.ClearRecordActivity;
|
||||
import cc.winboll.studio.powerbell.activities.SettingsActivity;
|
||||
import cc.winboll.studio.powerbell.activities.WinBoLLActivity;
|
||||
import cc.winboll.studio.powerbell.beans.BackgroundPictureBean;
|
||||
import cc.winboll.studio.powerbell.fragments.MainViewFragment;
|
||||
import cc.winboll.studio.powerbell.unittest.MainUnitTestActivity;
|
||||
import cc.winboll.studio.powerbell.utils.BackgroundPictureUtils;
|
||||
|
||||
/**
|
||||
@@ -37,6 +39,8 @@ public class MainActivity extends WinBoLLActivity {
|
||||
|
||||
public static final String TAG = "MainActivity";
|
||||
|
||||
private static final int REQUEST_WRITE_STORAGE_PERMISSION = 1001;
|
||||
|
||||
// private static final String PRIVACY_FILE = "privacy_pfs";
|
||||
// private static final String PRIVACY_VALUE = "privacy_value";//0: 拒绝,1:赞同
|
||||
//
|
||||
@@ -81,7 +85,7 @@ public class MainActivity extends WinBoLLActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
mADsBannerView = findViewById(R.id.adsbanner);
|
||||
|
||||
|
||||
// mContainer = findViewById(R.id.ads_container);
|
||||
//
|
||||
// // 初始化主线程Handler(关键:确保广告操作在主线程执行)
|
||||
@@ -119,7 +123,7 @@ public class MainActivity extends WinBoLLActivity {
|
||||
// if (mMainHandler != null) {
|
||||
// mMainHandler.removeCallbacksAndMessages(null);
|
||||
// }
|
||||
if(mADsBannerView != null) {
|
||||
if (mADsBannerView != null) {
|
||||
mADsBannerView.releaseAdResources();
|
||||
}
|
||||
}
|
||||
@@ -165,7 +169,7 @@ public class MainActivity extends WinBoLLActivity {
|
||||
public static void reloadBackground() {
|
||||
// 修复:添加非空校验,避免Activity已销毁时调用
|
||||
if (_mMainActivity != null && !_mMainActivity.isFinishing() && !_mMainActivity.isDestroyed()) {
|
||||
_mMainActivity.mMainViewFragment.loadBackground();
|
||||
_mMainActivity.mMainViewFragment.reloadBackground();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,8 +198,8 @@ public class MainActivity extends WinBoLLActivity {
|
||||
super.onResume();
|
||||
reloadBackground();
|
||||
setBackgroundColor();
|
||||
if(mADsBannerView != null) {
|
||||
mADsBannerView.resumeADs();
|
||||
if (mADsBannerView != null) {
|
||||
mADsBannerView.resumeADs(MainActivity.this);
|
||||
}
|
||||
|
||||
// // 修复:优化广告请求逻辑(添加生命周期判断 + 主线程执行)
|
||||
@@ -221,6 +225,9 @@ public class MainActivity extends WinBoLLActivity {
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
mMenu = menu;
|
||||
getMenuInflater().inflate(R.menu.toolbar_main, mMenu);
|
||||
if (App.isDebugging()) {
|
||||
getMenuInflater().inflate(R.menu.toolbar_unittest, mMenu);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -228,7 +235,9 @@ public class MainActivity extends WinBoLLActivity {
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
super.onOptionsItemSelected(item);
|
||||
int menuItemId = item.getItemId();
|
||||
if (menuItemId == R.id.action_about) {
|
||||
if (menuItemId == R.id.action_settings) {
|
||||
startActivity(new Intent(this, SettingsActivity.class));
|
||||
} else if (menuItemId == R.id.action_about) {
|
||||
startActivity(new Intent(this, AboutActivity.class));
|
||||
} else if (menuItemId == R.id.action_battery_report) {
|
||||
startActivity(new Intent(this, BatteryReportActivity.class));
|
||||
@@ -238,6 +247,8 @@ public class MainActivity extends WinBoLLActivity {
|
||||
startActivity(new Intent(this, BackgroundPictureActivity.class));
|
||||
} else if (menuItemId == R.id.action_log) {
|
||||
LogActivity.startLogActivity(this);
|
||||
} else if (menuItemId == R.id.action_unittestactivity) {
|
||||
startActivity(new Intent(this, MainUnitTestActivity.class));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ public class AboutActivity extends Activity {
|
||||
appInfo.setAppGitOwner("Studio");
|
||||
appInfo.setAppGitAPPBranch(szBranchName);
|
||||
appInfo.setAppGitAPPSubProjectFolder(szBranchName);
|
||||
appInfo.setAppHomePage("https://discuz.winboll.cc/forum.php?mod=viewthread&tid=1");
|
||||
appInfo.setAppHomePage("https://www.winboll.cc/apks/index.php?project=PowerBell");
|
||||
appInfo.setAppAPKName("PowerBell");
|
||||
appInfo.setAppAPKFolderName("PowerBell");
|
||||
return new AboutView(mContext, appInfo);
|
||||
|
||||
@@ -7,16 +7,16 @@ import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.MediaStore;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import cc.winboll.studio.libaes.dialogs.YesNoAlertDialog;
|
||||
import cc.winboll.studio.libaes.views.AToolbar;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
@@ -24,9 +24,11 @@ import cc.winboll.studio.powerbell.App;
|
||||
import cc.winboll.studio.powerbell.R;
|
||||
import cc.winboll.studio.powerbell.beans.BackgroundPictureBean;
|
||||
import cc.winboll.studio.powerbell.dialogs.BackgroundPicturePreviewDialog;
|
||||
import cc.winboll.studio.powerbell.dialogs.NetworkBackgroundDialog;
|
||||
import cc.winboll.studio.powerbell.utils.BackgroundPictureUtils;
|
||||
import cc.winboll.studio.powerbell.utils.FileUtils;
|
||||
import cc.winboll.studio.powerbell.utils.UriUtil;
|
||||
import cc.winboll.studio.powerbell.views.BackgroundView;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
@@ -53,6 +55,10 @@ public class BackgroundPictureActivity extends WinBoLLActivity implements Backgr
|
||||
private File mfTempCropPicture; // 剪裁临时文件
|
||||
private File mfRecivedCropPicture; // 剪裁后的目标文件
|
||||
|
||||
private String preViewFileBackgroundView = "";
|
||||
BackgroundView bvPreviewBackground;
|
||||
boolean isCommitSettings = false;
|
||||
|
||||
// 静态变量
|
||||
public static String _mszRecivedCropPicture = "RecivedCrop.jpg";
|
||||
private static String _mszCommonFileType = "jpeg";
|
||||
@@ -102,7 +108,7 @@ public class BackgroundPictureActivity extends WinBoLLActivity implements Backgr
|
||||
mAToolbar.setNavigationOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
finish(); // 点击导航栏返回按钮,触发 finish()
|
||||
}
|
||||
});
|
||||
|
||||
@@ -157,31 +163,36 @@ public class BackgroundPictureActivity extends WinBoLLActivity implements Backgr
|
||||
*/
|
||||
public void updatePreviewBackground() {
|
||||
LogUtils.d(TAG, "updatePreviewBackground");
|
||||
ImageView ivPreviewBackground = (ImageView) findViewById(R.id.activitybackgroundpictureImageView1);
|
||||
//ImageView ivPreviewBackground = (ImageView) findViewById(R.id.activitybackgroundpictureImageView1);
|
||||
bvPreviewBackground = (BackgroundView) findViewById(R.id.activitybackgroundpictureBackgroundView1);
|
||||
BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(this);
|
||||
utils.loadBackgroundPictureBean();
|
||||
|
||||
boolean isUseBackgroundFile = utils.getBackgroundPictureBean().isUseBackgroundFile();
|
||||
if (isUseBackgroundFile && mfRecivedCropPicture.exists()) {
|
||||
try {
|
||||
String filePath = utils.getBackgroundDir() + getBackgroundFileName();
|
||||
Drawable drawable = FileUtils.getImageDrawable(filePath);
|
||||
if (drawable != null) {
|
||||
//drawable.setAlpha(120);
|
||||
ivPreviewBackground.setImageDrawable(drawable);
|
||||
}
|
||||
//ToastUtils.show("背景图片已更新");
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||
ToastUtils.show("背景图片加载失败");
|
||||
}
|
||||
//try {
|
||||
String filePath = utils.getBackgroundDir() + getBackgroundFileName();
|
||||
preViewFileBackgroundView = filePath;
|
||||
bvPreviewBackground.previewBackgroundImage(preViewFileBackgroundView);
|
||||
/*Drawable drawable = FileUtils.getImageDrawable(filePath);
|
||||
if (drawable != null) {
|
||||
//drawable.setAlpha(120);
|
||||
//bvPreviewBackground.setImageDrawable(drawable);
|
||||
}*/
|
||||
//ToastUtils.show("背景图片已更新");
|
||||
// } catch (IOException e) {
|
||||
// LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||
// ToastUtils.show("背景图片加载失败");
|
||||
// }
|
||||
} else {
|
||||
ToastUtils.show("未使用背景图片");
|
||||
Drawable drawable = getResources().getDrawable(R.drawable.blank10x10);
|
||||
if (drawable != null) {
|
||||
drawable.setAlpha(120);
|
||||
ivPreviewBackground.setImageDrawable(drawable);
|
||||
}
|
||||
preViewFileBackgroundView = "";
|
||||
bvPreviewBackground.previewBackgroundImage(preViewFileBackgroundView);
|
||||
// Drawable drawable = getResources().getDrawable(R.drawable.blank10x10);
|
||||
// if (drawable != null) {
|
||||
// drawable.setAlpha(120);
|
||||
// bvPreviewBackground.setImageDrawable(drawable);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,13 +444,13 @@ public class BackgroundPictureActivity extends WinBoLLActivity implements Backgr
|
||||
fos.close();
|
||||
} catch (IOException e) {
|
||||
LogUtils.e(TAG, "流关闭异常" + e);
|
||||
}
|
||||
}
|
||||
if (scaledBitmap != null && !scaledBitmap.isRecycled()) {
|
||||
scaledBitmap.recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (scaledBitmap != null && !scaledBitmap.isRecycled()) {
|
||||
scaledBitmap.recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 缩放Bitmap
|
||||
@@ -589,5 +600,60 @@ public class BackgroundPictureActivity extends WinBoLLActivity implements Backgr
|
||||
super.onResume();
|
||||
setBackgroundColor();
|
||||
}
|
||||
|
||||
public void onNetworkBackgroundDialog(View view) {
|
||||
// 在需要显示对话框的地方(如网络状态监听回调中)
|
||||
NetworkBackgroundDialog dialog = new NetworkBackgroundDialog(this, new NetworkBackgroundDialog.OnDialogClickListener() {
|
||||
@Override
|
||||
public void onConfirm() {
|
||||
ToastUtils.show("onConfirm");
|
||||
// 处理确认逻辑(如允许后台网络使用)
|
||||
LogUtils.d("MainActivity", "用户允许后台网络使用");
|
||||
// 执行具体业务:如开启后台网络请求服务
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel() {
|
||||
ToastUtils.show("onCancel");
|
||||
// 处理取消逻辑(如禁止后台网络使用)
|
||||
LogUtils.d("MainActivity", "用户禁止后台网络使用");
|
||||
// 执行具体业务:如关闭后台网络请求
|
||||
}
|
||||
});
|
||||
|
||||
// 可选:修改对话框标题和内容(适配自定义场景)
|
||||
dialog.setTitle("网络图片下载对话框");
|
||||
dialog.setContent("是否下载地址中的图片资源,作为应用背景图片?");
|
||||
|
||||
// 显示对话框
|
||||
dialog.show();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写finish方法,确保所有退出场景都触发Toast
|
||||
*/
|
||||
@Override
|
||||
public void finish() {
|
||||
if (!isCommitSettings) {
|
||||
YesNoAlertDialog.show(this, "应用背景更改提示:", "是否应用预览图片?", new YesNoAlertDialog.OnDialogResultListener(){
|
||||
|
||||
@Override
|
||||
public void onNo() {
|
||||
isCommitSettings = true;
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onYes() {
|
||||
bvPreviewBackground.saveToBackgroundSources(preViewFileBackgroundView);
|
||||
isCommitSettings = true;
|
||||
finish();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
super.finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package cc.winboll.studio.powerbell.activities;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import cc.winboll.studio.powerbell.R;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/27 14:26
|
||||
* @Describe 应用设置窗口
|
||||
*/
|
||||
public class SettingsActivity extends Activity {
|
||||
|
||||
public static final String TAG = "SettingsActivity";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_settings);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
package cc.winboll.studio.winboll.activities;
|
||||
package cc.winboll.studio.powerbell.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;
|
||||
import cc.winboll.studio.powerbell.R;
|
||||
import cc.winboll.studio.powerbell.utils.APPPlusUtils;
|
||||
import cc.winboll.studio.powerbell.App;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/27 09:00
|
||||
* @Date 2025/11/15 13:45
|
||||
* @Describe 应用快捷方式活动类
|
||||
*/
|
||||
public class ShortcutActionActivity extends Activity {
|
||||
@@ -29,7 +29,6 @@ public class ShortcutActionActivity extends Activity {
|
||||
* 处理应用图标快捷菜单的请求
|
||||
*/
|
||||
private void handleSwitchRequest() {
|
||||
//ToastUtils.show("handleSwitchRequest");
|
||||
Intent intent = getIntent();
|
||||
if (intent != null && "switchto_en1".equals(intent.getDataString())) {
|
||||
APPPlusUtils.switchAppLauncherToComponent(this, App.COMPONENT_EN1);
|
||||
@@ -0,0 +1,291 @@
|
||||
package cc.winboll.studio.powerbell.dialogs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import cc.winboll.studio.powerbell.R;
|
||||
import cc.winboll.studio.powerbell.utils.PictureUtils;
|
||||
import cc.winboll.studio.powerbell.views.BackgroundView;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/19 20:11
|
||||
* @Describe 网络后台使用提示对话框
|
||||
* 继承 AndroidX AlertDialog,绑定自定义布局 dialog_networkbackground.xml
|
||||
*/
|
||||
public class NetworkBackgroundDialog extends AlertDialog {
|
||||
public static final String TAG = "NetworkBackgroundDialog";
|
||||
// 消息标识:图片加载成功
|
||||
private static final int MSG_IMAGE_LOAD_SUCCESS = 1001;
|
||||
// 消息标识:图片加载失败
|
||||
private static final int MSG_IMAGE_LOAD_FAILED = 1002;
|
||||
|
||||
// 控件引用
|
||||
private TextView tvTitle;
|
||||
private TextView tvContent;
|
||||
private Button btnCancel;
|
||||
private Button btnConfirm;
|
||||
private Button btnPreview;
|
||||
private EditText etURL;
|
||||
BackgroundView bvBackgroundPreview;
|
||||
Context mContext;
|
||||
// 主线程 Handler,用于接收子线程消息并更新 UI
|
||||
private Handler mUiHandler;
|
||||
String previewFilePath;
|
||||
|
||||
// 按钮点击回调接口(Java7 接口实现)
|
||||
public interface OnDialogClickListener {
|
||||
void onConfirm(); // 确认按钮点击
|
||||
void onCancel(); // 取消按钮点击
|
||||
}
|
||||
|
||||
private OnDialogClickListener listener;
|
||||
|
||||
// Java7 显式构造(必须传入 Context)
|
||||
public NetworkBackgroundDialog(@NonNull Context context) {
|
||||
super(context);
|
||||
initHandler(); // 初始化 Handler
|
||||
initView(); // 初始化布局和控件
|
||||
setDismissListener(); // 设置对话框消失监听
|
||||
}
|
||||
|
||||
// 带回调的构造(便于外部处理点击事件)
|
||||
public NetworkBackgroundDialog(@NonNull Context context, OnDialogClickListener listener) {
|
||||
super(context);
|
||||
this.listener = listener;
|
||||
initHandler(); // 初始化 Handler
|
||||
initView();
|
||||
setDismissListener(); // 设置对话框消失监听
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化主线程 Handler,用于更新 UI
|
||||
*/
|
||||
private void initHandler() {
|
||||
mUiHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
super.handleMessage(msg);
|
||||
// 对话框已消失时,不再处理 UI 消息
|
||||
if (!isShowing()) {
|
||||
return;
|
||||
}
|
||||
switch (msg.what) {
|
||||
case MSG_IMAGE_LOAD_SUCCESS:
|
||||
// 图片加载成功,获取文件路径并设置背景
|
||||
String filePath = (String) msg.obj;
|
||||
setBackgroundFromPath(filePath);
|
||||
break;
|
||||
case MSG_IMAGE_LOAD_FAILED:
|
||||
// 图片加载失败,设置默认背景
|
||||
bvBackgroundPreview.setBackgroundResource(R.drawable.ic_launcher);
|
||||
ToastUtils.show("图片预览失败,请检查链接");
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置对话框消失监听:移除 Handler 消息,避免内存泄漏
|
||||
*/
|
||||
private void setDismissListener() {
|
||||
this.setOnDismissListener(new OnDismissListener() {
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
// 对话框消失时,移除所有未处理的消息和回调
|
||||
if (mUiHandler != null) {
|
||||
mUiHandler.removeCallbacksAndMessages(null);
|
||||
}
|
||||
LogUtils.d(TAG, "对话框已消失,Handler 消息已清理");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化布局和控件
|
||||
*/
|
||||
private void initView() {
|
||||
mContext = this.getContext();
|
||||
// 加载自定义布局
|
||||
View dialogView = LayoutInflater.from(getContext())
|
||||
.inflate(R.layout.dialog_networkbackground, null);
|
||||
// 设置对话框内容视图
|
||||
setView(dialogView);
|
||||
|
||||
// 绑定控件
|
||||
tvTitle = (TextView) dialogView.findViewById(R.id.tv_dialog_title);
|
||||
tvContent = (TextView) dialogView.findViewById(R.id.tv_dialog_content);
|
||||
btnCancel = (Button) dialogView.findViewById(R.id.btn_cancel);
|
||||
btnConfirm = (Button) dialogView.findViewById(R.id.btn_confirm);
|
||||
btnPreview = (Button) dialogView.findViewById(R.id.btn_preview);
|
||||
etURL = (EditText) dialogView.findViewById(R.id.et_url);
|
||||
bvBackgroundPreview = (BackgroundView) dialogView.findViewById(R.id.bv_background_preview);
|
||||
|
||||
// 设置按钮点击事件
|
||||
setButtonClickListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置按钮点击监听
|
||||
*/
|
||||
private void setButtonClickListeners() {
|
||||
// 取消按钮:关闭对话框 + 回调外部
|
||||
btnCancel.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d("NetworkBackgroundDialog", "取消按钮点击");
|
||||
dismiss(); // 关闭对话框
|
||||
if (listener != null) {
|
||||
listener.onCancel();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 确认按钮:关闭对话框 + 回调外部
|
||||
btnConfirm.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d("NetworkBackgroundDialog", "确认按钮点击");
|
||||
// 确定预览背景资源
|
||||
bvBackgroundPreview.saveToBackgroundSources(previewFilePath);
|
||||
|
||||
dismiss(); // 关闭对话框
|
||||
if (listener != null) {
|
||||
listener.onConfirm();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 图片预览按钮:预览输入框地址图片
|
||||
btnPreview.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d("NetworkBackgroundDialog", "确认预览点击");
|
||||
downloadImageToAlbumAndPreview();
|
||||
/*String url = etURL.getText().toString().trim();
|
||||
if (url.isEmpty()) {
|
||||
ToastUtils.show("请输入图片链接");
|
||||
return;
|
||||
}
|
||||
ImageDownloader.getInstance(mContext).downloadImage(url, mDownloadCallback);*/
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据文件路径设置 BackgroundView 背景(主线程调用)
|
||||
* @param filePath 图片文件路径
|
||||
*/
|
||||
private void setBackgroundFromPath(String filePath) {
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
File imageFile = new File(filePath);
|
||||
if (!imageFile.exists()) {
|
||||
LogUtils.e(TAG, "图片文件不存在:" + filePath);
|
||||
ToastUtils.show("Test");
|
||||
//bvBackgroundPreview.setBackgroundResource(R.drawable.ic_launcher);
|
||||
return;
|
||||
}
|
||||
|
||||
// 预览背景
|
||||
previewFilePath = filePath;
|
||||
bvBackgroundPreview.previewBackgroundImage(previewFilePath);
|
||||
|
||||
LogUtils.d(TAG, "图片预览成功:" + filePath);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
bvBackgroundPreview.setBackgroundResource(R.drawable.ic_launcher);
|
||||
LogUtils.e(TAG, "图片预览失败:" + e.getMessage());
|
||||
} finally {
|
||||
// Java7 手动关闭流,避免资源泄漏
|
||||
if (fis != null) {
|
||||
try {
|
||||
fis.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对外提供方法:修改对话框标题(灵活适配不同场景)
|
||||
*/
|
||||
public void setTitle(String title) {
|
||||
if (tvTitle != null) {
|
||||
tvTitle.setText(title);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对外提供方法:修改对话框内容(灵活适配不同场景)
|
||||
*/
|
||||
public void setContent(String content) {
|
||||
if (tvContent != null) {
|
||||
tvContent.setText(content);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对外提供方法:设置按钮点击回调(替代带参构造)
|
||||
*/
|
||||
public void setOnDialogClickListener(OnDialogClickListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
/*ImageDownloader.DownloadCallback mDownloadCallback = new ImageDownloader.DownloadCallback() {
|
||||
@Override
|
||||
public void onSuccess(String filePath) {
|
||||
ToastUtils.show("图片下载成功:" + filePath);
|
||||
LogUtils.d(TAG, filePath);
|
||||
// 发送消息到主线程,携带图片路径
|
||||
Message successMsg = mUiHandler.obtainMessage(MSG_IMAGE_LOAD_SUCCESS, filePath);
|
||||
mUiHandler.sendMessage(successMsg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(String errorMsg) {
|
||||
ToastUtils.show("下载失败:" + errorMsg);
|
||||
LogUtils.e(TAG, errorMsg);
|
||||
// 发送图片加载失败消息
|
||||
mUiHandler.sendEmptyMessage(MSG_IMAGE_LOAD_FAILED);
|
||||
}
|
||||
};*/
|
||||
|
||||
void downloadImageToAlbumAndPreview() {
|
||||
//String imgUrl = "https://example.com/test.jpg";
|
||||
String imgUrl = etURL.getText().toString();
|
||||
PictureUtils.downloadImageToAlbum(mContext, imgUrl, new PictureUtils.DownloadCallback(){
|
||||
@Override
|
||||
public void onSuccess(String savePath) {
|
||||
ToastUtils.show("下载成功:" + savePath);
|
||||
// 发送消息到主线程,携带图片路径
|
||||
Message successMsg = mUiHandler.obtainMessage(MSG_IMAGE_LOAD_SUCCESS, savePath);
|
||||
mUiHandler.sendMessage(successMsg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
ToastUtils.show("下载失败:" + e.getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import android.os.Message;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.ImageView;
|
||||
@@ -20,15 +19,15 @@ import android.widget.TextView;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.powerbell.App;
|
||||
import cc.winboll.studio.powerbell.R;
|
||||
import cc.winboll.studio.powerbell.activities.BackgroundPictureActivity;
|
||||
import cc.winboll.studio.powerbell.activities.PixelPickerActivity;
|
||||
import cc.winboll.studio.powerbell.beans.BackgroundPictureBean;
|
||||
import cc.winboll.studio.powerbell.services.ControlCenterService;
|
||||
import cc.winboll.studio.powerbell.utils.AppConfigUtils;
|
||||
import cc.winboll.studio.powerbell.utils.BackgroundPictureUtils;
|
||||
import cc.winboll.studio.powerbell.utils.ServiceUtils;
|
||||
import cc.winboll.studio.powerbell.views.BackgroundView;
|
||||
import cc.winboll.studio.powerbell.views.BatteryDrawable;
|
||||
import cc.winboll.studio.powerbell.views.VerticalSeekBar;
|
||||
import java.io.File;
|
||||
|
||||
public class MainViewFragment extends Fragment {
|
||||
|
||||
@@ -72,6 +71,7 @@ public class MainViewFragment extends Fragment {
|
||||
TextView mtvUsegeReminderValue;
|
||||
CheckBox mcbUsegeReminderValue;
|
||||
TextView mtvCurrentValue;
|
||||
BackgroundView bvPreviewBackground;
|
||||
|
||||
|
||||
@Override
|
||||
@@ -79,27 +79,33 @@ public class MainViewFragment extends Fragment {
|
||||
mView = inflater.inflate(R.layout.fragment_mainview, container, false);
|
||||
_mMainViewFragment = MainViewFragment.this;
|
||||
mAppConfigUtils = App.getAppConfigUtils(getActivity());
|
||||
|
||||
|
||||
// 获取指定ID的View实例
|
||||
final View mainImageView = mView.findViewById(R.id.fragmentmainviewImageView1);
|
||||
bvPreviewBackground = mView.findViewById(R.id.fragmentmainviewBackgroundView1);
|
||||
|
||||
BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(getActivity());
|
||||
BackgroundPictureBean bean = utils.getBackgroundPictureBean();
|
||||
int nPixelColor = bean.getPixelColor();
|
||||
bvPreviewBackground.setBackgroundColor(nPixelColor);
|
||||
/*final View mainImageView = mView.findViewById(R.id.fragmentmainviewImageView1);
|
||||
|
||||
// 注册OnGlobalLayoutListener
|
||||
mainImageView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
// 获取宽度和高度
|
||||
int width = mainImageView.getMeasuredWidth();
|
||||
int height = mainImageView.getMeasuredHeight();
|
||||
// 注册OnGlobalLayoutListener
|
||||
mainImageView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
// 获取宽度和高度
|
||||
int width = mainImageView.getMeasuredWidth();
|
||||
int height = mainImageView.getMeasuredHeight();
|
||||
|
||||
BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(getActivity());
|
||||
BackgroundPictureBean bean = utils.loadBackgroundPictureBean();
|
||||
bean.setBackgroundWidth(width);
|
||||
bean.setBackgroundHeight(height);
|
||||
utils.saveData();
|
||||
// 移除监听器以避免内存泄漏
|
||||
mainImageView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
}
|
||||
});
|
||||
BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(getActivity());
|
||||
BackgroundPictureBean bean = utils.loadBackgroundPictureBean();
|
||||
bean.setBackgroundWidth(width);
|
||||
bean.setBackgroundHeight(height);
|
||||
utils.saveData();
|
||||
// 移除监听器以避免内存泄漏
|
||||
mainImageView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
}
|
||||
});*/
|
||||
|
||||
mDrawableFrame = getActivity().getDrawable(R.drawable.bg_frame);
|
||||
mllLeftSeekBar = (LinearLayout) mView.findViewById(R.id.fragmentmainviewLinearLayout1);
|
||||
@@ -143,6 +149,17 @@ public class MainViewFragment extends Fragment {
|
||||
return mView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(getActivity());
|
||||
BackgroundPictureBean bean = utils.getBackgroundPictureBean();
|
||||
int nPixelColor = bean.getPixelColor();
|
||||
bvPreviewBackground.setBackgroundColor(nPixelColor);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void setViewData() {
|
||||
int nChargeReminderValue = mAppConfigUtils.getChargeReminderValue();
|
||||
int nUsegeReminderValue = mAppConfigUtils.getUsegeReminderValue();
|
||||
@@ -302,22 +319,23 @@ public class MainViewFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
public void loadBackground() {
|
||||
BackgroundPictureBean bean = BackgroundPictureUtils.getInstance(getActivity()).getBackgroundPictureBean();
|
||||
ImageView imageView = mView.findViewById(R.id.fragmentmainviewImageView1);
|
||||
String szBackgroundFilePath = BackgroundPictureUtils.getInstance(getActivity()).getBackgroundDir() + BackgroundPictureActivity.getBackgroundFileName();
|
||||
File fBackgroundFilePath = new File(szBackgroundFilePath);
|
||||
LogUtils.d(TAG, "szBackgroundFilePath : " + szBackgroundFilePath);
|
||||
LogUtils.d(TAG, String.format("fBackgroundFilePath.exists() %s", fBackgroundFilePath.exists()));
|
||||
if (bean.isUseBackgroundFile() && fBackgroundFilePath.exists()) {
|
||||
Drawable drawableBackground = Drawable.createFromPath(szBackgroundFilePath);
|
||||
//drawableBackground.setAlpha(120);
|
||||
imageView.setImageDrawable(drawableBackground);
|
||||
} else {
|
||||
Drawable drawableBackground = getActivity().getDrawable(R.drawable.blank10x10);
|
||||
//drawableBackground.setAlpha(120);
|
||||
imageView.setImageDrawable(drawableBackground);
|
||||
}
|
||||
public void reloadBackground() {
|
||||
bvPreviewBackground.reloadBackgroundImage();
|
||||
// BackgroundPictureBean bean = BackgroundPictureUtils.getInstance(getActivity()).getBackgroundPictureBean();
|
||||
// ImageView imageView = mView.findViewById(R.id.fragmentmainviewImageView1);
|
||||
// String szBackgroundFilePath = BackgroundPictureUtils.getInstance(getActivity()).getBackgroundDir() + BackgroundPictureActivity.getBackgroundFileName();
|
||||
// File fBackgroundFilePath = new File(szBackgroundFilePath);
|
||||
// LogUtils.d(TAG, "szBackgroundFilePath : " + szBackgroundFilePath);
|
||||
// LogUtils.d(TAG, String.format("fBackgroundFilePath.exists() %s", fBackgroundFilePath.exists()));
|
||||
// if (bean.isUseBackgroundFile() && fBackgroundFilePath.exists()) {
|
||||
// Drawable drawableBackground = Drawable.createFromPath(szBackgroundFilePath);
|
||||
// //drawableBackground.setAlpha(120);
|
||||
// imageView.setImageDrawable(drawableBackground);
|
||||
// } else {
|
||||
// Drawable drawableBackground = getActivity().getDrawable(R.drawable.blank10x10);
|
||||
// //drawableBackground.setAlpha(120);
|
||||
// imageView.setImageDrawable(drawableBackground);
|
||||
// }
|
||||
}
|
||||
|
||||
Handler mHandler = new Handler(){
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package cc.winboll.studio.powerbell.unittest;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import cc.winboll.studio.libappbase.GlobalApplication;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import cc.winboll.studio.powerbell.R;
|
||||
import android.widget.Button;
|
||||
import cc.winboll.studio.powerbell.MainActivity;
|
||||
import android.content.Intent;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/19 18:16
|
||||
* @Describe BackgroundViewTestFragment
|
||||
*/
|
||||
public class BackgroundViewTestFragment extends Fragment {
|
||||
|
||||
public static final String TAG = "BackgroundViewTestFragment";
|
||||
|
||||
View mainView;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
//super.onCreateView(inflater, container, savedInstanceState);
|
||||
|
||||
// 非调试状态就结束本线程
|
||||
if (!GlobalApplication.isDebugging()) {
|
||||
Thread.currentThread().destroy();
|
||||
}
|
||||
|
||||
mainView = inflater.inflate(R.layout.fragment_test_backgroundview, container, false);
|
||||
|
||||
((Button)mainView.findViewById(R.id.btn_main_activity)).setOnClickListener(new View.OnClickListener(){
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
getActivity().startActivity(new Intent(getActivity(), MainActivity.class));
|
||||
}
|
||||
});
|
||||
|
||||
ToastUtils.show(String.format("%s onCreate", TAG));
|
||||
return mainView;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package cc.winboll.studio.powerbell.unittest;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.widget.FrameLayout;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import cc.winboll.studio.libappbase.GlobalApplication;
|
||||
import cc.winboll.studio.powerbell.R;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import android.nfc.tech.TagTechnology;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/19 18:04
|
||||
* @Describe 单元测试启动主页窗口
|
||||
*/
|
||||
public class MainUnitTestActivity extends AppCompatActivity {
|
||||
|
||||
public static final String TAG = "MainUnitTestActivity";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
// 非调试状态就退出
|
||||
if (!GlobalApplication.isDebugging()) {
|
||||
finish();
|
||||
}
|
||||
setContentView(R.layout.activity_mainunittest);
|
||||
|
||||
FragmentManager fragmentManager = getSupportFragmentManager();
|
||||
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
|
||||
fragmentTransaction.add(R.id.activitymainunittestFrameLayout1, new BackgroundViewTestFragment(), BackgroundViewTestFragment.TAG);
|
||||
fragmentTransaction.commit();
|
||||
|
||||
ToastUtils.show(String.format("%s onCreate", TAG));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,10 @@
|
||||
package cc.winboll.studio.winboll.utils;
|
||||
package cc.winboll.studio.powerbell.utils;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/26 15:54
|
||||
* @Describe 应用图标切换工具类(启用组件时创建对应快捷方式)
|
||||
*/
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -7,14 +12,10 @@ 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;
|
||||
import cc.winboll.studio.powerbell.App;
|
||||
import cc.winboll.studio.powerbell.MainActivity;
|
||||
import cc.winboll.studio.powerbell.R;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/27 08:56
|
||||
* @Describe APPPlusUtils
|
||||
*/
|
||||
public class APPPlusUtils {
|
||||
public static final String TAG = "APPPlusUtils";
|
||||
|
||||
@@ -33,7 +34,7 @@ public class APPPlusUtils {
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -0,0 +1,294 @@
|
||||
package cc.winboll.studio.powerbell.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Environment;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/19 20:52
|
||||
* @Describe 图片下载工具类(单例模式)
|
||||
* 功能:下载网络图片到缓存目录、清理过期文件、获取最新下载文件
|
||||
*/
|
||||
public class ImageDownloader {
|
||||
public static final String TAG = "ImageDownloader";
|
||||
// 单例实例
|
||||
private static ImageDownloader sInstance;
|
||||
// OkHttp 客户端(全局复用,提升性能)
|
||||
private OkHttpClient mOkHttpClient;
|
||||
// 缓存目录:/data/data/应用包名/cache/networkdownload
|
||||
private File mCacheDir;
|
||||
// 过期时间:7天(单位:毫秒),可按需调整
|
||||
private static final long EXPIRE_TIME = 7 * 24 * 3600 * 1000;
|
||||
|
||||
/**
|
||||
* 私有构造(单例模式禁止外部实例化)
|
||||
* @param context 上下文(用于获取缓存目录)
|
||||
*/
|
||||
private ImageDownloader(Context context) {
|
||||
// 初始化 OkHttp 客户端(设置超时时间)
|
||||
mOkHttpClient = new OkHttpClient.Builder()
|
||||
.connectTimeout(10, TimeUnit.SECONDS)
|
||||
.readTimeout(15, TimeUnit.SECONDS)
|
||||
.writeTimeout(15, TimeUnit.SECONDS)
|
||||
.build();
|
||||
|
||||
// 初始化缓存目录:networkdownload
|
||||
initCacheDir(context);
|
||||
// 初始化时清理过期文件
|
||||
clearExpiredFiles();
|
||||
}
|
||||
|
||||
/**
|
||||
* 单例获取方法(线程安全)
|
||||
* @param context 上下文(建议使用 Application 上下文避免内存泄漏)
|
||||
* @return 单例实例
|
||||
*/
|
||||
public static synchronized ImageDownloader getInstance(Context context) {
|
||||
if (sInstance == null) {
|
||||
// 使用 Application 上下文,防止 Activity 销毁导致的内存泄漏
|
||||
sInstance = new ImageDownloader(context.getApplicationContext());
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化缓存目录:若不存在则创建
|
||||
*/
|
||||
private void initCacheDir(Context context) {
|
||||
// 获取应用内置缓存目录(无需权限)
|
||||
File cacheRoot = context.getCacheDir();
|
||||
mCacheDir = new File(cacheRoot, "networkdownload");
|
||||
|
||||
// 若目录不存在则创建(包括父目录)
|
||||
if (!mCacheDir.exists()) {
|
||||
boolean isCreated = mCacheDir.mkdirs();
|
||||
if (isCreated) {
|
||||
LogUtils.d("ImageDownloader", "networkdownload 缓存目录创建成功:" + mCacheDir.getAbsolutePath());
|
||||
} else {
|
||||
LogUtils.e("ImageDownloader", "networkdownload 缓存目录创建失败");
|
||||
}
|
||||
} else {
|
||||
LogUtils.d("ImageDownloader", "networkdownload 缓存目录已存在:" + mCacheDir.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期文件(最后修改时间超过 EXPIRE_TIME 的文件)
|
||||
*/
|
||||
private void clearExpiredFiles() {
|
||||
if (mCacheDir == null || !mCacheDir.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
File[] files = mCacheDir.listFiles();
|
||||
if (files == null || files.length == 0) {
|
||||
LogUtils.d("ImageDownloader", "缓存目录无文件,无需清理");
|
||||
return;
|
||||
}
|
||||
|
||||
long currentTime = System.currentTimeMillis();
|
||||
int deleteCount = 0;
|
||||
|
||||
// 遍历所有文件,删除过期文件
|
||||
for (File file : files) {
|
||||
long lastModifyTime = file.lastModified();
|
||||
if (currentTime - lastModifyTime > EXPIRE_TIME) {
|
||||
if (file.delete()) {
|
||||
deleteCount++;
|
||||
LogUtils.d("ImageDownloader", "删除过期文件:" + file.getName());
|
||||
} else {
|
||||
LogUtils.e("ImageDownloader", "删除过期文件失败:" + file.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LogUtils.d("ImageDownloader", "过期文件清理完成,共删除 " + deleteCount + " 个文件");
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载网络图片到缓存目录
|
||||
* @param imageUrl 图片网络链接
|
||||
* @param callback 下载结果回调(成功/失败)
|
||||
*/
|
||||
public void downloadImage(final String imageUrl, final DownloadCallback callback) {
|
||||
// 校验参数
|
||||
if (TextUtils.isEmpty(imageUrl)) {
|
||||
if (callback != null) {
|
||||
callback.onFailure("图片链接为空");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (mCacheDir == null || !mCacheDir.exists()) {
|
||||
if (callback != null) {
|
||||
callback.onFailure("缓存目录不存在");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建 OkHttp 请求
|
||||
Request request = new Request.Builder()
|
||||
.url(imageUrl)
|
||||
.build();
|
||||
|
||||
// 异步下载(避免阻塞主线程)
|
||||
mOkHttpClient.newCall(request).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(Call call, IOException e) {
|
||||
// 下载失败,回调主线程
|
||||
if (callback != null) {
|
||||
callback.onFailure("下载失败:" + e.getMessage());
|
||||
}
|
||||
LogUtils.e("ImageDownloader", "图片下载失败:" + e.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(Call call, Response response) throws IOException {
|
||||
if (!response.isSuccessful()) {
|
||||
// 响应失败(如 404、500)
|
||||
if (callback != null) {
|
||||
callback.onFailure("响应失败:" + response.code());
|
||||
}
|
||||
LogUtils.e("ImageDownloader", "图片响应失败,状态码:" + response.code());
|
||||
return;
|
||||
}
|
||||
|
||||
// 响应成功,写入文件
|
||||
InputStream inputStream = null;
|
||||
FileOutputStream outputStream = null;
|
||||
try {
|
||||
inputStream = response.body().byteStream();
|
||||
// 生成 UUID 唯一文件名(保留原文件后缀)
|
||||
String fileExtension = getFileExtension(imageUrl);
|
||||
String fileName = UUID.randomUUID().toString() + fileExtension;
|
||||
File imageFile = new File(mCacheDir, fileName);
|
||||
|
||||
// 写入文件
|
||||
outputStream = new FileOutputStream(imageFile);
|
||||
byte[] buffer = new byte[1024];
|
||||
int len;
|
||||
while ((len = inputStream.read(buffer)) != -1) {
|
||||
outputStream.write(buffer, 0, len);
|
||||
}
|
||||
outputStream.flush();
|
||||
|
||||
// 下载成功,回调主线程并返回文件路径
|
||||
if (callback != null) {
|
||||
callback.onSuccess(imageFile.getAbsolutePath());
|
||||
}
|
||||
LogUtils.d("ImageDownloader", "图片下载成功:" + imageFile.getAbsolutePath());
|
||||
|
||||
} catch (IOException e) {
|
||||
if (callback != null) {
|
||||
callback.onFailure("文件写入失败:" + e.getMessage());
|
||||
}
|
||||
LogUtils.e("ImageDownloader", "图片写入失败:" + e.getMessage());
|
||||
} finally {
|
||||
// 关闭流(Java7 手动关闭,避免资源泄漏)
|
||||
if (inputStream != null) {
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (outputStream != null) {
|
||||
try {
|
||||
outputStream.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
// 关闭响应体
|
||||
if (response.body() != null) {
|
||||
response.body().close();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 networkdownload 目录中最后下载的文件(按修改时间排序)
|
||||
* @return 最后下载的文件路径(null 表示无文件)
|
||||
*/
|
||||
public String getLastDownloadedFile() {
|
||||
if (mCacheDir == null || !mCacheDir.exists()) {
|
||||
LogUtils.e("ImageDownloader", "缓存目录不存在");
|
||||
return null;
|
||||
}
|
||||
|
||||
File[] files = mCacheDir.listFiles();
|
||||
if (files == null || files.length == 0) {
|
||||
LogUtils.d("ImageDownloader", "缓存目录无文件");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 按最后修改时间降序排序,取第一个即为最新文件
|
||||
File lastFile = files[0];
|
||||
for (File file : files) {
|
||||
if (file.lastModified() > lastFile.lastModified()) {
|
||||
lastFile = file;
|
||||
}
|
||||
}
|
||||
|
||||
LogUtils.d("ImageDownloader", "最后下载的文件:" + lastFile.getAbsolutePath());
|
||||
return lastFile.getAbsolutePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具方法:从图片链接中提取文件后缀(如 .png、.jpg)
|
||||
* @param imageUrl 图片链接
|
||||
* @return 文件后缀(含点号,若无法提取则返回 .jpg)
|
||||
*/
|
||||
private String getFileExtension(String imageUrl) {
|
||||
if (TextUtils.isEmpty(imageUrl)) {
|
||||
return ".jpg";
|
||||
}
|
||||
|
||||
int lastDotIndex = imageUrl.lastIndexOf(".");
|
||||
int lastSlashIndex = imageUrl.lastIndexOf("/");
|
||||
// 确保后缀在最后一个斜杠之后,且长度合理(1-5 个字符)
|
||||
if (lastDotIndex > lastSlashIndex && lastDotIndex < imageUrl.length() - 1) {
|
||||
String extension = imageUrl.substring(lastDotIndex);
|
||||
if (extension.length() <= 5) {
|
||||
return extension.toLowerCase(); // 统一转为小写
|
||||
}
|
||||
}
|
||||
|
||||
// 无法提取后缀时,默认使用 .jpg
|
||||
return ".jpg";
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载结果回调接口(Java7 接口实现)
|
||||
*/
|
||||
public interface DownloadCallback {
|
||||
/**
|
||||
* 下载成功
|
||||
* @param filePath 图片保存路径
|
||||
*/
|
||||
void onSuccess(String filePath);
|
||||
|
||||
/**
|
||||
* 下载失败
|
||||
* @param errorMsg 失败原因
|
||||
*/
|
||||
void onFailure(String errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
package cc.winboll.studio.powerbell.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Environment;
|
||||
import android.util.Log;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/21 18:55
|
||||
* @Describe
|
||||
* 图片下载工具类(指定目录保存:Pictures/PowerBell/BackgroundHistory)
|
||||
*/
|
||||
public class PictureUtils {
|
||||
private static final String TAG = "PictureUtils";
|
||||
private static final String ROOT_DIR = "PowerBell/BackgroundHistory"; // 自定义目录结构
|
||||
private static OkHttpClient sOkHttpClient;
|
||||
|
||||
static {
|
||||
sOkHttpClient = new OkHttpClient();
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载网络图片到指定目录(外部存储/Pictures/PowerBell/BackgroundHistory)
|
||||
* @param context 上下文(用于通知相册刷新)
|
||||
* @param imgUrl 图片网络URL
|
||||
* @param callback 下载结果回调(成功/失败)
|
||||
*/
|
||||
public static void downloadImageToAlbum(final Context context, final String imgUrl, final DownloadCallback callback) {
|
||||
// 检查参数合法性
|
||||
if (context == null) {
|
||||
if (callback != null) {
|
||||
callback.onFailure(new IllegalArgumentException("Context不能为空"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (imgUrl == null || imgUrl.isEmpty()) {
|
||||
if (callback != null) {
|
||||
callback.onFailure(new IllegalArgumentException("图片URL为空"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
startDownload(context, imgUrl, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行实际的下载逻辑
|
||||
*/
|
||||
private static void startDownload(final Context context, final String imgUrl, final DownloadCallback callback) {
|
||||
Request request = new Request.Builder().url(imgUrl).build();
|
||||
sOkHttpClient.newCall(request).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onResponse(Call call, Response response) throws IOException {
|
||||
if (!response.isSuccessful()) {
|
||||
if (callback != null) {
|
||||
callback.onFailure(new IOException("请求失败,响应码:" + response.code()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
InputStream inputStream = null;
|
||||
FileOutputStream outputStream = null;
|
||||
try {
|
||||
inputStream = response.body().byteStream();
|
||||
// 1. 获取并创建指定保存目录(外部存储/Pictures/PowerBell/BackgroundHistory)
|
||||
File saveDir = getTargetSaveDir(context);
|
||||
if (!saveDir.exists()) {
|
||||
boolean isDirCreated = saveDir.mkdirs(); // 递归创建多级目录
|
||||
if (!isDirCreated) {
|
||||
if (callback != null) {
|
||||
callback.onFailure(new IOException("创建目录失败:" + saveDir.getAbsolutePath()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 解析图片后缀
|
||||
String fileSuffix = getImageSuffix(imgUrl, response);
|
||||
// 3. 生成时间戳文件名
|
||||
String fileName = generateTimeFileName() + fileSuffix;
|
||||
// 4. 创建文件
|
||||
final File saveFile = new File(saveDir, fileName);
|
||||
|
||||
// 5. 写入文件
|
||||
outputStream = new FileOutputStream(saveFile);
|
||||
byte[] buffer = new byte[1024 * 4];
|
||||
int len;
|
||||
while ((len = inputStream.read(buffer)) != -1) {
|
||||
outputStream.write(buffer, 0, len);
|
||||
}
|
||||
outputStream.flush();
|
||||
|
||||
// 6. 通知相册刷新(使图片显示在系统相册中)
|
||||
notifyAlbumRefresh(context, saveFile);
|
||||
|
||||
// 成功回调
|
||||
if (callback != null) {
|
||||
callback.onSuccess(saveFile.getAbsolutePath());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "下载图片异常", e);
|
||||
if (callback != null) {
|
||||
callback.onFailure(e);
|
||||
}
|
||||
} finally {
|
||||
// 关闭资源
|
||||
if (inputStream != null) inputStream.close();
|
||||
if (outputStream != null) outputStream.close();
|
||||
if (response.body() != null) response.body().close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call call, final IOException e) {
|
||||
Log.e(TAG, "下载图片失败", e);
|
||||
if (callback != null) {
|
||||
callback.onFailure(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取目标保存目录:外部存储/Pictures/PowerBell/BackgroundHistory
|
||||
*/
|
||||
private static File getTargetSaveDir(Context context) {
|
||||
// 优先使用公共Pictures目录(兼容多数设备)
|
||||
File publicPicturesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
|
||||
if (publicPicturesDir.exists()) {
|
||||
return new File(publicPicturesDir, ROOT_DIR);
|
||||
}
|
||||
// 备选:应用私有Pictures目录(若公共目录不可用)
|
||||
File appPicturesDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
|
||||
if (appPicturesDir != null) {
|
||||
return new File(appPicturesDir, ROOT_DIR);
|
||||
}
|
||||
// 极端情况:外部存储根目录
|
||||
return new File(Environment.getExternalStorageDirectory(), ROOT_DIR);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析图片后缀名
|
||||
*/
|
||||
private static String getImageSuffix(String imgUrl, Response response) {
|
||||
// 优先从URL解析
|
||||
if (imgUrl.lastIndexOf('.') != -1) {
|
||||
String suffix = imgUrl.substring(imgUrl.lastIndexOf('.'));
|
||||
if (suffix.length() <= 5 && (suffix.contains("png") || suffix.contains("jpg") || suffix.contains("jpeg") || suffix.contains("gif"))) {
|
||||
return suffix.toLowerCase(Locale.getDefault());
|
||||
}
|
||||
}
|
||||
// 从响应头解析
|
||||
String contentType = response.header("Content-Type");
|
||||
if (contentType != null) {
|
||||
if (contentType.contains("png")) return ".png";
|
||||
if (contentType.contains("jpeg") || contentType.contains("jpg")) return ".jpg";
|
||||
if (contentType.contains("gif")) return ".gif";
|
||||
}
|
||||
return ".jpg"; // 默认后缀
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成时间戳文件名
|
||||
*/
|
||||
private static String generateTimeFileName() {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS", Locale.getDefault());
|
||||
return sdf.format(new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知相册刷新
|
||||
*/
|
||||
private static void notifyAlbumRefresh(Context context, File file) {
|
||||
try {
|
||||
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
|
||||
Uri uri = Uri.fromFile(file);
|
||||
intent.setData(uri);
|
||||
context.sendBroadcast(intent);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "通知相册刷新失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载结果回调接口
|
||||
*/
|
||||
public interface DownloadCallback {
|
||||
void onSuccess(String savePath); // 下载成功(子线程回调)
|
||||
void onFailure(Exception e); // 下载失败(子线程回调)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,401 @@
|
||||
package cc.winboll.studio.powerbell.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.powerbell.R;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/19 18:01
|
||||
* @Describe 背景图片视图控件(全透明背景 + 不拉伸居中平铺 + 完全填充父视图)
|
||||
*/
|
||||
public class BackgroundView extends RelativeLayout {
|
||||
|
||||
public static final String TAG = "BackgroundView";
|
||||
|
||||
Context mContext;
|
||||
private ImageView ivBackground;
|
||||
|
||||
private static String BACKGROUND_IMAGE_FOLDER = "Background";
|
||||
private static String BACKGROUND_IMAGE_FILENAME = "current.data";
|
||||
private static String BACKGROUND_IMAGE_PREVIEW_FILENAME = "current_preview.data";
|
||||
private static String backgroundSourceFilePath;
|
||||
private float imageAspectRatio = 1.0f; // 图片原始宽高比(控制不拉伸)
|
||||
// 标记当前是否处于预览模式
|
||||
private boolean isPreviewMode = false;
|
||||
|
||||
public BackgroundView(Context context) {
|
||||
super(context);
|
||||
this.mContext = context;
|
||||
initView();
|
||||
}
|
||||
|
||||
public BackgroundView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
this.mContext = context;
|
||||
initView();
|
||||
}
|
||||
|
||||
public BackgroundView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
this.mContext = context;
|
||||
initView();
|
||||
}
|
||||
|
||||
public BackgroundView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
this.mContext = context;
|
||||
initView();
|
||||
}
|
||||
|
||||
void initView() {
|
||||
// 1. 控件本身:完全填充父视图 + 全透明背景 + 无内边距
|
||||
setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
|
||||
setPadding(0, 0, 0, 0); // 取消自身内边距
|
||||
setBackgroundColor(0x00000000); // 全透明背景(#00000000)
|
||||
setBackground(new ColorDrawable(0x00000000)); // 双重保障:同时设置Background为透明(兼容低版本)
|
||||
|
||||
initBackgroundImageView(); // 初始化内部ImageView(全透明 + 不拉伸居中平铺)
|
||||
initBackgroundImagePath();
|
||||
loadAndSetImageViewBackground();
|
||||
}
|
||||
|
||||
private void initBackgroundImageView() {
|
||||
ivBackground = new ImageView(mContext);
|
||||
// 2. ImageView:初始宽高WRAP_CONTENT + 居中 + 无内边距 + 全透明背景
|
||||
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
|
||||
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
||||
layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT); // 居中显示
|
||||
layoutParams.setMargins(0, 0, 0, 0); // 取消边距
|
||||
ivBackground.setLayoutParams(layoutParams);
|
||||
|
||||
// 3. 缩放模式:FIT_CENTER(不拉伸,按比例显示)
|
||||
ivBackground.setScaleType(ImageView.ScaleType.FIT_CENTER);
|
||||
ivBackground.setPadding(0, 0, 0, 0); // 取消内部padding
|
||||
ivBackground.setBackgroundColor(0x00000000); // ImageView背景全透明
|
||||
ivBackground.setBackground(new ColorDrawable(0x00000000)); // 双重保障(兼容低版本)
|
||||
|
||||
this.addView(ivBackground);
|
||||
}
|
||||
|
||||
private void initBackgroundImagePath() {
|
||||
File externalFilesDir = mContext.getExternalFilesDir(null);
|
||||
if (externalFilesDir == null) {
|
||||
LogUtils.e(TAG, "外置存储不可用,无法初始化背景图片路径");
|
||||
return;
|
||||
}
|
||||
|
||||
File backgroundDir = new File(externalFilesDir, BACKGROUND_IMAGE_FOLDER);
|
||||
if (!backgroundDir.exists()) {
|
||||
backgroundDir.mkdirs();
|
||||
}
|
||||
backgroundSourceFilePath = new File(backgroundDir, BACKGROUND_IMAGE_FILENAME).getAbsolutePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* 拷贝图片文件到背景资源目录(正式背景)
|
||||
*/
|
||||
public void saveToBackgroundSources(String srcBackgroundPath) {
|
||||
initBackgroundImagePath();
|
||||
if (backgroundSourceFilePath == null) {
|
||||
LogUtils.e(TAG, "目标路径初始化失败,无法保存背景图片");
|
||||
return;
|
||||
}
|
||||
|
||||
File srcFile = new File(srcBackgroundPath);
|
||||
if (!srcFile.exists() || !srcFile.isFile()) {
|
||||
LogUtils.e(TAG, String.format("源文件不存在或不是文件:%s", srcBackgroundPath));
|
||||
return;
|
||||
}
|
||||
|
||||
File destFile = new File(backgroundSourceFilePath);
|
||||
File destDir = destFile.getParentFile();
|
||||
if (destDir != null && !destDir.exists()) {
|
||||
boolean isDirCreated = destDir.mkdirs();
|
||||
if (!isDirCreated) {
|
||||
LogUtils.e(TAG, "目标目录创建失败:" + destDir.getAbsolutePath());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
FileInputStream fis = null;
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fis = new FileInputStream(srcFile);
|
||||
fos = new FileOutputStream(destFile);
|
||||
|
||||
byte[] buffer = new byte[4096];
|
||||
int len;
|
||||
while ((len = fis.read(buffer)) != -1) {
|
||||
fos.write(buffer, 0, len);
|
||||
}
|
||||
fos.flush();
|
||||
|
||||
LogUtils.d(TAG, String.format("文件拷贝成功:%s -> %s", srcBackgroundPath, backgroundSourceFilePath));
|
||||
// 拷贝成功后,若处于预览模式则退出预览,加载正式背景
|
||||
if (isPreviewMode) {
|
||||
exitPreviewMode();
|
||||
} else {
|
||||
loadAndSetImageViewBackground();
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, String.format("文件拷贝失败:%s", e.getMessage()), e);
|
||||
if (destFile.exists()) {
|
||||
destFile.delete();
|
||||
LogUtils.d(TAG, "已删除损坏的目标文件");
|
||||
}
|
||||
} finally {
|
||||
if (fis != null) {
|
||||
try {
|
||||
fis.close();
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "输入流关闭失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
if (fos != null) {
|
||||
try {
|
||||
fos.close();
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "输出流关闭失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预览临时图片(全透明背景 + 不拉伸居中平铺)
|
||||
* @param previewImagePath 临时预览图片的路径
|
||||
*/
|
||||
public void previewBackgroundImage(String previewImagePath) {
|
||||
if (previewImagePath == null || previewImagePath.isEmpty()) {
|
||||
LogUtils.e(TAG, "预览图片路径为空");
|
||||
setDefaultImageViewBackground();
|
||||
return;
|
||||
}
|
||||
|
||||
File previewFile = new File(previewImagePath);
|
||||
if (!previewFile.exists() || !previewFile.isFile()) {
|
||||
LogUtils.e(TAG, "预览图片不存在或不是文件:" + previewImagePath);
|
||||
setDefaultImageViewBackground();
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算图片原始宽高比
|
||||
if (!calculateImageAspectRatio(previewFile)) {
|
||||
LogUtils.e(TAG, "预览图片尺寸无效,无法预览");
|
||||
setDefaultImageViewBackground();
|
||||
return;
|
||||
}
|
||||
|
||||
// 压缩加载预览图片(保持比例)
|
||||
Bitmap previewBitmap = decodeBitmapWithCompress(previewFile, 1080, 1920);
|
||||
if (previewBitmap == null) {
|
||||
LogUtils.e(TAG, "预览图片加载失败");
|
||||
setDefaultImageViewBackground();
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置预览图片(保持ImageView透明背景)
|
||||
Drawable previewDrawable = new BitmapDrawable(mContext.getResources(), previewBitmap);
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
|
||||
ivBackground.setBackground(previewDrawable);
|
||||
} else {
|
||||
ivBackground.setBackgroundDrawable(previewDrawable);
|
||||
}
|
||||
|
||||
// 调整ImageView尺寸(居中平铺,不拉伸)
|
||||
adjustImageViewSize();
|
||||
isPreviewMode = true;
|
||||
LogUtils.d(TAG, "进入预览模式,预览图片路径:" + previewImagePath + ",宽高比:" + imageAspectRatio);
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出预览模式,恢复显示正式背景图片
|
||||
*/
|
||||
public void exitPreviewMode() {
|
||||
if (isPreviewMode) {
|
||||
loadAndSetImageViewBackground();
|
||||
isPreviewMode = false;
|
||||
LogUtils.d(TAG, "退出预览模式,恢复正式背景");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 公共函数:供外部类调用,重新加载正式背景图片(刷新显示)
|
||||
*/
|
||||
public void reloadBackgroundImage() {
|
||||
LogUtils.d(TAG, "外部调用重新加载背景图片");
|
||||
initBackgroundImagePath();
|
||||
loadAndSetImageViewBackground();
|
||||
// 若处于预览模式,退出预览
|
||||
if (isPreviewMode) {
|
||||
isPreviewMode = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载正式背景图片并设置到 ImageView(全透明背景 + 不拉伸居中平铺)
|
||||
*/
|
||||
private void loadAndSetImageViewBackground() {
|
||||
if (backgroundSourceFilePath == null) {
|
||||
setDefaultImageViewBackground();
|
||||
return;
|
||||
}
|
||||
|
||||
File backgroundFile = new File(backgroundSourceFilePath);
|
||||
if (!backgroundFile.exists() || !backgroundFile.isFile()) {
|
||||
LogUtils.e(TAG, "背景图片不存在:" + backgroundSourceFilePath);
|
||||
setDefaultImageViewBackground();
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算图片原始宽高比
|
||||
if (!calculateImageAspectRatio(backgroundFile)) {
|
||||
setDefaultImageViewBackground();
|
||||
return;
|
||||
}
|
||||
|
||||
// 压缩加载 Bitmap(保持比例)
|
||||
Bitmap bitmap = decodeBitmapWithCompress(backgroundFile, 1080, 1920);
|
||||
if (bitmap == null) {
|
||||
LogUtils.e(TAG, "图片加载失败,无法解析为 Bitmap");
|
||||
setDefaultImageViewBackground();
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置图片(保持ImageView透明背景)
|
||||
Drawable backgroundDrawable = new BitmapDrawable(mContext.getResources(), bitmap);
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
|
||||
ivBackground.setBackground(backgroundDrawable);
|
||||
} else {
|
||||
ivBackground.setBackgroundDrawable(backgroundDrawable);
|
||||
}
|
||||
|
||||
// 调整ImageView尺寸(居中平铺,不拉伸)
|
||||
adjustImageViewSize();
|
||||
LogUtils.d(TAG, "ImageView 背景加载成功(全透明+不拉伸),宽高比:" + imageAspectRatio);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算图片原始宽高比(宽/高)
|
||||
*/
|
||||
private boolean calculateImageAspectRatio(File file) {
|
||||
try {
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeFile(file.getAbsolutePath(), options);
|
||||
|
||||
int imageWidth = options.outWidth;
|
||||
int imageHeight = options.outHeight;
|
||||
|
||||
if (imageWidth <= 0 || imageHeight <= 0) {
|
||||
LogUtils.e(TAG, "图片尺寸无效:宽=" + imageWidth + ", 高=" + imageHeight);
|
||||
return false;
|
||||
}
|
||||
|
||||
imageAspectRatio = (float) imageWidth / imageHeight;
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "计算图片宽高比失败:" + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调整ImageView尺寸(不拉伸,居中平铺)
|
||||
*/
|
||||
private void adjustImageViewSize() {
|
||||
int parentWidth = getWidth(); // 控件宽度(已填充父视图)
|
||||
int parentHeight = getHeight(); // 控件高度(已填充父视图)
|
||||
|
||||
if (parentWidth == 0 || parentHeight == 0) {
|
||||
post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
adjustImageViewSize();
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
int imageViewWidth, imageViewHeight;
|
||||
if (imageAspectRatio >= 1.0f) { // 横图
|
||||
imageViewWidth = Math.min(parentWidth, (int) (parentHeight * imageAspectRatio));
|
||||
imageViewHeight = (int) (imageViewWidth / imageAspectRatio);
|
||||
} else { // 竖图
|
||||
imageViewHeight = Math.min(parentHeight, (int) (parentWidth / imageAspectRatio));
|
||||
imageViewWidth = (int) (imageViewHeight * imageAspectRatio);
|
||||
}
|
||||
|
||||
// 应用尺寸
|
||||
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) ivBackground.getLayoutParams();
|
||||
layoutParams.width = imageViewWidth;
|
||||
layoutParams.height = imageViewHeight;
|
||||
ivBackground.setLayoutParams(layoutParams);
|
||||
|
||||
LogUtils.d(TAG, "ImageView 尺寸调整完成:宽=" + imageViewWidth + ", 高=" + imageViewHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* 带压缩的 Bitmap 解码(保持比例,避免 OOM)
|
||||
*/
|
||||
private Bitmap decodeBitmapWithCompress(File file, int maxWidth, int maxHeight) {
|
||||
try {
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeFile(file.getAbsolutePath(), options);
|
||||
|
||||
int scaleX = options.outWidth / maxWidth;
|
||||
int scaleY = options.outHeight / maxHeight;
|
||||
int inSampleSize = Math.max(scaleX, scaleY);
|
||||
if (inSampleSize <= 0) {
|
||||
inSampleSize = 1;
|
||||
}
|
||||
|
||||
options.inJustDecodeBounds = false;
|
||||
options.inSampleSize = inSampleSize;
|
||||
options.inPreferredConfig = Bitmap.Config.RGB_565;
|
||||
return BitmapFactory.decodeFile(file.getAbsolutePath(), options);
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "图片压缩加载失败:" + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认背景(全透明兜底,避免空白)
|
||||
*/
|
||||
private void setDefaultImageViewBackground() {
|
||||
// 关键:默认背景设为全透明(而非默认图,避免遮挡下层视图)
|
||||
ivBackground.setBackground(new ColorDrawable(0x00000000)); // 全透明背景
|
||||
imageAspectRatio = 1.0f;
|
||||
adjustImageViewSize();
|
||||
LogUtils.d(TAG, "已设置默认透明背景");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
adjustImageViewSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* 对外提供:判断当前是否处于预览模式
|
||||
*/
|
||||
public boolean isPreviewMode() {
|
||||
return isPreviewMode;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,169 +2,169 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#26A69A"
|
||||
android:fillColor="#FF009DCB"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
||||
@@ -21,14 +21,15 @@
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/activitybackgroundpictureRelativeLayout1"/>
|
||||
|
||||
|
||||
<ImageView
|
||||
<cc.winboll.studio.powerbell.views.BackgroundView
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/activitybackgroundpictureImageView1"
|
||||
android:layout_below="@id/toolbar">
|
||||
android:background="#FF7381FF"
|
||||
android:id="@+id/activitybackgroundpictureBackgroundView1">
|
||||
|
||||
</ImageView>
|
||||
</cc.winboll.studio.powerbell.views.BackgroundView>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
@@ -80,6 +81,23 @@
|
||||
android:layout_margin="5dp"
|
||||
android:id="@+id/activitybackgroundpictureAButton2"/>
|
||||
|
||||
<cc.winboll.studio.libaes.views.AButton
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="36dp"
|
||||
android:text="♾"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_margin="5dp"
|
||||
android:id="@+id/activitybackgroundpictureAButton9"
|
||||
android:onClick="onNetworkBackgroundDialog"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="right">
|
||||
|
||||
<cc.winboll.studio.libaes.views.AButton
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="36dp"
|
||||
@@ -103,7 +121,7 @@
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_margin="5dp"
|
||||
android:id="@+id/activitybackgroundpictureAButton7"/>
|
||||
|
||||
|
||||
<cc.winboll.studio.libaes.views.AButton
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="36dp"
|
||||
@@ -111,11 +129,12 @@
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_margin="5dp"
|
||||
android:id="@+id/activitybackgroundpictureAButton8"/>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -21,8 +21,7 @@
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/activitymainRelativeLayout1"
|
||||
android:background="#FFB7B7B7"/>
|
||||
android:id="@+id/activitymainRelativeLayout1"/>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -4,8 +4,12 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/aboutviewroot_ll">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/activitymainunittestFrameLayout1"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
19
powerbell/src/main/res/layout/activity_settings.xml
Normal file
19
powerbell/src/main/res/layout/activity_settings.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<cc.winboll.studio.libaes.views.AToolbar
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/toolbar_height"
|
||||
android:id="@+id/toolbar"
|
||||
style="@style/DefaultAToolbar"/>
|
||||
|
||||
<cc.winboll.studio.libaes.views.ADsControlView
|
||||
android:id="@+id/ads_control_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
</LinearLayout>
|
||||
93
powerbell/src/main/res/layout/dialog_networkbackground.xml
Normal file
93
powerbell/src/main/res/layout/dialog_networkbackground.xml
Normal file
@@ -0,0 +1,93 @@
|
||||
<?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="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:orientation="vertical"
|
||||
android:background="@android:color/white"
|
||||
android:padding="20dp"
|
||||
android:radius="12dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_dialog_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="网络后台提示"
|
||||
android:textSize="18sp"
|
||||
android:textColor="@android:color/black"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp">
|
||||
|
||||
<EditText
|
||||
android:layout_width="0dp"
|
||||
android:ems="10"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1.0"
|
||||
android:id="@+id/et_url"
|
||||
android:singleLine="true"/>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="↻"
|
||||
android:id="@+id/btn_preview"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_gravity="center_vertical">
|
||||
|
||||
<cc.winboll.studio.powerbell.views.BackgroundView
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
android:id="@+id/bv_background_preview"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_dialog_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="应用正在后台使用网络,是否继续允许?"
|
||||
android:textSize="15sp"
|
||||
android:textColor="@android:color/darker_gray"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="end">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_cancel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="取消"
|
||||
android:textSize="14sp"
|
||||
android:background="@android:drawable/btn_default_small"
|
||||
android:layout_marginRight="8dp"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_confirm"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="允许"
|
||||
android:textSize="14sp"
|
||||
android:background="@android:drawable/btn_default_small"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -5,12 +5,13 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
<cc.winboll.studio.powerbell.views.BackgroundView
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/fragmentmainviewImageView1">
|
||||
|
||||
</ImageView>
|
||||
android:background="#FF7381FF"
|
||||
android:id="@+id/fragmentmainviewBackgroundView1"/>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
<cc.winboll.studio.powerbell.views.BackgroundView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
android:background="#FF7381FF">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
<HorizontalScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<Switch
|
||||
android:layout_width="match_parent"
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Position Service"
|
||||
android:padding="10dp"
|
||||
android:id="@+id/fragmentmainSwitch1"/>
|
||||
android:text="Main"
|
||||
android:id="@+id/btn_main_activity"/>
|
||||
|
||||
</LinearLayout>
|
||||
</HorizontalScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
</cc.winboll.studio.powerbell.views.BackgroundView>
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
android:id="@+id/action_changepicture"
|
||||
android:title="@string/item_changepicture"/>
|
||||
<item
|
||||
android:id="@+id/action_log"
|
||||
android:title="@string/item_logview"/>
|
||||
android:id="@+id/action_settings"
|
||||
android:title="@string/item_settings"/>
|
||||
<item
|
||||
android:id="@+id/action_about"
|
||||
android:title="@string/item_aboutview"/>
|
||||
android:title="@string/item_about"/>
|
||||
</menu>
|
||||
|
||||
12
powerbell/src/main/res/menu/toolbar_unittest.xml
Normal file
12
powerbell/src/main/res/menu/toolbar_unittest.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_log"
|
||||
android:title="@string/item_logview"/>
|
||||
<item
|
||||
android:id="@+id/action_unittestactivity"
|
||||
android:title="@string/item_mainunittestactivity"/>
|
||||
|
||||
</menu>
|
||||
@@ -1,15 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">PowerBell</string>
|
||||
<string name="app_name_cn1">能源钟</string>
|
||||
<string name="app_name_cn2">泡额呗额</string>
|
||||
<string name="app_description">一个接收手机电量信息的应用,当电量值达到设定范围时会提醒用户。</string>
|
||||
<string name="about_crashed">本应用崩溃了,作者水平有限,敬请谅解!</string>
|
||||
<string name="item_mainview">Main View</string>
|
||||
<string name="item_aboutview">About</string>
|
||||
<string name="item_clearrecord">Clear Record</string>
|
||||
<string name="item_changepicture">Change Picture</string>
|
||||
<string name="item_devoloperoptionsview">Developer View</string>
|
||||
<string name="item_logview">Log View</string>
|
||||
<string name="item_sourceview">Source View</string>
|
||||
<string name="item_main">主窗口</string>
|
||||
<string name="item_about">关于</string>
|
||||
<string name="item_settings">应用设置</string>
|
||||
<string name="item_battery_report">应用耗电记录</string>
|
||||
<string name="item_mainunittestactivity">开发调试窗口</string>
|
||||
<string name="item_clearrecord">清理电量记录</string>
|
||||
<string name="item_changepicture">更换背景图片</string>
|
||||
<string name="item_devoloperoptionsview">开发调试窗口</string>
|
||||
<string name="item_logview">日志窗口</string>
|
||||
<string name="item_sourceview">源码窗口</string>
|
||||
<string name="txt_aboveswitch">消息总开关</string>
|
||||
<string name="txt_aboveswitchtips">当电量低于左边(放电状态)或高于右边(充电状态),就会发送一个提醒铃声。</string>
|
||||
<string name="texthint_CustomSlideToCleanRecord">Slide Right To Clean Up APP Record.</string>
|
||||
|
||||
@@ -1,16 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">PowerBell</string>
|
||||
<string name="app_name_cn1">能源钟</string>
|
||||
<string name="app_name_cn2">泡额呗额</string>
|
||||
<string name="app_projectname">PowerBell</string>
|
||||
<string name="app_description">A mobile app that receives battery level information from a phone and alerts the user when the battery level reaches a predefined range.</string>
|
||||
<string name="about_crashed">This application has crashed, the author level is limited, please understand!</string>
|
||||
<string name="switchto_en1">PowerBell</string>
|
||||
<string name="switchto_cn1">能源钟</string>
|
||||
<string name="switchto_cn2">泡额呗额</string>
|
||||
<string name="en1_switch_disabled">PowerBell X</string>
|
||||
<string name="cn1_switch_disabled">能源钟 X</string>
|
||||
<string name="cn2_switch_disabled">泡额呗额 X</string>
|
||||
<string name="item_mainview">Main View</string>
|
||||
<string name="item_aboutview">About</string>
|
||||
<string name="item_about">About</string>
|
||||
<string name="item_settings">Settings</string>
|
||||
<string name="item_battery_report">Battery Report</string>
|
||||
<string name="item_clearrecord">Clear Record</string>
|
||||
<string name="item_changepicture">Change Picture</string>
|
||||
<string name="item_devoloperoptionsview">Developer View</string>
|
||||
<string name="item_logview">Log View</string>
|
||||
<string name="item_mainunittestactivity">Debug Activity</string>
|
||||
<string name="item_cleanlog">Clean Log</string>
|
||||
<string name="item_sourceview">Source View</string>
|
||||
<string name="txt_aboveswitch">Message master switch</string>
|
||||
|
||||
46
powerbell/src/main/res/xml/shortcutsmaincn1.xml
Normal file
46
powerbell/src/main/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.powerbell.App.ACTION_SWITCHTO_EN1"
|
||||
android:targetPackage="cc.winboll.studio.powerbell"
|
||||
android:targetClass="cc.winboll.studio.powerbell.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.powerbell.App.ACTION_SWITCHTO_CN1"
|
||||
android:targetPackage="cc.winboll.studio.powerbell"
|
||||
android:targetClass="cc.winboll.studio.powerbell.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.powerbell.App.ACTION_SWITCHTO_CN2"
|
||||
android:targetPackage="cc.winboll.studio.powerbell"
|
||||
android:targetClass="cc.winboll.studio.powerbell.activities.ShortcutActionActivity"
|
||||
android:data="switchto_cn2" />
|
||||
<categories android:name="android.shortcut.conversation" />
|
||||
</shortcut>
|
||||
</shortcuts>
|
||||
46
powerbell/src/main/res/xml/shortcutsmaincn2.xml
Normal file
46
powerbell/src/main/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.powerbell.App.ACTION_SWITCHTO_EN1"
|
||||
android:targetPackage="cc.winboll.studio.powerbell"
|
||||
android:targetClass="cc.winboll.studio.powerbell.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.powerbell.App.ACTION_SWITCHTO_CN1"
|
||||
android:targetPackage="cc.winboll.studio.powerbell"
|
||||
android:targetClass="cc.winboll.studio.powerbell.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.powerbell.App.ACTION_SWITCHTO_CN2"
|
||||
android:targetPackage="cc.winboll.studio.powerbell"
|
||||
android:targetClass="cc.winboll.studio.powerbell.activities.ShortcutActionActivity"
|
||||
android:data="switchto_cn2" />
|
||||
<categories android:name="android.shortcut.conversation" />
|
||||
</shortcut>-->
|
||||
</shortcuts>
|
||||
@@ -9,9 +9,9 @@
|
||||
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"
|
||||
android:targetClass="cc.winboll.studio.winboll.activities.ShortcutActionActivity"
|
||||
android:action="cc.winboll.studio.powerbell.App.ACTION_SWITCHTO_EN1"
|
||||
android:targetPackage="cc.winboll.studio.powerbell"
|
||||
android:targetClass="cc.winboll.studio.powerbell.activities.ShortcutActionActivity"
|
||||
android:data="switchto_en1" />
|
||||
<categories android:name="android.shortcut.conversation" />
|
||||
</shortcut>-->
|
||||
@@ -23,9 +23,9 @@
|
||||
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"
|
||||
android:targetClass="cc.winboll.studio.winboll.activities.ShortcutActionActivity"
|
||||
android:action="cc.winboll.studio.powerbell.App.ACTION_SWITCHTO_CN1"
|
||||
android:targetPackage="cc.winboll.studio.powerbell"
|
||||
android:targetClass="cc.winboll.studio.powerbell.activities.ShortcutActionActivity"
|
||||
android:data="switchto_cn1" />
|
||||
<categories android:name="android.shortcut.conversation" />
|
||||
</shortcut>
|
||||
@@ -37,9 +37,9 @@
|
||||
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"
|
||||
android:targetClass="cc.winboll.studio.winboll.activities.ShortcutActionActivity"
|
||||
android:action="cc.winboll.studio.powerbell.App.ACTION_SWITCHTO_CN2"
|
||||
android:targetPackage="cc.winboll.studio.powerbell"
|
||||
android:targetClass="cc.winboll.studio.powerbell.activities.ShortcutActionActivity"
|
||||
android:data="switchto_cn2" />
|
||||
<categories android:name="android.shortcut.conversation" />
|
||||
</shortcut>
|
||||
@@ -4,18 +4,6 @@
|
||||
|
||||
<application>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="cc.winboll.studio.powerbell.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_provider"/>
|
||||
|
||||
</provider>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -69,7 +69,3 @@
|
||||
// Positions 项目编译设置
|
||||
//include ':positions'
|
||||
//rootProject.name = "positions"
|
||||
|
||||
// WinBoLL 项目编译设置
|
||||
//include ':winboll'
|
||||
//rootProject.name = "winboll"
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply from: '../.winboll/winboll_app_build.gradle'
|
||||
apply from: '../.winboll/winboll_lint_build.gradle'
|
||||
|
||||
def genVersionName(def versionName){
|
||||
// 检查编译标志位配置
|
||||
assert (winbollBuildProps['stageCount'] != null)
|
||||
assert (winbollBuildProps['baseVersion'] != null)
|
||||
// 保存基础版本号
|
||||
winbollBuildProps.setProperty("baseVersion", "${versionName}");
|
||||
//保存编译标志配置
|
||||
FileOutputStream fos = new FileOutputStream(winbollBuildPropsFile)
|
||||
winbollBuildProps.store(fos, "${winbollBuildPropsDesc}");
|
||||
fos.close();
|
||||
|
||||
// 返回编译版本号
|
||||
return "${versionName}." + winbollBuildProps['stageCount']
|
||||
}
|
||||
|
||||
android {
|
||||
|
||||
// 1. compileSdkVersion:必须 ≥ targetSdkVersion,建议直接等于 targetSdkVersion(30)
|
||||
compileSdkVersion 30
|
||||
|
||||
// 2. buildToolsVersion:需匹配 compileSdkVersion,建议使用 30.x.x 最新稳定版(无需高于 compileSdkVersion)
|
||||
buildToolsVersion "30.0.3" // 这是 30 对应的最新稳定版,避免使用 beta 版
|
||||
|
||||
defaultConfig {
|
||||
applicationId "cc.winboll.studio.winboll"
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 30
|
||||
versionCode 1
|
||||
// versionName 更新后需要手动设置
|
||||
// .winboll/winbollBuildProps.properties 文件的 stageCount=0
|
||||
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
|
||||
versionName "15.11"
|
||||
if(true) {
|
||||
versionName = genVersionName("${versionName}")
|
||||
}
|
||||
}
|
||||
|
||||
// 米盟 SDK
|
||||
packagingOptions {
|
||||
doNotStrip "*/*/libmimo_1011.so"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
api 'com.google.code.gson:gson:2.10.1'
|
||||
|
||||
// 下拉控件
|
||||
api 'com.baoyz.pullrefreshlayout:library:1.2.0'
|
||||
|
||||
// SSH
|
||||
api 'com.jcraft:jsch:0.1.55'
|
||||
// Html 解析
|
||||
api 'org.jsoup:jsoup:1.13.1'
|
||||
// 二维码类库
|
||||
api 'com.google.zxing:core:3.4.1'
|
||||
api 'com.journeyapps:zxing-android-embedded:3.6.0'
|
||||
// 应用介绍页类库
|
||||
api 'io.github.medyo:android-about-page:2.0.0'
|
||||
// 网络连接类库
|
||||
api 'com.squareup.okhttp3:okhttp:4.4.1'
|
||||
|
||||
// AndroidX 类库
|
||||
api 'androidx.appcompat:appcompat:1.1.0'
|
||||
//api 'com.google.android.material:material:1.4.0'
|
||||
//api 'androidx.viewpager:viewpager:1.0.0'
|
||||
//api 'androidx.vectordrawable:vectordrawable:1.1.0'
|
||||
//api 'androidx.vectordrawable:vectordrawable-animated:1.1.0'
|
||||
//api 'androidx.fragment:fragment:1.1.0'
|
||||
|
||||
// 米盟
|
||||
api 'com.miui.zeus:mimo-ad-sdk:5.3.+'//请使用最新版sdk
|
||||
//注意:以下5个库必须要引入
|
||||
//implementation 'androidx.appcompat:appcompat:1.4.1'
|
||||
api 'androidx.recyclerview:recyclerview:1.0.0'
|
||||
api 'com.google.code.gson:gson:2.8.5'
|
||||
api 'com.github.bumptech.glide:glide:4.9.0'
|
||||
//annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
|
||||
|
||||
api 'cc.winboll.studio:libaes:15.11.8'
|
||||
api 'cc.winboll.studio:libappbase:15.11.6'
|
||||
|
||||
api fileTree(dir: 'libs', include: ['*.jar'])
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Thu Dec 04 21:06:15 HKT 2025
|
||||
stageCount=6
|
||||
libraryProject=
|
||||
baseVersion=15.11
|
||||
publishVersion=15.11.5
|
||||
buildCount=0
|
||||
baseBetaVersion=15.11.6
|
||||
137
winboll/proguard-rules.pro
vendored
137
winboll/proguard-rules.pro
vendored
@@ -1,137 +0,0 @@
|
||||
# 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
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools" >
|
||||
|
||||
<application
|
||||
tools:replace="android:icon"
|
||||
android:icon="@drawable/ic_launcher_beta">
|
||||
|
||||
<!-- Put flavor specific code here -->
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
</resources>
|
||||
@@ -1,8 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,46 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,46 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,46 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,281 +0,0 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="cc.winboll.studio.winboll">
|
||||
|
||||
<!-- 拥有完全的网络访问权限 -->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
|
||||
<!-- 发送持久广播 -->
|
||||
<uses-permission android:name="android.permission.BROADCAST_STICKY"/>
|
||||
|
||||
<!-- 对正在运行的应用重新排序 -->
|
||||
<uses-permission android:name="android.permission.REORDER_TASKS"/>
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/ic_launcher_stage"
|
||||
android:roundIcon="@drawable/ic_launcher_stage"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/MyAppTheme"
|
||||
android:resizeableActivity="true"
|
||||
android:name=".App"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:networkSecurityConfig="@xml/network_security_config">
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:exported="true">
|
||||
|
||||
</activity>
|
||||
|
||||
<activity android:name=".activities.ShortcutActionActivity"/>
|
||||
|
||||
<activity-alias
|
||||
android:name=".MainActivityEN1"
|
||||
android:targetActivity=".MainActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:icon="@drawable/ic_launcher_stage"
|
||||
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"/>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -1,364 +0,0 @@
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
//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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package cc.winboll.studio.winboll;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@188.com>
|
||||
* @Date 2025/05/22 13:08
|
||||
*/
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
public class CustomToolbar extends Toolbar {
|
||||
|
||||
private View viewMain;
|
||||
|
||||
public CustomToolbar(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public CustomToolbar(Context context, @Nullable AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public CustomToolbar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
initView(context, attrs);
|
||||
}
|
||||
|
||||
private void initView(Context context, AttributeSet attrs) {
|
||||
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomToolbar);
|
||||
|
||||
// 获取属性值
|
||||
String toolbarTitle = typedArray.getString(R.styleable.CustomToolbar_toolbarTitle);
|
||||
int toolbarTitleColor = typedArray.getColor(R.styleable.CustomToolbar_toolbarTitleColor, android.graphics.Color.WHITE);
|
||||
int toolbarBackgroundColor = typedArray.getColor(R.styleable.CustomToolbar_toolbarBackgroundColor, android.graphics.Color.BLUE);
|
||||
|
||||
// 加载布局
|
||||
viewMain = LayoutInflater.from(context).inflate(R.layout.view_toolbar, this, true);
|
||||
|
||||
// 应用属性值
|
||||
TextView toolbarTitleTextView = viewMain.findViewById(R.id.toolbar_title);
|
||||
toolbarTitleTextView.setText(toolbarTitle);
|
||||
toolbarTitleTextView.setTextColor(toolbarTitleColor);
|
||||
viewMain.setBackgroundColor(toolbarBackgroundColor);
|
||||
|
||||
// 释放 TypedArray
|
||||
typedArray.recycle();
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
package cc.winboll.studio.winboll;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
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.interfaces.IWinBoLLActivity;
|
||||
import cc.winboll.studio.libaes.models.DrawerMenuBean;
|
||||
import cc.winboll.studio.libaes.unittests.TestAButtonFragment;
|
||||
import cc.winboll.studio.libaes.unittests.TestViewPageFragment;
|
||||
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.fragments.MainFragment;
|
||||
import java.util.ArrayList;
|
||||
import cc.winboll.studio.winboll.fragments.BrowserFragment;
|
||||
|
||||
public class MainActivity extends DrawerFragmentActivity implements IWinBoLLActivity {
|
||||
|
||||
|
||||
public static final String TAG = "MainActivity";
|
||||
|
||||
BrowserFragment mBrowserFragment;
|
||||
|
||||
@Override
|
||||
public Activity getActivity() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (mBrowserFragment == null) {
|
||||
mBrowserFragment = new BrowserFragment();
|
||||
addFragment(mBrowserFragment);
|
||||
}
|
||||
showFragment(mBrowserFragment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initDrawerMenuItemList(ArrayList<DrawerMenuBean> listDrawerMenu) {
|
||||
super.initDrawerMenuItemList(listDrawerMenu);
|
||||
LogUtils.d(TAG, "initDrawerMenuItemList");
|
||||
//listDrawerMenu.clear();
|
||||
// 添加抽屉菜单项
|
||||
listDrawerMenu.add(new DrawerMenuBean(R.drawable.ic_launcher, TestAButtonFragment.TAG));
|
||||
listDrawerMenu.add(new DrawerMenuBean(R.drawable.ic_launcher, TestViewPageFragment.TAG));
|
||||
notifyDrawerMenuDataChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reinitDrawerMenuItemList(ArrayList<DrawerMenuBean> listDrawerMenu) {
|
||||
super.reinitDrawerMenuItemList(listDrawerMenu);
|
||||
LogUtils.d(TAG, "reinitDrawerMenuItemList");
|
||||
//listDrawerMenu.clear();
|
||||
// 添加抽屉菜单项
|
||||
listDrawerMenu.add(new DrawerMenuBean(R.drawable.ic_launcher, TestAButtonFragment.TAG));
|
||||
listDrawerMenu.add(new DrawerMenuBean(R.drawable.ic_launcher, TestViewPageFragment.TAG));
|
||||
notifyDrawerMenuDataChanged();
|
||||
}
|
||||
|
||||
@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(cc.winboll.studio.libapputils.R.menu.toolbar_studio_debug, menu);
|
||||
// }
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
super.onItemClick(parent, view, position, id);
|
||||
switch (position) {
|
||||
case 0 : {
|
||||
if (mBrowserFragment == null) {
|
||||
mBrowserFragment = new BrowserFragment();
|
||||
addFragment(mBrowserFragment);
|
||||
}
|
||||
showFragment(mBrowserFragment);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
int nItemId = item.getItemId();
|
||||
if (item.getItemId() == R.id.item_log) {
|
||||
WinBoLLActivityManager.getInstance().startLogActivity(getApplicationContext());
|
||||
} else if (nItemId == R.id.item_about) {
|
||||
WinBoLLActivityManager.getInstance().startWinBoLLActivity(getApplicationContext(), AboutActivity.class);
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
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) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
package cc.winboll.studio.winboll.activities;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/09/29 13:30
|
||||
* @Describe 应用介绍窗口
|
||||
*/
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import cc.winboll.studio.winboll.BuildConfig;
|
||||
import cc.winboll.studio.winboll.R;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
|
||||
import cc.winboll.studio.libaes.models.APPInfo;
|
||||
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
|
||||
import cc.winboll.studio.libaes.views.AboutView;
|
||||
|
||||
public class AboutActivity extends WinBoLLActivity implements IWinBoLLActivity {
|
||||
|
||||
public static final String TAG = "AboutActivity";
|
||||
|
||||
Context mContext;
|
||||
Toolbar mToolbar;
|
||||
|
||||
@Override
|
||||
public Activity getActivity() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
mContext = this;
|
||||
setContentView(R.layout.activity_about);
|
||||
|
||||
mToolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(mToolbar);
|
||||
mToolbar.setSubtitle(TAG);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
AboutView aboutView = CreateAboutView();
|
||||
// 在 Activity 的 onCreate 或其他生命周期方法中调用
|
||||
// LinearLayout layout = new LinearLayout(this);
|
||||
// layout.setOrientation(LinearLayout.VERTICAL);
|
||||
// // 创建布局参数(宽度和高度)
|
||||
// ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
|
||||
// ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
// ViewGroup.LayoutParams.MATCH_PARENT
|
||||
// );
|
||||
// addContentView(aboutView, params);
|
||||
|
||||
LinearLayout layout = findViewById(R.id.aboutviewroot_ll);
|
||||
// 创建布局参数(宽度和高度)
|
||||
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
);
|
||||
layout.addView(aboutView, params);
|
||||
|
||||
WinBoLLActivityManager.getInstance().add(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
WinBoLLActivityManager.getInstance().registeRemove(this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public AboutView CreateAboutView() {
|
||||
String szBranchName = "winboll";
|
||||
APPInfo appInfo = new APPInfo();
|
||||
appInfo.setAppName("WinBoLL");
|
||||
appInfo.setAppIcon(cc.winboll.studio.libaes.R.drawable.ic_winboll);
|
||||
appInfo.setAppDescription("WinBoLL Description");
|
||||
appInfo.setAppGitName("APPBase");
|
||||
appInfo.setAppGitOwner("Studio");
|
||||
appInfo.setAppGitAPPBranch(szBranchName);
|
||||
appInfo.setAppGitAPPSubProjectFolder(szBranchName);
|
||||
appInfo.setAppHomePage("https://discuz.winboll.cc/forum.php?mod=viewthread&tid=3&extra=page%3D1");
|
||||
appInfo.setAppAPKName("WinBoLL");
|
||||
appInfo.setAppAPKFolderName("WinBoLL");
|
||||
//appInfo.setIsAddDebugTools(false);
|
||||
//appInfo.setIsAddDebugTools(BuildConfig.DEBUG);
|
||||
return new AboutView(mContext, appInfo);
|
||||
}
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package cc.winboll.studio.winboll.activities;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/03/25 11:46:40
|
||||
* @Describe 测试窗口2
|
||||
*/
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Toolbar;
|
||||
import cc.winboll.studio.winboll.R;
|
||||
|
||||
public class New2Activity extends WinBoLLActivity {
|
||||
|
||||
public static final String TAG = "New2Activity";
|
||||
|
||||
Toolbar mToolbar;
|
||||
//LogView mLogView;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_new2);
|
||||
|
||||
// mLogView = findViewById(R.id.logview);
|
||||
// mLogView.start();
|
||||
mToolbar = findViewById(R.id.toolbar);
|
||||
setActionBar(mToolbar);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
//mLogView.start();
|
||||
}
|
||||
|
||||
public void onCloseThisActivity(View view) {
|
||||
//WinBoLLActivityManager.getInstance().finish(this);
|
||||
}
|
||||
|
||||
public void onCloseAllActivity(View view) {
|
||||
//WinBoLLActivityManager.getInstance().finishAll();
|
||||
}
|
||||
|
||||
public void onNewActivity(View view) {
|
||||
//WinBoLLActivityManager.getInstance().startWinBoLLActivity(this, NewActivity.class);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
//getMenuInflater().inflate(R.menu.toolbar_main, menu);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
// 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
package cc.winboll.studio.winboll.activities;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/03/25 05:04:22
|
||||
* @Describe
|
||||
*/
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Toolbar;
|
||||
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
|
||||
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
|
||||
import cc.winboll.studio.winboll.R;
|
||||
import cc.winboll.studio.winboll.App;
|
||||
|
||||
public class NewActivity extends WinBoLLActivity implements IWinBoLLActivity {
|
||||
|
||||
public static final String TAG = "NewActivity";
|
||||
|
||||
Toolbar mToolbar;
|
||||
//LogView mLogView;
|
||||
|
||||
@Override
|
||||
public Activity getActivity() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_new);
|
||||
// mLogView = findViewById(R.id.logview);
|
||||
// mLogView.start();
|
||||
mToolbar = findViewById(R.id.toolbar);
|
||||
setActionBar(mToolbar);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
//mLogView.start();
|
||||
}
|
||||
|
||||
public void onCloseThisActivity(View view) {
|
||||
WinBoLLActivityManager.getInstance().finish(this);
|
||||
}
|
||||
|
||||
public void onCloseAllActivity(View view) {
|
||||
WinBoLLActivityManager.getInstance().finishAll();
|
||||
}
|
||||
|
||||
public void onNew2Activity(View view) {
|
||||
// WinBoLLActivityManager.getInstance().startWinBoLLActivity(App.getInstance(), New2Activity.class);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
//getMenuInflater().inflate(R.menu.toolbar_main, menu);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
// 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package cc.winboll.studio.winboll.activities;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/05/10 09:48
|
||||
* @Describe WinBoLL 窗口基础类
|
||||
*/
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
|
||||
public class WinBoLLActivity extends AppCompatActivity {
|
||||
|
||||
public static final String TAG = "WinBoLLActivity";
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
/*if (item.getItemId() == R.id.item_log) {
|
||||
WinBoLLActivityManager.getInstance().startLogActivity(this);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.item_home) {
|
||||
startActivity(new Intent(this, MainActivity.class));
|
||||
return true;
|
||||
}*/
|
||||
// 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostCreate(Bundle savedInstanceState) {
|
||||
super.onPostCreate(savedInstanceState);
|
||||
//WinBoLLActivityManager.getInstance().add(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
//WinBoLLActivityManager.getInstance().registeRemove(this);
|
||||
}
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,228 +0,0 @@
|
||||
package cc.winboll.studio.winboll.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
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.R;
|
||||
import cc.winboll.studio.winboll.views.WinBoLLView;
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/27 11:09
|
||||
* @Describe 浏览器Fragment(Java 7 语法完整版)
|
||||
* 适配Java 7特性,移除Lambda/方法引用,兼容低版本Android系统
|
||||
*/
|
||||
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;
|
||||
|
||||
// 单例创建方法(Java 7 静态工厂模式)
|
||||
public static BrowserFragment newInstance() {
|
||||
return new BrowserFragment();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
// 加载布局(Java 7 显式强转,无菱形语法)
|
||||
View view = inflater.inflate(R.layout.fragment_browser, container, false);
|
||||
// 初始化控件
|
||||
initViews(view);
|
||||
// 绑定事件
|
||||
initEvents();
|
||||
// 初始化WinBoLLView
|
||||
initWinBoLLView();
|
||||
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);
|
||||
// 预加载默认页面(兼容Java 7 字符串拼接)
|
||||
//String defaultUrl = "https://www.baidu.com";
|
||||
String defaultUrl = "https://www.winboll.cc";
|
||||
mWinBoLLView.loadUrlSafe(defaultUrl);
|
||||
mEtUrl.setText(defaultUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* 点击事件处理(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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageError(String errorMsg) {
|
||||
// 页面加载错误:显示错误提示
|
||||
showToast("加载失败:" + errorMsg);
|
||||
}
|
||||
|
||||
// ------------------- 生命周期管理(防止内存泄漏,Java 7 显式重写) -------------------
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
// 销毁WinBoLLView(释放资源,避免内存泄漏)
|
||||
if (mWinBoLLView != null) {
|
||||
mWinBoLLView.destroyWebView();
|
||||
mWinBoLLView = 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
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));
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,316 +0,0 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,179 +0,0 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
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.");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,222 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
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();
|
||||
}*/
|
||||
}
|
||||
}
|
||||
@@ -1,282 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,232 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user