Compare commits
89 Commits
aes-v15.11
...
powerbell
| Author | SHA1 | Date | |
|---|---|---|---|
| eab26dbba2 | |||
| 5d4a5f25ad | |||
| 858b874ea1 | |||
| 2e41aae853 | |||
| 56a9a2f476 | |||
| 2a2c006264 | |||
| df51b415fb | |||
| 19e6e276bd | |||
| 7f3c91fb1d | |||
| 4434221827 | |||
| 75415956eb | |||
| c930308425 | |||
| 8a16728609 | |||
| 59992542c4 | |||
| fe5dd9e1ab | |||
| 0d99057880 | |||
| ca66120e55 | |||
| ca1850fe8a | |||
| 4a31b9eef0 | |||
| c6a6826102 | |||
| 16c44e5e0e | |||
| 8fb7147333 | |||
| e190d3ff39 | |||
| a997fb01c8 | |||
| 637d4577df | |||
| 33f1b430a4 | |||
| f6a00fac36 | |||
| 5d3d46f2fe | |||
| ed660aa4ef | |||
| ee4b0ca6d9 | |||
| 6538ebafef | |||
| 1cadc4ed93 | |||
| 04b8906a96 | |||
| e14744b2ac | |||
| 4e7b7daa42 | |||
| f7ef8f6b19 | |||
| 6951f642a1 | |||
| a6b25eaf2b | |||
| 80363c6b4c | |||
| 66e3e602e5 | |||
| 9b010df881 | |||
| 3c3bcc4ee4 | |||
| 21c712f7b3 | |||
| e408b5cbde | |||
| 5a9469317d | |||
| 0d5f7f40cd | |||
| 64e5bc753a | |||
| 13b6af6921 | |||
| e2703495ae | |||
| c9bc5c88d8 | |||
| 34356b8116 | |||
| de189c3fb0 | |||
| c4b2ecaecb | |||
| 1b53594086 | |||
| 6a2d011ceb | |||
| 5aa54091e5 | |||
| bc8a63867c | |||
| 2a10cb493d | |||
| ea7e2f8366 | |||
| d073a86b9b | |||
| e76427eac8 | |||
| af53216af3 | |||
| 3ae56bb202 | |||
| 09f1974c8e | |||
| 721c93c4b2 | |||
| 5a9a138463 | |||
| ae601c1445 | |||
| c5cd274b0f | |||
| 07a53da918 | |||
| 2aaf18f29f | |||
| 9892f3de2d | |||
|
|
c06a325c42 | ||
|
|
7897100659 | ||
|
|
51793077bd | ||
| ada29fb2b4 | |||
|
|
306f62f7ca | ||
|
|
50e2bd375d | ||
| 0f5d2cb34e | |||
|
|
9cec7bccdc | ||
|
|
faf8c39fe9 | ||
| 2480c8c1f0 | |||
|
|
81950699b3 | ||
| 47ea47cddc | |||
|
|
2404a9c532 | ||
|
|
82518af2d6 | ||
|
|
bb98d6bb1b | ||
|
|
230038f6f3 | ||
|
|
f8980446a8 | ||
|
|
643b84aece |
@@ -28,12 +28,17 @@ android {
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
signingConfig signingConfigs.winboll
|
||||
}
|
||||
debug {
|
||||
signingConfig signingConfigs.winboll
|
||||
}
|
||||
signingConfig signingConfigs.winboll
|
||||
}
|
||||
release {
|
||||
signingConfig signingConfigs.winboll
|
||||
minifyEnabled true // 开启混淆(核心开关)
|
||||
shrinkResources true // 可选:移除无用资源(进一步减小体积)
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), // 官方默认规则(优化版)
|
||||
'proguard-rules.pro' // 自定义规则文件
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
flavorDimensions "WinBoLLApp"
|
||||
@@ -71,6 +76,7 @@ android {
|
||||
// 2. 配置 Beta Debug 版应用包输出
|
||||
//
|
||||
if((variant.flavorName == "beta" && variant.buildType.name == "debug")
|
||||
|| (variant.flavorName == "beta" && variant.buildType.name == "release")
|
||||
|| (variant.flavorName == "stage" && variant.buildType.name == "debug")
|
||||
|| (variant.flavorName == "stage" && variant.buildType.name == "release")) {
|
||||
println "Project root directory: " + project.rootDir.toString()
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Fri Nov 21 03:15:34 HKT 2025
|
||||
stageCount=6
|
||||
#Wed Nov 19 09:04:33 HKT 2025
|
||||
stageCount=5
|
||||
libraryProject=libaes
|
||||
baseVersion=15.11
|
||||
publishVersion=15.11.5
|
||||
publishVersion=15.11.4
|
||||
buildCount=0
|
||||
baseBetaVersion=15.11.6
|
||||
baseBetaVersion=15.11.5
|
||||
|
||||
@@ -83,11 +83,11 @@ public class AboutActivity extends WinBoLLActivity implements IWinBoLLActivity {
|
||||
appInfo.setAppGitOwner("Studio");
|
||||
appInfo.setAppGitAPPBranch(szBranchName);
|
||||
appInfo.setAppGitAPPSubProjectFolder(szBranchName);
|
||||
appInfo.setAppHomePage("https://www.winboll.cc/apks/index.php?project=AES");
|
||||
appInfo.setAppHomePage("https://discuz.winboll.cc/forum.php?mod=viewthread&tid=3&extra=page%3D1");
|
||||
appInfo.setAppAPKName("AES");
|
||||
appInfo.setAppAPKFolderName("AES");
|
||||
//appInfo.setIsAddDebugTools(false);
|
||||
//appInfo.setIsAddDebugTools(BuildConfig.DEBUG);
|
||||
appInfo.setIsAddDebugTools(BuildConfig.DEBUG);
|
||||
return new AboutView(mContext, appInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,13 +38,12 @@ android {
|
||||
versionName = genVersionName("${versionName}")
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
// 确保 Java 7 兼容性(已适配项目技术栈)
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Tue Nov 18 07:02:48 GMT 2025
|
||||
stageCount=1
|
||||
#Fri Nov 21 11:41:04 HKT 2025
|
||||
stageCount=2
|
||||
libraryProject=libappbase
|
||||
baseVersion=15.11
|
||||
publishVersion=15.11.0
|
||||
buildCount=7
|
||||
baseBetaVersion=15.11.1
|
||||
publishVersion=15.11.1
|
||||
buildCount=0
|
||||
baseBetaVersion=15.11.2
|
||||
|
||||
109
appbase/proguard-rules.pro
vendored
109
appbase/proguard-rules.pro
vendored
@@ -15,3 +15,112 @@
|
||||
#-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 public class * extends com.winboll.WinBoLLActivity
|
||||
#-keep public class * extends com.winboll.WinBoLLFragment
|
||||
# 主包名
|
||||
-keep class cc.winboll.studio.*.** { *; }
|
||||
# beta包名
|
||||
-keep class cc.winboll.studio.*.beta.** { *; }
|
||||
-keepclassmembers class cc.winboll.studio.*.** { *; }
|
||||
-keepclassmembers class cc.winboll.studio.*.beta.** { *; }
|
||||
|
||||
# 保留所有类中的 public static final String TAG 字段
|
||||
-keepclassmembers class * {
|
||||
public static final java.lang.String TAG;
|
||||
}
|
||||
|
||||
# 保留序列化类
|
||||
-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 文件
|
||||
-keepclassmembers class **.R$* {
|
||||
public static <fields>;
|
||||
}
|
||||
|
||||
# 保留 native 方法
|
||||
-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.**
|
||||
|
||||
# ============================== 第三方框架规则 ==============================
|
||||
# Retrofit + OkHttp
|
||||
-keep class retrofit2.** { *; }
|
||||
-keep interface retrofit2.** { *; }
|
||||
-keep class okhttp3.** { *; }
|
||||
-keep interface okhttp3.** { *; }
|
||||
-keep class okio.** { *; }
|
||||
-keepclasseswithmembers class * {
|
||||
@retrofit2.http.* <methods>;
|
||||
}
|
||||
|
||||
# Glide 4.x
|
||||
-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 *;
|
||||
}
|
||||
-dontwarn com.bumptech.glide.load.resource.bitmap.VideoDecoder
|
||||
|
||||
# GreenDAO 3.x
|
||||
-keepclassmembers class * extends org.greenrobot.greendao.AbstractDao {
|
||||
public static java.lang.String TABLENAME;
|
||||
}
|
||||
-keep class **$Properties
|
||||
# 实体类包名(按实际调整)
|
||||
#-keep class cc.winboll.studio.appbase.model.** { *; }
|
||||
|
||||
# ButterKnife 8.x
|
||||
-keep class butterknife.** { *; }
|
||||
-dontwarn butterknife.internal.**
|
||||
-keep class **$$ViewBinder { *; }
|
||||
-keepclasseswithmembernames class * {
|
||||
@butterknife.BindView <fields>;
|
||||
@butterknife.OnClick <methods>;
|
||||
}
|
||||
|
||||
# EventBus 3.x
|
||||
-keepclassmembers class ** {
|
||||
@org.greenrobot.eventbus.Subscribe <methods>;
|
||||
}
|
||||
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
|
||||
|
||||
# ============================== 优化与调试 ==============================
|
||||
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
|
||||
-optimizationpasses 5
|
||||
-verbose
|
||||
-dontpreverify
|
||||
-dontusemixedcaseclassnames
|
||||
# 保留行号(便于崩溃定位)
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Fri Nov 21 03:15:34 HKT 2025
|
||||
stageCount=6
|
||||
#Wed Nov 19 09:04:27 HKT 2025
|
||||
stageCount=5
|
||||
libraryProject=libaes
|
||||
baseVersion=15.11
|
||||
publishVersion=15.11.5
|
||||
publishVersion=15.11.4
|
||||
buildCount=0
|
||||
baseBetaVersion=15.11.6
|
||||
baseBetaVersion=15.11.5
|
||||
|
||||
@@ -193,9 +193,8 @@ public class AboutView extends LinearLayout {
|
||||
elementGitWeb.setOnClickListener(mGitWebOnClickListener);
|
||||
// 定义检查更新按钮
|
||||
//
|
||||
/*Element elementAppUpdate = new Element(_mContext.getString(R.string.app_update), R.drawable.ic_winboll);
|
||||
Element elementAppUpdate = new Element(_mContext.getString(R.string.app_update), R.drawable.ic_winboll);
|
||||
elementAppUpdate.setOnClickListener(mAppUpdateOnClickListener);
|
||||
*/
|
||||
|
||||
String szAppInfo = "";
|
||||
try {
|
||||
@@ -215,8 +214,8 @@ public class AboutView extends LinearLayout {
|
||||
//.addGroup("Connect with us")
|
||||
.addEmail("ZhanGSKen<zhangsken@qq.com>")
|
||||
.addWebsite(mszHomePage)
|
||||
.addItem(elementGitWeb);
|
||||
//.addItem(elementAppUpdate);
|
||||
.addItem(elementGitWeb)
|
||||
.addItem(elementAppUpdate);
|
||||
//.addFacebook("the.medy")
|
||||
//.addTwitter("medyo80")
|
||||
//.addYoutube("UCdPQtdWIsg7_pi4mrRu46vA")
|
||||
@@ -225,7 +224,7 @@ public class AboutView extends LinearLayout {
|
||||
//.addInstagram("medyo80")
|
||||
//.create();
|
||||
|
||||
/*if (mAPPInfo.isAddDebugTools()) {
|
||||
if (mAPPInfo.isAddDebugTools()) {
|
||||
// 定义应用调试按钮
|
||||
//
|
||||
Element elementAppMode;
|
||||
@@ -237,7 +236,7 @@ public class AboutView extends LinearLayout {
|
||||
elementAppMode.setOnClickListener(mAppDebugOnClickListener);
|
||||
}
|
||||
aboutPage.addItem(elementAppMode);
|
||||
}*/
|
||||
}
|
||||
|
||||
return aboutPage.create();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Tue Nov 18 07:02:48 GMT 2025
|
||||
stageCount=1
|
||||
#Fri Nov 21 11:41:04 HKT 2025
|
||||
stageCount=2
|
||||
libraryProject=libappbase
|
||||
baseVersion=15.11
|
||||
publishVersion=15.11.0
|
||||
buildCount=7
|
||||
baseBetaVersion=15.11.1
|
||||
publishVersion=15.11.1
|
||||
buildCount=0
|
||||
baseBetaVersion=15.11.2
|
||||
|
||||
@@ -33,20 +33,13 @@ android {
|
||||
// versionName 更新后需要手动设置
|
||||
// .winboll/winbollBuildProps.properties 文件的 stageCount=0
|
||||
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
|
||||
versionName "15.11"
|
||||
versionName "15.12"
|
||||
if(true) {
|
||||
versionName = genVersionName("${versionName}")
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
// 米盟
|
||||
// 米盟 SDK
|
||||
packagingOptions {
|
||||
doNotStrip "*/*/libmimo_1011.so"
|
||||
}
|
||||
@@ -55,14 +48,20 @@ 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'
|
||||
|
||||
// uCrop 核心依赖(最新稳定版)
|
||||
implementation 'com.github.yalantis:ucrop:2.2.8'
|
||||
// 兼容AndroidX(若项目用AndroidX,必须添加)
|
||||
//implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'androidx.exifinterface:exifinterface:1.3.6'
|
||||
|
||||
// 应用介绍页类库
|
||||
api 'io.github.medyo:android-about-page:2.0.0'
|
||||
// SSH
|
||||
@@ -83,8 +82,8 @@ 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:libappbase:15.11.0'
|
||||
implementation 'cc.winboll.studio:libaes:15.11.8'
|
||||
implementation 'cc.winboll.studio:libappbase:15.11.6'
|
||||
|
||||
//api fileTree(dir: 'libs', include: ['*.aar'])
|
||||
api fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Wed Nov 19 09:09:24 HKT 2025
|
||||
stageCount=4
|
||||
#Thu Dec 04 10:29:58 GMT 2025
|
||||
stageCount=1
|
||||
libraryProject=
|
||||
baseVersion=15.11
|
||||
publishVersion=15.11.3
|
||||
buildCount=0
|
||||
baseBetaVersion=15.11.4
|
||||
baseVersion=15.12
|
||||
publishVersion=15.12.0
|
||||
buildCount=6
|
||||
baseBetaVersion=15.12.1
|
||||
|
||||
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"
|
||||
@@ -88,7 +150,7 @@
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="cc.winboll.studio.powerbell.activities.BackgroundPictureActivity"
|
||||
android:name="cc.winboll.studio.powerbell.activities.BackgroundSettingsActivity"
|
||||
android:parentActivityName="cc.winboll.studio.powerbell.MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTask">
|
||||
@@ -152,6 +214,31 @@
|
||||
|
||||
<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"/>
|
||||
|
||||
<!-- 1. 注册 UCropActivity(关键:解决崩溃) -->
|
||||
<activity
|
||||
android:name="com.yalantis.ucrop.UCropActivity"
|
||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
|
||||
android:exported="true"> <!-- 必须添加:Android 12+ 要求显式声明 exported -->
|
||||
</activity>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -2,7 +2,6 @@ package cc.winboll.studio.powerbell;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Environment;
|
||||
import android.view.Gravity;
|
||||
import cc.winboll.studio.libappbase.GlobalApplication;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import cc.winboll.studio.powerbell.receivers.GlobalApplicationReceiver;
|
||||
@@ -12,8 +11,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;
|
||||
@@ -27,6 +33,7 @@ public class App extends GlobalApplication {
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
//setIsDebugging(false);
|
||||
setIsDebugging(BuildConfig.DEBUG);
|
||||
|
||||
// 临时文件夹方案1
|
||||
|
||||
@@ -16,14 +16,18 @@ import androidx.appcompat.widget.Toolbar;
|
||||
import cc.winboll.studio.libaes.views.ADsBannerView;
|
||||
import cc.winboll.studio.libappbase.LogActivity;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import cc.winboll.studio.powerbell.activities.AboutActivity;
|
||||
import cc.winboll.studio.powerbell.activities.BackgroundPictureActivity;
|
||||
import cc.winboll.studio.powerbell.activities.BackgroundSettingsActivity;
|
||||
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.utils.BackgroundPictureUtils;
|
||||
import cc.winboll.studio.powerbell.model.BackgroundBean;
|
||||
import cc.winboll.studio.powerbell.unittest.MainUnitTestActivity;
|
||||
import cc.winboll.studio.powerbell.utils.BackgroundSourceUtils;
|
||||
import cc.winboll.studio.powerbell.utils.PermissionUtils;
|
||||
|
||||
/**
|
||||
* 主活动类(修复小米广告SDK空Context崩溃问题)
|
||||
@@ -37,6 +41,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 +87,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(关键:确保广告操作在主线程执行)
|
||||
@@ -106,6 +112,8 @@ public class MainActivity extends WinBoLLActivity {
|
||||
tx.commit();
|
||||
}
|
||||
showFragment(mMainViewFragment);
|
||||
|
||||
PermissionUtils.getInstance().checkAndRequestStoragePermission(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -119,7 +127,7 @@ public class MainActivity extends WinBoLLActivity {
|
||||
// if (mMainHandler != null) {
|
||||
// mMainHandler.removeCallbacksAndMessages(null);
|
||||
// }
|
||||
if(mADsBannerView != null) {
|
||||
if (mADsBannerView != null) {
|
||||
mADsBannerView.releaseAdResources();
|
||||
}
|
||||
}
|
||||
@@ -165,7 +173,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 +202,8 @@ public class MainActivity extends WinBoLLActivity {
|
||||
super.onResume();
|
||||
reloadBackground();
|
||||
setBackgroundColor();
|
||||
if(mADsBannerView != null) {
|
||||
mADsBannerView.resumeADs();
|
||||
if (mADsBannerView != null) {
|
||||
mADsBannerView.resumeADs(MainActivity.this);
|
||||
}
|
||||
|
||||
// // 修复:优化广告请求逻辑(添加生命周期判断 + 主线程执行)
|
||||
@@ -221,6 +229,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,16 +239,20 @@ 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));
|
||||
} else if (menuItemId == R.id.action_clearrecord) {
|
||||
startActivity(new Intent(this, ClearRecordActivity.class));
|
||||
} else if (menuItemId == R.id.action_changepicture) {
|
||||
startActivity(new Intent(this, BackgroundPictureActivity.class));
|
||||
startActivity(new Intent(this, BackgroundSettingsActivity.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;
|
||||
}
|
||||
@@ -270,8 +285,8 @@ public class MainActivity extends WinBoLLActivity {
|
||||
if (isFinishing() || isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(this);
|
||||
BackgroundPictureBean bean = utils.getBackgroundPictureBean();
|
||||
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(this);
|
||||
BackgroundBean bean = utils.getCurrentBackgroundBean();
|
||||
int nPixelColor = bean.getPixelColor();
|
||||
RelativeLayout mainLayout = findViewById(R.id.activitymainRelativeLayout1);
|
||||
if (mainLayout != null) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,593 +0,0 @@
|
||||
package cc.winboll.studio.powerbell.activities;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.MediaStore;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import cc.winboll.studio.libaes.views.AToolbar;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
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.utils.BackgroundPictureUtils;
|
||||
import cc.winboll.studio.powerbell.utils.FileUtils;
|
||||
import cc.winboll.studio.powerbell.utils.UriUtil;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class BackgroundPictureActivity extends WinBoLLActivity implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
|
||||
|
||||
public static final String TAG = "BackgroundPictureActivity";
|
||||
public BackgroundPictureUtils mBackgroundPictureUtils;
|
||||
|
||||
// 图片选择请求码
|
||||
public static final int REQUEST_SELECT_PICTURE = 0;
|
||||
public static final int REQUEST_TAKE_PHOTO = 1;
|
||||
public static final int REQUEST_CROP_IMAGE = 2;
|
||||
private static final int STORAGE_PERMISSION_REQUEST = 100;
|
||||
|
||||
private AToolbar mAToolbar;
|
||||
private File mfBackgroundDir; // 背景图片存储文件夹
|
||||
private File mfPictureDir; // 拍照与剪裁临时文件夹
|
||||
private File mfTakePhoto; // 拍照文件
|
||||
private File mfRecivedPicture; // 接收的图片文件
|
||||
private File mfTempCropPicture; // 剪裁临时文件
|
||||
private File mfRecivedCropPicture; // 剪裁后的目标文件
|
||||
|
||||
// 静态变量
|
||||
public static String _mszRecivedCropPicture = "RecivedCrop.jpg";
|
||||
private static String _mszCommonFileType = "jpeg";
|
||||
private int mnPictureCompress = 100;
|
||||
private static String _RecivedPictureFileName;
|
||||
|
||||
@Override
|
||||
public Activity getActivity() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_backgroundpicture);
|
||||
initEnv();
|
||||
|
||||
// 初始化工具类和文件夹
|
||||
mBackgroundPictureUtils = BackgroundPictureUtils.getInstance(this);
|
||||
mfBackgroundDir = new File(mBackgroundPictureUtils.getBackgroundDir());
|
||||
if (!mfBackgroundDir.exists()) {
|
||||
mfBackgroundDir.mkdirs();
|
||||
}
|
||||
|
||||
mfPictureDir = new File(App.getTempDirPath());
|
||||
if (!mfPictureDir.exists()) {
|
||||
mfPictureDir.mkdirs();
|
||||
}
|
||||
|
||||
// 初始化文件对象
|
||||
mfTakePhoto = new File(mfPictureDir, "TakePhoto.jpg");
|
||||
mfTempCropPicture = new File(mfPictureDir, "TempCrop.jpg");
|
||||
|
||||
mfRecivedPicture = getRecivedPictureFile(this);
|
||||
mfRecivedCropPicture = new File(mfBackgroundDir, _mszRecivedCropPicture);
|
||||
|
||||
// 初始化工具栏
|
||||
mAToolbar = (AToolbar) findViewById(R.id.toolbar);
|
||||
setActionBar(mAToolbar);
|
||||
mAToolbar.setSubtitle(R.string.subtitle_activity_backgroundpicture);
|
||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
mAToolbar.setNavigationOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
// 设置按钮点击事件
|
||||
findViewById(R.id.activitybackgroundpictureAButton5).setOnClickListener(onOriginNullClickListener);
|
||||
findViewById(R.id.activitybackgroundpictureAButton4).setOnClickListener(onReceivedPictureClickListener);
|
||||
findViewById(R.id.activitybackgroundpictureAButton1).setOnClickListener(onTakePhotoClickListener);
|
||||
findViewById(R.id.activitybackgroundpictureAButton2).setOnClickListener(onSelectPictureClickListener);
|
||||
findViewById(R.id.activitybackgroundpictureAButton3).setOnClickListener(onCropPictureClickListener);
|
||||
findViewById(R.id.activitybackgroundpictureAButton6).setOnClickListener(onCropFreePictureClickListener);
|
||||
findViewById(R.id.activitybackgroundpictureAButton7).setOnClickListener(onPixelPickerClickListener);
|
||||
findViewById(R.id.activitybackgroundpictureAButton8).setOnClickListener(onCleanPixelClickListener);
|
||||
|
||||
updatePreviewBackground();
|
||||
|
||||
// 处理分享的图片
|
||||
Intent intent = getIntent();
|
||||
String action = intent.getAction();
|
||||
String type = intent.getType();
|
||||
|
||||
if (Intent.ACTION_SEND.equals(action) && type != null && isImageType(type)) {
|
||||
BackgroundPicturePreviewDialog dlg = new BackgroundPicturePreviewDialog(this);
|
||||
dlg.show();
|
||||
}
|
||||
}
|
||||
|
||||
private void initEnv() {
|
||||
LogUtils.d(TAG, "initEnv()");
|
||||
_RecivedPictureFileName = "Recived.data";
|
||||
}
|
||||
|
||||
public static String getBackgroundFileName() {
|
||||
return _mszRecivedCropPicture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAcceptRecivedPicture(String szPreRecivedPictureName) {
|
||||
BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(this);
|
||||
utils.getBackgroundPictureBean().setIsUseBackgroundFile(true);
|
||||
utils.saveData();
|
||||
|
||||
File sourceFile = new File(utils.getBackgroundDir(), szPreRecivedPictureName);
|
||||
if (FileUtils.copyFile(sourceFile, mfRecivedPicture)) {
|
||||
startCropImageActivity(false);
|
||||
} else {
|
||||
ToastUtils.show("图片复制失败,请重试");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新背景图片预览
|
||||
*/
|
||||
public void updatePreviewBackground() {
|
||||
LogUtils.d(TAG, "updatePreviewBackground");
|
||||
ImageView ivPreviewBackground = (ImageView) findViewById(R.id.activitybackgroundpictureImageView1);
|
||||
BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(this);
|
||||
utils.loadBackgroundPictureBean();
|
||||
|
||||
boolean isUseBackgroundFile = utils.getBackgroundPictureBean().isUseBackgroundFile();
|
||||
if (isUseBackgroundFile && mfRecivedCropPicture.exists()) {
|
||||
try {
|
||||
String filePath = utils.getBackgroundDir() + getBackgroundFileName();
|
||||
Drawable drawable = FileUtils.getImageDrawable(filePath);
|
||||
if (drawable != null) {
|
||||
//drawable.setAlpha(120);
|
||||
ivPreviewBackground.setImageDrawable(drawable);
|
||||
}
|
||||
//ToastUtils.show("背景图片已更新");
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||
ToastUtils.show("背景图片加载失败");
|
||||
}
|
||||
} else {
|
||||
ToastUtils.show("未使用背景图片");
|
||||
Drawable drawable = getResources().getDrawable(R.drawable.blank10x10);
|
||||
if (drawable != null) {
|
||||
drawable.setAlpha(120);
|
||||
ivPreviewBackground.setImageDrawable(drawable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 点击事件监听器
|
||||
private View.OnClickListener onOriginNullClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(BackgroundPictureActivity.this);
|
||||
BackgroundPictureBean bean = utils.getBackgroundPictureBean();
|
||||
bean.setIsUseBackgroundFile(false);
|
||||
utils.saveData();
|
||||
updatePreviewBackground();
|
||||
}
|
||||
};
|
||||
|
||||
private View.OnClickListener onSelectPictureClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (checkAndRequestStoragePermission()) {
|
||||
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
|
||||
startActivityForResult(intent, REQUEST_SELECT_PICTURE);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private View.OnClickListener onCropPictureClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
File fCheck = new File(mfBackgroundDir, getBackgroundFileName());
|
||||
if (fCheck.exists()) {
|
||||
startCropImageActivity(false);
|
||||
} else {
|
||||
ToastUtils.show("没有可剪裁的图片");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private View.OnClickListener onCropFreePictureClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
File fCheck = new File(mfBackgroundDir, getBackgroundFileName());
|
||||
if (fCheck.exists()) {
|
||||
startCropImageActivity(true);
|
||||
} else {
|
||||
ToastUtils.show("没有可剪裁的图片");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private View.OnClickListener onTakePhotoClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d(TAG, "onTakePhotoClickListener");
|
||||
LogUtils.d(TAG, "mfTakePhoto : " + mfTakePhoto.getPath());
|
||||
|
||||
if (mfTakePhoto.exists()) {
|
||||
mfTakePhoto.delete();
|
||||
}
|
||||
try {
|
||||
mfTakePhoto.createNewFile();
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||
ToastUtils.show("拍照文件创建失败");
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkAndRequestStoragePermission()) {
|
||||
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
||||
startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private View.OnClickListener onReceivedPictureClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(BackgroundPictureActivity.this);
|
||||
utils.getBackgroundPictureBean().setIsUseBackgroundFile(true);
|
||||
utils.saveData();
|
||||
updatePreviewBackground();
|
||||
}
|
||||
};
|
||||
|
||||
private View.OnClickListener onPixelPickerClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// 从文件路径启动像素拾取活动
|
||||
//String imagePath = "/storage/emulated/0/DCIM/Camera/sample.jpg";
|
||||
String imagePath = mfRecivedCropPicture.toString();
|
||||
Intent intent = new Intent(getApplicationContext(), PixelPickerActivity.class);
|
||||
intent.putExtra("imagePath", imagePath);
|
||||
startActivity(intent);
|
||||
//App.getWinBoLLActivityManager().startWinBoLLActivity(getActivity(), intent, PixelPickerActivity.class);
|
||||
}
|
||||
};
|
||||
|
||||
private View.OnClickListener onCleanPixelClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(BackgroundPictureActivity.this);
|
||||
BackgroundPictureBean bean = utils.getBackgroundPictureBean();
|
||||
bean.setPixelColor(0);
|
||||
utils.saveData();
|
||||
setBackgroundColor();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 压缩图片并保存到接收文件
|
||||
*/
|
||||
void compressQualityToRecivedPicture(Bitmap bitmap) {
|
||||
OutputStream outStream = null;
|
||||
try {
|
||||
mfRecivedPicture = getRecivedPictureFile(this);
|
||||
if (!mfRecivedPicture.exists()) {
|
||||
mfRecivedPicture.createNewFile();
|
||||
}
|
||||
|
||||
FileOutputStream fos = new FileOutputStream(mfRecivedPicture);
|
||||
outStream = new BufferedOutputStream(fos);
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outStream);
|
||||
outStream.flush();
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||
ToastUtils.show("图片压缩失败");
|
||||
} finally {
|
||||
if (outStream != null) {
|
||||
try {
|
||||
outStream.close();
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||
}
|
||||
}
|
||||
if (bitmap != null && !bitmap.isRecycled()) {
|
||||
bitmap.recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动图片裁剪活动
|
||||
* @param isCropFree 是否自由裁剪
|
||||
*/
|
||||
public void startCropImageActivity(boolean isCropFree) {
|
||||
LogUtils.d(TAG, "startCropImageActivity");
|
||||
BackgroundPictureBean bean = mBackgroundPictureUtils.loadBackgroundPictureBean();
|
||||
mfRecivedPicture = getRecivedPictureFile(this);
|
||||
Uri uri = UriUtil.getUriForFile(this, mfRecivedPicture);
|
||||
LogUtils.d(TAG, "uri : " + uri.toString());
|
||||
|
||||
if (mfTempCropPicture.exists()) {
|
||||
mfTempCropPicture.delete();
|
||||
}
|
||||
try {
|
||||
mfTempCropPicture.createNewFile();
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||
ToastUtils.show("剪裁临时文件创建失败");
|
||||
return;
|
||||
}
|
||||
|
||||
Uri cropOutPutUri = Uri.fromFile(mfTempCropPicture);
|
||||
LogUtils.d(TAG, "mfTempCropPicture : " + mfTempCropPicture.getPath());
|
||||
|
||||
Intent intent = new Intent("com.android.camera.action.CROP");
|
||||
intent.setDataAndType(uri, "image/" + _mszCommonFileType);
|
||||
intent.putExtra("crop", "true");
|
||||
intent.putExtra("noFaceDetection", true);
|
||||
|
||||
if (!isCropFree) {
|
||||
intent.putExtra("aspectX", bean.getBackgroundWidth());
|
||||
intent.putExtra("aspectY", bean.getBackgroundHeight());
|
||||
}
|
||||
|
||||
intent.putExtra("return-data", true);
|
||||
intent.putExtra(MediaStore.EXTRA_OUTPUT, cropOutPutUri);
|
||||
intent.putExtra("scale", true);
|
||||
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
startActivityForResult(intent, REQUEST_CROP_IMAGE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存剪裁后的Bitmap(优化版)
|
||||
*/
|
||||
private void saveCropBitmap(Bitmap bitmap) {
|
||||
if (bitmap == null) {
|
||||
ToastUtils.show("剪裁图片为空");
|
||||
return;
|
||||
}
|
||||
|
||||
// 内存优化:大图片自动缩放
|
||||
Bitmap scaledBitmap = bitmap;
|
||||
if (bitmap.getByteCount() > 10 * 1024 * 1024) { // 超过10MB
|
||||
float scale = 1.0f;
|
||||
while (scaledBitmap.getByteCount() > 5 * 1024 * 1024) {
|
||||
scale -= 0.2f; // 每次缩小20%
|
||||
if (scale < 0.2f) break; // 最小缩放到20%
|
||||
scaledBitmap = scaleBitmap(scaledBitmap, scale);
|
||||
}
|
||||
if (scaledBitmap != bitmap) {
|
||||
bitmap.recycle(); // 回收原Bitmap
|
||||
}
|
||||
}
|
||||
|
||||
// 优化:创建保存目录
|
||||
File backgroundDir = new File(mBackgroundPictureUtils.getBackgroundDir());
|
||||
if (!backgroundDir.exists()) {
|
||||
if (!backgroundDir.mkdirs()) {
|
||||
ToastUtils.show("无法创建保存目录");
|
||||
if (scaledBitmap != bitmap) scaledBitmap.recycle();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
File saveFile = new File(backgroundDir, getBackgroundFileName());
|
||||
|
||||
// 优化:检查文件是否可写
|
||||
if (saveFile.exists() && !saveFile.canWrite()) {
|
||||
if (!saveFile.delete()) {
|
||||
ToastUtils.show("无法删除旧文件");
|
||||
if (scaledBitmap != bitmap) scaledBitmap.recycle();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(saveFile);
|
||||
boolean success = scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 80, fos);
|
||||
fos.flush();
|
||||
if (success) {
|
||||
ToastUtils.show("保存成功");
|
||||
// 更新数据
|
||||
mBackgroundPictureUtils.getBackgroundPictureBean().setIsUseBackgroundFile(true);
|
||||
updatePreviewBackground();
|
||||
} else {
|
||||
ToastUtils.show("图片压缩保存失败");
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
LogUtils.e(TAG, "文件未找到" + e);
|
||||
ToastUtils.show("保存失败:文件路径错误");
|
||||
} catch (IOException e) {
|
||||
LogUtils.e(TAG, "写入异常" + e);
|
||||
ToastUtils.show("保存失败:磁盘可能已满或路径错误");
|
||||
} finally {
|
||||
if (fos != null) {
|
||||
try {
|
||||
fos.close();
|
||||
} catch (IOException e) {
|
||||
LogUtils.e(TAG, "流关闭异常" + e);
|
||||
}
|
||||
}
|
||||
if (scaledBitmap != null && !scaledBitmap.isRecycled()) {
|
||||
scaledBitmap.recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 缩放Bitmap
|
||||
*/
|
||||
private Bitmap scaleBitmap(Bitmap original, float scale) {
|
||||
if (original == null) {
|
||||
return null;
|
||||
}
|
||||
int width = (int) (original.getWidth() * scale);
|
||||
int height = (int) (original.getHeight() * scale);
|
||||
return Bitmap.createScaledBitmap(original, width, height, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分享图片
|
||||
*/
|
||||
void sharePicture() {
|
||||
Uri uri = UriUtil.getUriForFile(this, mfRecivedPicture);
|
||||
Intent shareIntent = new Intent(Intent.ACTION_SEND);
|
||||
shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
|
||||
shareIntent.setType("image/" + _mszCommonFileType);
|
||||
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
startActivity(Intent.createChooser(shareIntent, "Share Image"));
|
||||
}
|
||||
|
||||
public static File getRecivedPictureFile(Context context) {
|
||||
BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(context);
|
||||
utils.loadBackgroundPictureBean();
|
||||
return new File(utils.getBackgroundDir(), _RecivedPictureFileName);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (requestCode == REQUEST_SELECT_PICTURE && resultCode == RESULT_OK) {
|
||||
try {
|
||||
Uri selectedImage = data.getData();
|
||||
LogUtils.d(TAG, "Uri is : " + selectedImage.toString());
|
||||
File fSrcImage = new File(UriUtil.getFilePathFromUri(this, selectedImage));
|
||||
mfRecivedPicture = getRecivedPictureFile(this);
|
||||
if (FileUtils.copyFile(fSrcImage, mfRecivedPicture)) {
|
||||
startCropImageActivity(false);
|
||||
} else {
|
||||
ToastUtils.show("图片复制失败,请重试");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "选择图片异常" + e);
|
||||
ToastUtils.show("选择图片失败:" + e.getMessage());
|
||||
}
|
||||
} else if (requestCode == REQUEST_TAKE_PHOTO && resultCode == RESULT_OK) {
|
||||
LogUtils.d(TAG, "REQUEST_TAKE_PHOTO");
|
||||
Bundle extras = data.getExtras();
|
||||
if (extras != null) {
|
||||
Bitmap imageBitmap = (Bitmap) extras.get("data");
|
||||
if (imageBitmap != null) {
|
||||
compressQualityToRecivedPicture(imageBitmap);
|
||||
startCropImageActivity(false);
|
||||
} else {
|
||||
ToastUtils.show("拍照图片为空");
|
||||
}
|
||||
} else {
|
||||
ToastUtils.show("拍照数据获取失败");
|
||||
}
|
||||
} else if (requestCode == REQUEST_CROP_IMAGE && resultCode == RESULT_OK) {
|
||||
LogUtils.d(TAG, "CROP_IMAGE_REQUEST_CODE");
|
||||
try {
|
||||
Bitmap cropBitmap = null;
|
||||
// 方案1:通过Intent获取剪裁后的Bitmap
|
||||
if (data != null && data.hasExtra("data")) {
|
||||
cropBitmap = data.getParcelableExtra("data");
|
||||
} else if (mfTempCropPicture.exists()) {
|
||||
cropBitmap = BitmapFactory.decodeFile(mfTempCropPicture.getPath());
|
||||
} else {
|
||||
ToastUtils.show("剪裁文件不存在");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cropBitmap != null) {
|
||||
saveCropBitmap(cropBitmap);
|
||||
} else {
|
||||
ToastUtils.show("获取剪裁图片失败");
|
||||
}
|
||||
} catch (OutOfMemoryError e) {
|
||||
LogUtils.e(TAG, "内存溢出" + e);
|
||||
ToastUtils.show("保存失败:内存不足,请尝试裁剪更小的图片");
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "剪裁保存异常" + e);
|
||||
ToastUtils.show("保存失败:" + e.getMessage());
|
||||
}/* finally {
|
||||
// 安全删除临时文件
|
||||
if (mfTempCropPicture.exists()) {
|
||||
mfTempCropPicture.delete();
|
||||
}
|
||||
}*/
|
||||
} else if (resultCode != RESULT_OK) {
|
||||
LogUtils.d(TAG, "操作取消或失败,requestCode: " + requestCode);
|
||||
ToastUtils.show("操作已取消");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查类型是否为图片
|
||||
*/
|
||||
private boolean isImageType(String type) {
|
||||
return type.startsWith("image/") || "image/jpeg".equals(type) ||
|
||||
"image/jpg".equals(type) || "image/png".equals(type) ||
|
||||
"image/webp".equals(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查并申请存储权限
|
||||
*/
|
||||
private boolean checkAndRequestStoragePermission() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
||||
ActivityCompat.requestPermissions(this,
|
||||
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
||||
STORAGE_PERMISSION_REQUEST);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
if (requestCode == STORAGE_PERMISSION_REQUEST) {
|
||||
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
ToastUtils.show("存储权限已获取");
|
||||
} else {
|
||||
ToastUtils.show("需要存储权限才能保存图片");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setBackgroundColor() {
|
||||
BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(BackgroundPictureActivity.this);
|
||||
BackgroundPictureBean bean = utils.getBackgroundPictureBean();
|
||||
int nPixelColor = bean.getPixelColor();
|
||||
RelativeLayout mainLayout = findViewById(R.id.activitybackgroundpictureRelativeLayout1);
|
||||
mainLayout.setBackgroundColor(nPixelColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
setBackgroundColor();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,771 @@
|
||||
package cc.winboll.studio.powerbell.activities;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.CompressFormat;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.provider.MediaStore;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.content.FileProvider;
|
||||
import cc.winboll.studio.libaes.views.AToolbar;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import cc.winboll.studio.powerbell.App;
|
||||
import cc.winboll.studio.powerbell.R;
|
||||
import cc.winboll.studio.powerbell.dialogs.BackgroundPicturePreviewDialog;
|
||||
import cc.winboll.studio.powerbell.dialogs.YesNoAlertDialog;
|
||||
import cc.winboll.studio.powerbell.model.BackgroundBean;
|
||||
import cc.winboll.studio.powerbell.utils.BackgroundSourceUtils;
|
||||
import cc.winboll.studio.powerbell.utils.ImageCropUtils;
|
||||
import cc.winboll.studio.powerbell.utils.PermissionUtils;
|
||||
import cc.winboll.studio.powerbell.views.BackgroundView;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class BackgroundSettingsActivity extends WinBoLLActivity implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
|
||||
|
||||
public static final String TAG = "BackgroundSettingsActivity";
|
||||
private BackgroundSourceUtils mBgSourceUtils;
|
||||
private PermissionUtils mPermissionUtils;
|
||||
|
||||
public static final int REQUEST_SELECT_PICTURE = 0;
|
||||
public static final int REQUEST_TAKE_PHOTO = 1;
|
||||
public static final int REQUEST_CROP_IMAGE = 2;
|
||||
|
||||
private AToolbar mAToolbar;
|
||||
private BackgroundView mBackgroundView;
|
||||
private File mfTakePhoto;
|
||||
volatile boolean isCommitSettings = false;
|
||||
|
||||
@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_background_settings);
|
||||
|
||||
mBackgroundView = (BackgroundView) findViewById(R.id.background_view);
|
||||
mBgSourceUtils = BackgroundSourceUtils.getInstance(this);
|
||||
mPermissionUtils = PermissionUtils.getInstance();
|
||||
|
||||
File tempDir = new File(App.getTempDirPath());
|
||||
if (!tempDir.exists()) {
|
||||
tempDir.mkdirs();
|
||||
}
|
||||
mfTakePhoto = new File(tempDir, "TakePhoto.jpg");
|
||||
|
||||
File selectTempDir = new File(mBgSourceUtils.getBackgroundSourceDirPath(), "SelectTemp");
|
||||
if (!selectTempDir.exists()) {
|
||||
selectTempDir.mkdirs();
|
||||
LogUtils.d(TAG, "【选图初始化】选图临时目录创建完成:" + selectTempDir.getAbsolutePath());
|
||||
}
|
||||
|
||||
initToolbar();
|
||||
initClickListeners();
|
||||
initBackgroundViewByPreviewBean();
|
||||
handleShareIntent();
|
||||
|
||||
LogUtils.d(TAG, "【初始化】BackgroundSettingsActivity 初始化完成");
|
||||
}
|
||||
|
||||
private void initBackgroundViewByPreviewBean() {
|
||||
LogUtils.d(TAG, "【Bean初始化】正式Bean → 预览Bean");
|
||||
mBgSourceUtils.setCurrentSourceToPreview();
|
||||
doubleRefreshPreview();
|
||||
LogUtils.d(TAG, "【Bean初始化】预览Bean初始化完成");
|
||||
}
|
||||
|
||||
private void initToolbar() {
|
||||
mAToolbar = (AToolbar) findViewById(R.id.toolbar);
|
||||
setActionBar(mAToolbar);
|
||||
mAToolbar.setSubtitle(R.string.subtitle_activity_backgroundpicture);
|
||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
mAToolbar.setNavigationOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d(TAG, "【导航栏】点击返回");
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initClickListeners() {
|
||||
findViewById(R.id.activitybackgroundpictureAButton5).setOnClickListener(onOriginNullClickListener);
|
||||
findViewById(R.id.activitybackgroundpictureAButton4).setOnClickListener(onReceivedPictureClickListener);
|
||||
findViewById(R.id.activitybackgroundpictureAButton1).setOnClickListener(onTakePhotoClickListener);
|
||||
findViewById(R.id.activitybackgroundpictureAButton2).setOnClickListener(onSelectPictureClickListener);
|
||||
findViewById(R.id.activitybackgroundpictureAButton3).setOnClickListener(onCropPictureClickListener);
|
||||
findViewById(R.id.activitybackgroundpictureAButton6).setOnClickListener(onCropFreePictureClickListener);
|
||||
findViewById(R.id.activitybackgroundpictureAButton7).setOnClickListener(onPixelPickerClickListener);
|
||||
findViewById(R.id.activitybackgroundpictureAButton8).setOnClickListener(onCleanPixelClickListener);
|
||||
}
|
||||
|
||||
private void handleShareIntent() {
|
||||
Intent intent = getIntent();
|
||||
if (intent != null) {
|
||||
String action = intent.getAction();
|
||||
String type = intent.getType();
|
||||
if (Intent.ACTION_SEND.equals(action) && type != null && isImageType(type)) {
|
||||
BackgroundPicturePreviewDialog dlg = new BackgroundPicturePreviewDialog(this);
|
||||
dlg.show();
|
||||
LogUtils.d(TAG, "【分享处理】收到分享图片意图");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean isImageType(String lowerMimeType) {
|
||||
return lowerMimeType.equals("image/jpeg")
|
||||
|| lowerMimeType.equals("image/png")
|
||||
|| lowerMimeType.equals("image/tiff")
|
||||
|| lowerMimeType.equals("image/jpg")
|
||||
|| lowerMimeType.equals("image/svg+xml");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAcceptRecivedPicture(String szPreRecivedPictureName) {
|
||||
ToastUtils.show("图片接收功能暂未实现");
|
||||
LogUtils.d(TAG, "【分享接收】图片名:" + szPreRecivedPictureName);
|
||||
}
|
||||
|
||||
private View.OnClickListener onOriginNullClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d(TAG, "【按钮点击】取消背景图片");
|
||||
BackgroundBean previewBackgroundBean = mBgSourceUtils.getPreviewBackgroundBean();
|
||||
previewBackgroundBean.setIsUseBackgroundFile(false);
|
||||
doubleRefreshPreview();
|
||||
}
|
||||
};
|
||||
|
||||
private View.OnClickListener onSelectPictureClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d(TAG, "【按钮点击】选择图片");
|
||||
if (mPermissionUtils.checkAndRequestStoragePermission(BackgroundSettingsActivity.this)) {
|
||||
LogUtils.d(TAG, "【选图权限】已获取");
|
||||
Intent[] intents = new Intent[3];
|
||||
Intent getContentIntent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
getContentIntent.setType("image/*");
|
||||
getContentIntent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
getContentIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
intents[0] = getContentIntent;
|
||||
|
||||
Intent pickIntent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
|
||||
pickIntent.setType("image/*");
|
||||
pickIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
intents[1] = pickIntent;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
Intent openDocIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
||||
openDocIntent.setType("image/*");
|
||||
openDocIntent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
openDocIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
|
||||
intents[2] = openDocIntent;
|
||||
}
|
||||
|
||||
Intent validIntent = null;
|
||||
for (int i = 0; i < intents.length; i++) {
|
||||
Intent intent = intents[i];
|
||||
if (intent != null && intent.resolveActivity(getPackageManager()) != null) {
|
||||
validIntent = intent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (validIntent != null) {
|
||||
Intent chooser = Intent.createChooser(validIntent, "选择图片");
|
||||
chooser.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
|
||||
startActivityForResult(chooser, REQUEST_SELECT_PICTURE);
|
||||
LogUtils.d(TAG, "【选图意图】启动图片选择");
|
||||
} else {
|
||||
LogUtils.d(TAG, "【选图意图】无相册应用");
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ToastUtils.show("未找到相册应用,请安装后重试");
|
||||
new AlertDialog.Builder(BackgroundSettingsActivity.this)
|
||||
.setTitle("无图片选择应用")
|
||||
.setMessage("需要安装相册应用才能选择图片")
|
||||
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
Intent marketIntent = new Intent(Intent.ACTION_VIEW);
|
||||
marketIntent.setData(Uri.parse("market://details?id=com.android.gallery3d"));
|
||||
if (marketIntent.resolveActivity(getPackageManager()) != null) {
|
||||
startActivity(marketIntent);
|
||||
} else {
|
||||
ToastUtils.show("无法打开应用商店");
|
||||
}
|
||||
}
|
||||
})
|
||||
.setNegativeButton("取消", null)
|
||||
.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
LogUtils.d(TAG, "【选图权限】已申请");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private View.OnClickListener onCropPictureClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d(TAG, "【按钮点击】固定比例裁剪");
|
||||
// 调用裁剪工具类:传入上下文、预览图、固定比例(按视图宽高)、请求码
|
||||
ImageCropUtils.startImageCrop(BackgroundSettingsActivity.this,
|
||||
mBgSourceUtils.getPreviewBackgroundBean(),
|
||||
mBackgroundView.getWidth(),
|
||||
mBackgroundView.getHeight(),
|
||||
false,
|
||||
REQUEST_CROP_IMAGE
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
private View.OnClickListener onCropFreePictureClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d(TAG, "【按钮点击】自由裁剪");
|
||||
// 调用裁剪工具类:传入上下文、预览图、自由裁剪(比例参数传0)、请求码
|
||||
ImageCropUtils.startImageCrop(BackgroundSettingsActivity.this,
|
||||
mBgSourceUtils.getPreviewBackgroundBean(),
|
||||
0,
|
||||
0,
|
||||
true,
|
||||
REQUEST_CROP_IMAGE
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
private View.OnClickListener onTakePhotoClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d(TAG, "【按钮点击】拍照");
|
||||
if (mfTakePhoto.exists()) {
|
||||
boolean deleteSuccess = mfTakePhoto.delete();
|
||||
LogUtils.d(TAG, "【拍照准备】清理旧文件:" + (deleteSuccess ? "成功" : "失败"));
|
||||
}
|
||||
try {
|
||||
boolean createSuccess = mfTakePhoto.createNewFile();
|
||||
LogUtils.d(TAG, "【拍照准备】创建新文件:" + (createSuccess ? "成功" : "失败"));
|
||||
if (!createSuccess) {
|
||||
ToastUtils.show("拍照文件创建失败");
|
||||
return;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LogUtils.e(TAG, "【拍照异常】" + e.getMessage());
|
||||
ToastUtils.show("拍照文件创建失败");
|
||||
return;
|
||||
}
|
||||
|
||||
if (mPermissionUtils.checkAndRequestStoragePermission(BackgroundSettingsActivity.this)) {
|
||||
LogUtils.d(TAG, "【拍照权限】已获取");
|
||||
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
||||
try {
|
||||
Uri photoUri = getFileProviderUri(mfTakePhoto);
|
||||
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
|
||||
startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
|
||||
LogUtils.d(TAG, "【拍照启动】Uri:" + photoUri.toString());
|
||||
} catch (Exception e) {
|
||||
String errMsg = "拍照启动异常:" + e.getMessage();
|
||||
ToastUtils.show(errMsg.substring(0, 20));
|
||||
LogUtils.e(TAG, "【拍照失败】");
|
||||
}
|
||||
} else {
|
||||
LogUtils.d(TAG, "【拍照权限】已申请");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private View.OnClickListener onReceivedPictureClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
ToastUtils.show("图片接收功能暂未实现");
|
||||
LogUtils.d(TAG, "【按钮点击】图片接收");
|
||||
}
|
||||
};
|
||||
|
||||
private View.OnClickListener onPixelPickerClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d(TAG, "【按钮点击】像素拾取");
|
||||
String targetImagePath = mBgSourceUtils.getCurrentBackgroundFilePath();
|
||||
File targetFile = new File(targetImagePath);
|
||||
if (targetFile == null || !targetFile.exists() || targetFile.length() <= 0) {
|
||||
ToastUtils.show("无有效图片可拾取像素");
|
||||
LogUtils.e(TAG, "【像素拾取失败】");
|
||||
return;
|
||||
}
|
||||
Intent intent = new Intent(getApplicationContext(), PixelPickerActivity.class);
|
||||
intent.putExtra("imagePath", targetImagePath);
|
||||
startActivity(intent);
|
||||
LogUtils.d(TAG, "【像素拾取启动】");
|
||||
}
|
||||
};
|
||||
|
||||
private View.OnClickListener onCleanPixelClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d(TAG, "【按钮点击】清空像素颜色");
|
||||
BackgroundBean bean = mBgSourceUtils.getCurrentBackgroundBean();
|
||||
int oldColor = bean.getPixelColor();
|
||||
bean.setPixelColor(0);
|
||||
mBgSourceUtils.saveSettings();
|
||||
doubleRefreshPreview();
|
||||
ToastUtils.show("像素颜色已清空");
|
||||
LogUtils.d(TAG, "【像素清空】旧颜色:" + oldColor);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
LogUtils.d(TAG, "【回调触发】requestCode:" + requestCode + ",resultCode:" + resultCode);
|
||||
|
||||
try {
|
||||
if (requestCode == PermissionUtils.REQUEST_MANAGE_EXTERNAL_STORAGE && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
handleStoragePermissionCallback();
|
||||
return;
|
||||
}
|
||||
|
||||
if (resultCode != RESULT_OK) {
|
||||
handleOperationCancelOrFail();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (requestCode) {
|
||||
case REQUEST_SELECT_PICTURE:
|
||||
handleSelectPictureResult(resultCode, data);
|
||||
break;
|
||||
case REQUEST_TAKE_PHOTO:
|
||||
handleTakePhotoResult(resultCode, data);
|
||||
break;
|
||||
case REQUEST_CROP_IMAGE:
|
||||
handleCropImageResult(requestCode, resultCode, data);
|
||||
break;
|
||||
default:
|
||||
LogUtils.d(TAG, "【回调忽略】未知requestCode");
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "【回调异常】" + e.getMessage());
|
||||
ToastUtils.show("操作失败");
|
||||
}
|
||||
}
|
||||
|
||||
private void handleStoragePermissionCallback() {
|
||||
if (Environment.isExternalStorageManager()) {
|
||||
LogUtils.d(TAG, "【权限回调】已授予");
|
||||
ToastUtils.show("存储权限已获取");
|
||||
} else {
|
||||
LogUtils.d(TAG, "【权限回调】已拒绝");
|
||||
ToastUtils.show("存储权限不足");
|
||||
}
|
||||
}
|
||||
|
||||
private void handleTakePhotoResult(int resultCode, Intent data) {
|
||||
if (resultCode != RESULT_OK || data == null) {
|
||||
handleOperationCancelOrFail();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mfTakePhoto.exists() || mfTakePhoto.length() <= 0) {
|
||||
ToastUtils.show("拍照文件无效");
|
||||
return;
|
||||
}
|
||||
|
||||
Bitmap photoBitmap = getTakePhotoBitmap(data);
|
||||
if (photoBitmap != null && !photoBitmap.isRecycled()) {
|
||||
mBgSourceUtils.compressQualityToRecivedPicture(photoBitmap);
|
||||
} else {
|
||||
ToastUtils.show("拍照图片为空");
|
||||
return;
|
||||
}
|
||||
|
||||
mBgSourceUtils.saveFileToPreviewBean(mfTakePhoto, mfTakePhoto.getAbsolutePath());
|
||||
doubleRefreshPreview();
|
||||
|
||||
// 拍照后启动固定比例裁剪(调用工具类)
|
||||
ImageCropUtils.startImageCrop(BackgroundSettingsActivity.this,
|
||||
mBgSourceUtils.getPreviewBackgroundBean(),
|
||||
mBackgroundView.getWidth(),
|
||||
mBackgroundView.getHeight(),
|
||||
false,
|
||||
REQUEST_CROP_IMAGE
|
||||
);
|
||||
LogUtils.d(TAG, "【拍照完成】已启动裁剪");
|
||||
}
|
||||
|
||||
private Bitmap getTakePhotoBitmap(Intent data) {
|
||||
LogUtils.d(TAG, "【拍照Bitmap解析】开始");
|
||||
if (mfTakePhoto != null && mfTakePhoto.exists()) {
|
||||
LogUtils.d(TAG, "【拍照Bitmap解析】从文件解析");
|
||||
Bitmap photoBitmap = parseCropTempFileToBitmap(mfTakePhoto);
|
||||
if (photoBitmap != null && !photoBitmap.isRecycled()) {
|
||||
LogUtils.d(TAG, "【拍照Bitmap解析】成功");
|
||||
return photoBitmap;
|
||||
} else {
|
||||
LogUtils.w(TAG, "【拍照Bitmap解析】文件解析失败,尝试Intent");
|
||||
}
|
||||
} else {
|
||||
LogUtils.w(TAG, "【拍照Bitmap解析】文件无效,尝试Intent");
|
||||
}
|
||||
|
||||
if (data != null) {
|
||||
try {
|
||||
Bitmap thumbnailBitmap = (Bitmap) data.getParcelableExtra("data");
|
||||
if (thumbnailBitmap != null && !thumbnailBitmap.isRecycled()) {
|
||||
LogUtils.d(TAG, "【拍照Bitmap解析】从Intent获取成功");
|
||||
return thumbnailBitmap;
|
||||
} else {
|
||||
LogUtils.e(TAG, "【拍照Bitmap解析】Intent解析失败");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "【拍照Bitmap解析】Intent异常:" + e.getMessage());
|
||||
}
|
||||
} else {
|
||||
LogUtils.e(TAG, "【拍照Bitmap解析】Intent为空");
|
||||
}
|
||||
|
||||
LogUtils.e(TAG, "【拍照Bitmap解析】失败");
|
||||
ToastUtils.show("拍照图片解析失败");
|
||||
return null;
|
||||
}
|
||||
|
||||
private void handleSelectPictureResult(int resultCode, Intent data) {
|
||||
if (resultCode != RESULT_OK || data == null) {
|
||||
handleOperationCancelOrFail();
|
||||
return;
|
||||
}
|
||||
|
||||
Uri selectedImage = data.getData();
|
||||
if (selectedImage == null) {
|
||||
ToastUtils.show("图片Uri为空");
|
||||
return;
|
||||
}
|
||||
LogUtils.d(TAG, "【选图回调】Uri : " + selectedImage.toString());
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
getContentResolver().takePersistableUriPermission(
|
||||
selectedImage,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
);
|
||||
LogUtils.d(TAG, "【选图权限】已添加持久化权限");
|
||||
}
|
||||
|
||||
mBgSourceUtils.createCropFileProviderBackgroundBean(selectedImage);
|
||||
|
||||
LogUtils.d(TAG, "【选图同步】路径绑定完成");
|
||||
// 选图后启动固定比例裁剪(调用工具类)
|
||||
mBgSourceUtils.loadSettings();
|
||||
ImageCropUtils.startImageCrop(BackgroundSettingsActivity.this,
|
||||
mBgSourceUtils.getPreviewBackgroundBean(),
|
||||
mBackgroundView.getWidth(),
|
||||
mBackgroundView.getHeight(),
|
||||
false,
|
||||
REQUEST_CROP_IMAGE
|
||||
);
|
||||
}
|
||||
|
||||
private void handleCropImageResult(int requestCode, int resultCode, Intent data) {
|
||||
LogUtils.d(TAG, "handleCropImageResult: 处理裁剪结果");
|
||||
File cropTempFile = new File(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundScaledCompressFilePath());
|
||||
boolean isFileExist = cropTempFile.exists();
|
||||
boolean isFileReadable = isFileExist ? cropTempFile.canRead() : false;
|
||||
long fileSize = isFileExist ? cropTempFile.length() : 0;
|
||||
boolean isCropSuccess = (resultCode == RESULT_OK) && isFileExist && isFileReadable && fileSize > 100;
|
||||
|
||||
if (isCropSuccess) {
|
||||
LogUtils.d(TAG, "handleCropImageResult: 裁剪成功");
|
||||
BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean();
|
||||
previewBean.setIsUseBackgroundFile(true);
|
||||
previewBean.setIsUseBackgroundScaledCompressFile(true);
|
||||
mBgSourceUtils.saveSettings();
|
||||
|
||||
float systemFileRatio = getRatioFromSystemCropFile(cropTempFile);
|
||||
if (systemFileRatio > 0) {
|
||||
Bitmap cropBitmap = parseCropTempFileToBitmap(cropTempFile);
|
||||
if (cropBitmap != null && !cropBitmap.isRecycled()) {
|
||||
Bitmap scaledCropBitmap = adjustBitmapToFinalRatio(cropBitmap, systemFileRatio);
|
||||
saveScaledBitmapToFile(scaledCropBitmap, cropTempFile);
|
||||
|
||||
if (scaledCropBitmap != cropBitmap && !scaledCropBitmap.isRecycled()) {
|
||||
scaledCropBitmap.recycle();
|
||||
}
|
||||
if (!cropBitmap.isRecycled()) {
|
||||
cropBitmap.recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mBackgroundView != null && !isFinishing()) {
|
||||
mBackgroundView.loadBackgroundBean(mBgSourceUtils.getPreviewBackgroundBean());
|
||||
LogUtils.d(TAG, "handleCropImageResult: 裁剪图片加载完成");
|
||||
}
|
||||
}
|
||||
}, 50);
|
||||
} else {
|
||||
handleOperationCancelOrFail();
|
||||
}
|
||||
}
|
||||
|
||||
private float getRatioFromSystemCropFile(File systemCropFile) {
|
||||
LogUtils.d(TAG, "getRatioFromSystemCropFile: 读取比例");
|
||||
if (systemCropFile == null || !systemCropFile.exists() || !systemCropFile.isFile()) {
|
||||
LogUtils.e(TAG, "getRatioFromSystemCropFile: 文件无效");
|
||||
return -1;
|
||||
}
|
||||
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeFile(systemCropFile.getAbsolutePath(), options);
|
||||
|
||||
int cropWidth = options.outWidth;
|
||||
int cropHeight = options.outHeight;
|
||||
if (cropWidth <= 0 || cropHeight <= 0) {
|
||||
LogUtils.e(TAG, "getRatioFromSystemCropFile: 尺寸无效");
|
||||
return -1;
|
||||
}
|
||||
|
||||
float systemRatio = (float) cropWidth / cropHeight;
|
||||
LogUtils.d(TAG, "getRatioFromSystemCropFile: 比例:" + systemRatio);
|
||||
return systemRatio;
|
||||
}
|
||||
|
||||
private Bitmap adjustBitmapToFinalRatio(Bitmap originalBitmap, float finalCropRatio) {
|
||||
LogUtils.d(TAG, "adjustBitmapToFinalRatio: 调整比例");
|
||||
if (originalBitmap == null || originalBitmap.isRecycled() || finalCropRatio <= 0) {
|
||||
LogUtils.e(TAG, "adjustBitmapToFinalRatio: 参数无效");
|
||||
return originalBitmap;
|
||||
}
|
||||
|
||||
int originalWidth = originalBitmap.getWidth();
|
||||
int originalHeight = originalBitmap.getHeight();
|
||||
float originalRatio = (float) originalWidth / originalHeight;
|
||||
|
||||
if (Math.abs(originalRatio - finalCropRatio) < 0.001f) {
|
||||
LogUtils.d(TAG, "adjustBitmapToFinalRatio: 比例一致");
|
||||
return originalBitmap;
|
||||
}
|
||||
|
||||
int targetWidth, targetHeight;
|
||||
targetHeight = originalHeight;
|
||||
targetWidth = Math.round(targetHeight * finalCropRatio);
|
||||
if (targetWidth > originalWidth) {
|
||||
targetWidth = originalWidth;
|
||||
targetHeight = Math.round(targetWidth / finalCropRatio);
|
||||
}
|
||||
|
||||
targetWidth = Math.round(targetHeight * finalCropRatio);
|
||||
LogUtils.d(TAG, "adjustBitmapToFinalRatio: 调整前:" + originalWidth + "x" + originalHeight + ",调整后:" + targetWidth + "x" + targetHeight);
|
||||
|
||||
Bitmap adjustedBitmap = Bitmap.createBitmap(
|
||||
originalBitmap,
|
||||
(originalWidth - targetWidth) / 2,
|
||||
(originalHeight - targetHeight) / 2,
|
||||
targetWidth,
|
||||
targetHeight
|
||||
);
|
||||
|
||||
return adjustedBitmap;
|
||||
}
|
||||
|
||||
private void saveScaledBitmapToFile(Bitmap bitmap, File targetFile) {
|
||||
LogUtils.d(TAG, "saveScaledBitmapToFile: 保存图片");
|
||||
if (bitmap == null || bitmap.isRecycled() || targetFile == null) {
|
||||
LogUtils.e(TAG, "saveScaledBitmapToFile: 参数无效");
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetFile.exists()) {
|
||||
boolean deleteSuccess = targetFile.delete();
|
||||
LogUtils.d(TAG, "saveScaledBitmapToFile: 删除原文件:" + (deleteSuccess ? "成功" : "失败"));
|
||||
}
|
||||
|
||||
OutputStream outputStream = null;
|
||||
try {
|
||||
outputStream = new BufferedOutputStream(new FileOutputStream(targetFile));
|
||||
bitmap.compress(CompressFormat.JPEG, 100, outputStream);
|
||||
outputStream.flush();
|
||||
LogUtils.d(TAG, "saveScaledBitmapToFile: 保存成功");
|
||||
} catch (IOException e) {
|
||||
LogUtils.e(TAG, "saveScaledBitmapToFile: 异常:" + e.getMessage());
|
||||
} finally {
|
||||
if (outputStream != null) {
|
||||
try {
|
||||
outputStream.close();
|
||||
} catch (IOException e) {
|
||||
LogUtils.e(TAG, "saveScaledBitmapToFile: 关闭流异常");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Bitmap parseCropTempFileToBitmap(File cropTempFile) {
|
||||
LogUtils.d(TAG, "parseCropTempFileToBitmap: 解析文件");
|
||||
if (cropTempFile == null || !cropTempFile.exists() || !cropTempFile.isFile() || cropTempFile.length() <= 100) {
|
||||
LogUtils.e(TAG, "parseCropTempFileToBitmap: 文件无效");
|
||||
return null;
|
||||
}
|
||||
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeFile(cropTempFile.getAbsolutePath(), options);
|
||||
|
||||
int maxSize = 2048;
|
||||
int sampleSize = 1;
|
||||
while (options.outWidth / sampleSize > maxSize || options.outHeight / sampleSize > maxSize) {
|
||||
sampleSize *= 2;
|
||||
}
|
||||
sampleSize = Math.min(sampleSize, 16);
|
||||
LogUtils.d(TAG, "parseCropTempFileToBitmap: 采样率:" + sampleSize);
|
||||
|
||||
options.inJustDecodeBounds = false;
|
||||
options.inSampleSize = sampleSize;
|
||||
options.inPreferredConfig = Bitmap.Config.RGB_565;
|
||||
options.inPurgeable = true;
|
||||
options.inInputShareable = true;
|
||||
|
||||
Bitmap cropBitmap = null;
|
||||
try {
|
||||
cropBitmap = BitmapFactory.decodeFile(cropTempFile.getAbsolutePath(), options);
|
||||
if (cropBitmap == null || cropBitmap.isRecycled()) {
|
||||
LogUtils.e(TAG, "parseCropTempFileToBitmap: 解析失败");
|
||||
return null;
|
||||
}
|
||||
LogUtils.d(TAG, "parseCropTempFileToBitmap: 解析成功");
|
||||
} catch (OutOfMemoryError e) {
|
||||
LogUtils.e(TAG, "parseCropTempFileToBitmap: OOM");
|
||||
Toast.makeText(this, "图片解析失败", Toast.LENGTH_SHORT).show();
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "parseCropTempFileToBitmap: 异常:" + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
|
||||
return cropBitmap;
|
||||
}
|
||||
|
||||
private void doubleRefreshPreview() {
|
||||
LogUtils.d(TAG, "【双重刷新】开始");
|
||||
if (mBackgroundView != null && !isFinishing()) {
|
||||
mBackgroundView.loadBackgroundBean(mBgSourceUtils.getPreviewBackgroundBean());
|
||||
LogUtils.d(TAG, "【双重刷新】第一重完成");
|
||||
} else {
|
||||
LogUtils.w(TAG, "【双重刷新】跳过");
|
||||
return;
|
||||
}
|
||||
|
||||
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mBackgroundView != null && !isFinishing()) {
|
||||
mBackgroundView.loadBackgroundBean(mBgSourceUtils.getPreviewBackgroundBean());
|
||||
LogUtils.d(TAG, "【双重刷新】第二重完成");
|
||||
}
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
|
||||
|
||||
private void handleOperationCancelOrFail() {
|
||||
initBackgroundViewByPreviewBean();
|
||||
LogUtils.d(TAG, "【操作回调】取消或失败");
|
||||
ToastUtils.show("操作已取消");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 启动系统裁剪工具
|
||||
* @param activity 上下文
|
||||
* @param srcFile 裁剪原图
|
||||
* @param aspectX 裁剪宽比例(自由裁剪传0)
|
||||
* @param aspectY 裁剪高比例(自由裁剪传0)
|
||||
* @param isFreeCrop 是否自由裁剪
|
||||
* @param requestCode 裁剪请求码
|
||||
*/
|
||||
/**
|
||||
* 启动系统裁剪工具
|
||||
* @param activity 上下文
|
||||
* @param srcFile 裁剪原图
|
||||
* @param aspectX 裁剪宽比例(自由裁剪传0)
|
||||
* @param aspectY 裁剪高比例(自由裁剪传0)
|
||||
* @param isFreeCrop 是否自由裁剪
|
||||
* @param requestCode 裁剪请求码
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* 获取FileProvider Uri(复用方法,避免重复代码)
|
||||
*/
|
||||
public Uri getFileProviderUri(File file) {
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
String FILE_PROVIDER_AUTHORITY = getPackageName() + ".fileprovider";
|
||||
|
||||
return FileProvider.getUriForFile(this, FILE_PROVIDER_AUTHORITY, file);
|
||||
} else {
|
||||
return Uri.fromFile(file);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "getFileProviderUri: 生成Uri失败:" + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
LogUtils.d(TAG, "【权限回调】转发处理");
|
||||
mPermissionUtils.handleStoragePermissionResult(this, requestCode, permissions, grantResults);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
if (isCommitSettings) {
|
||||
super.finish();
|
||||
} else {
|
||||
YesNoAlertDialog.show(this, "背景更换问题", "是否确定背景图片设置?", new YesNoAlertDialog.OnDialogResultListener(){
|
||||
@Override
|
||||
public void onYes() {
|
||||
mBgSourceUtils.commitPreviewSourceToCurrent();
|
||||
isCommitSettings = true;
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNo() {
|
||||
isCommitSettings = true;
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import cc.winboll.studio.powerbell.App;
|
||||
import cc.winboll.studio.powerbell.R;
|
||||
import cc.winboll.studio.powerbell.beans.BatteryInfoBean;
|
||||
import cc.winboll.studio.powerbell.model.BatteryInfoBean;
|
||||
import cc.winboll.studio.powerbell.receivers.ControlCenterServiceReceiver;
|
||||
import cc.winboll.studio.powerbell.utils.AppCacheUtils;
|
||||
import cc.winboll.studio.powerbell.utils.StringUtils;
|
||||
|
||||
@@ -24,10 +24,10 @@ import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
|
||||
import cc.winboll.studio.libaes.views.AToolbar;
|
||||
import cc.winboll.studio.libappbase.GlobalApplication;
|
||||
import cc.winboll.studio.powerbell.R;
|
||||
import cc.winboll.studio.powerbell.activities.BackgroundPictureActivity;
|
||||
import cc.winboll.studio.powerbell.activities.BackgroundSettingsActivity;
|
||||
import cc.winboll.studio.powerbell.activities.PixelPickerActivity;
|
||||
import cc.winboll.studio.powerbell.beans.BackgroundPictureBean;
|
||||
import cc.winboll.studio.powerbell.utils.BackgroundPictureUtils;
|
||||
import cc.winboll.studio.powerbell.model.BackgroundBean;
|
||||
import cc.winboll.studio.powerbell.utils.BackgroundSourceUtils;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
@@ -193,10 +193,10 @@ public class PixelPickerActivity extends WinBoLLActivity implements IWinBoLLActi
|
||||
public void onClick(View v) {
|
||||
dialog.dismiss();
|
||||
// 可以在这里添加确定后的回调逻辑
|
||||
BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(PixelPickerActivity.this);
|
||||
BackgroundPictureBean bean = utils.getBackgroundPictureBean();
|
||||
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(PixelPickerActivity.this);
|
||||
BackgroundBean bean = utils.getCurrentBackgroundBean();
|
||||
bean.setPixelColor(pixelColor);
|
||||
utils.saveData();
|
||||
utils.saveSettings();
|
||||
Toast.makeText(PixelPickerActivity.this, "已记录像素值", Toast.LENGTH_SHORT).show();
|
||||
setBackgroundColor();
|
||||
}
|
||||
@@ -217,8 +217,8 @@ public class PixelPickerActivity extends WinBoLLActivity implements IWinBoLLActi
|
||||
|
||||
|
||||
void setBackgroundColor() {
|
||||
BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(PixelPickerActivity.this);
|
||||
BackgroundPictureBean bean = utils.getBackgroundPictureBean();
|
||||
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(PixelPickerActivity.this);
|
||||
BackgroundBean bean = utils.getCurrentBackgroundBean();
|
||||
int nPixelColor = bean.getPixelColor();
|
||||
RelativeLayout mainLayout = findViewById(R.id.activitypixelpickerRelativeLayout1);
|
||||
mainLayout.setBackgroundColor(nPixelColor);
|
||||
@@ -235,7 +235,7 @@ public class PixelPickerActivity extends WinBoLLActivity implements IWinBoLLActi
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
Intent intent = new Intent();
|
||||
intent.setClass(this, BackgroundPictureActivity.class);
|
||||
intent.setClass(this, BackgroundSettingsActivity.class);
|
||||
startActivity(intent);
|
||||
//GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(getApplicationContext(), );
|
||||
return true;
|
||||
@@ -248,7 +248,7 @@ public class PixelPickerActivity extends WinBoLLActivity implements IWinBoLLActi
|
||||
public void onBackPressed() {
|
||||
super.onBackPressed();
|
||||
Intent intent = new Intent();
|
||||
intent.setClass(this, BackgroundPictureActivity.class);
|
||||
intent.setClass(this, BackgroundSettingsActivity.class);
|
||||
startActivity(intent);
|
||||
//GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(getApplicationContext(), BackgroundPictureActivity.class);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package cc.winboll.studio.powerbell.activities;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import cc.winboll.studio.powerbell.R;
|
||||
import cc.winboll.studio.powerbell.utils.PermissionUtils;
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
|
||||
public void onCheckPermission(View view) {
|
||||
//ToastUtils.show("onCheckPermission");
|
||||
if(PermissionUtils.getInstance().checkAndRequestStoragePermission(this)) {
|
||||
ToastUtils.show("【权限检查】存储权限已全部获取");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
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.powerbell.R;
|
||||
import cc.winboll.studio.powerbell.utils.APPPlusUtils;
|
||||
import cc.winboll.studio.powerbell.App;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/15 13:45
|
||||
* @Describe 应用快捷方式活动类
|
||||
*/
|
||||
public class ShortcutActionActivity extends Activity {
|
||||
|
||||
public static final String TAG = "ShortcutActionActivity";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
// 处理应用级别的切换请求
|
||||
handleSwitchRequest();
|
||||
finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理应用图标快捷菜单的请求
|
||||
*/
|
||||
private void handleSwitchRequest() {
|
||||
Intent intent = getIntent();
|
||||
if (intent != null && "switchto_en1".equals(intent.getDataString())) {
|
||||
APPPlusUtils.switchAppLauncherToComponent(this, App.COMPONENT_EN1);
|
||||
ToastUtils.show("切换至" + getString(R.string.app_name) + "图标");
|
||||
//moveTaskToBack(true);
|
||||
}
|
||||
if (intent != null && "switchto_cn1".equals(intent.getDataString())) {
|
||||
APPPlusUtils.switchAppLauncherToComponent(this, App.COMPONENT_CN1);
|
||||
ToastUtils.show("切换至" + getString(R.string.app_name_cn1) + "图标");
|
||||
//moveTaskToBack(true);
|
||||
}
|
||||
if (intent != null && "switchto_cn2".equals(intent.getDataString())) {
|
||||
APPPlusUtils.switchAppLauncherToComponent(this, App.COMPONENT_CN2);
|
||||
ToastUtils.show("切换至" + getString(R.string.app_name_cn2) + "图标");
|
||||
//moveTaskToBack(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import android.widget.TextView;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import cc.winboll.studio.powerbell.R;
|
||||
import cc.winboll.studio.powerbell.adapters.BatteryAdapter;
|
||||
import cc.winboll.studio.powerbell.beans.BatteryData;
|
||||
import cc.winboll.studio.powerbell.model.BatteryData;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
package cc.winboll.studio.powerbell.beans;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2024/07/18 11:52:28
|
||||
* @Describe 应用背景图片数据类
|
||||
*/
|
||||
import android.util.JsonReader;
|
||||
import android.util.JsonWriter;
|
||||
import cc.winboll.studio.libappbase.BaseBean;
|
||||
import java.io.IOException;
|
||||
|
||||
public class BackgroundPictureBean extends BaseBean {
|
||||
|
||||
public static final String TAG = "BackgroundPictureBean";
|
||||
|
||||
int backgroundWidth = 100;
|
||||
int backgroundHeight = 100;
|
||||
boolean isUseBackgroundFile = false;
|
||||
// 图片拾取像素颜色
|
||||
int pixelColor = 0;
|
||||
|
||||
public BackgroundPictureBean() {
|
||||
}
|
||||
|
||||
public BackgroundPictureBean(String recivedFileName, boolean isUseBackgroundFile) {
|
||||
this.isUseBackgroundFile = isUseBackgroundFile;
|
||||
}
|
||||
|
||||
public void setPixelColor(int pixelColor) {
|
||||
this.pixelColor = pixelColor;
|
||||
}
|
||||
|
||||
public int getPixelColor() {
|
||||
return pixelColor;
|
||||
}
|
||||
|
||||
public void setBackgroundWidth(int backgroundWidth) {
|
||||
this.backgroundWidth = backgroundWidth;
|
||||
}
|
||||
|
||||
public int getBackgroundWidth() {
|
||||
return backgroundWidth;
|
||||
}
|
||||
|
||||
public void setBackgroundHeight(int backgroundHeight) {
|
||||
this.backgroundHeight = backgroundHeight;
|
||||
}
|
||||
|
||||
public int getBackgroundHeight() {
|
||||
return backgroundHeight;
|
||||
}
|
||||
|
||||
public void setIsUseBackgroundFile(boolean isUseBackgroundFile) {
|
||||
this.isUseBackgroundFile = isUseBackgroundFile;
|
||||
}
|
||||
|
||||
public boolean isUseBackgroundFile() {
|
||||
return isUseBackgroundFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return BackgroundPictureBean.class.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
|
||||
super.writeThisToJsonWriter(jsonWriter);
|
||||
BackgroundPictureBean bean = this;
|
||||
jsonWriter.name("backgroundWidth").value(bean.getBackgroundWidth());
|
||||
jsonWriter.name("backgroundHeight").value(bean.getBackgroundHeight());
|
||||
jsonWriter.name("isUseBackgroundFile").value(bean.isUseBackgroundFile());
|
||||
jsonWriter.name("pixelColor").value(bean.getPixelColor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
|
||||
BackgroundPictureBean bean = new BackgroundPictureBean();
|
||||
jsonReader.beginObject();
|
||||
while (jsonReader.hasNext()) {
|
||||
String name = jsonReader.nextName();
|
||||
if (name.equals("backgroundWidth")) {
|
||||
bean.setBackgroundWidth(jsonReader.nextInt());
|
||||
} else if (name.equals("backgroundHeight")) {
|
||||
bean.setBackgroundHeight(jsonReader.nextInt());
|
||||
} else if (name.equals("isUseBackgroundFile")) {
|
||||
bean.setIsUseBackgroundFile(jsonReader.nextBoolean());
|
||||
} else if (name.equals("pixelColor")) {
|
||||
bean.setPixelColor(jsonReader.nextInt());
|
||||
} else {
|
||||
jsonReader.skipValue();
|
||||
}
|
||||
}
|
||||
// 结束 JSON 对象
|
||||
jsonReader.endObject();
|
||||
return bean;
|
||||
}
|
||||
}
|
||||
@@ -12,8 +12,8 @@ import android.widget.Toast;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.powerbell.MainActivity;
|
||||
import cc.winboll.studio.powerbell.R;
|
||||
import cc.winboll.studio.powerbell.activities.BackgroundPictureActivity;
|
||||
import cc.winboll.studio.powerbell.utils.BackgroundPictureUtils;
|
||||
import cc.winboll.studio.powerbell.activities.BackgroundSettingsActivity;
|
||||
import cc.winboll.studio.powerbell.utils.BackgroundSourceUtils;
|
||||
import cc.winboll.studio.powerbell.utils.FileUtils;
|
||||
import cc.winboll.studio.powerbell.utils.UriUtil;
|
||||
import java.io.File;
|
||||
@@ -29,7 +29,7 @@ public class BackgroundPicturePreviewDialog extends Dialog {
|
||||
public static final String TAG = "BackgroundPicturePreviewDialog";
|
||||
|
||||
Context mContext;
|
||||
BackgroundPictureUtils mBackgroundPictureUtils;
|
||||
BackgroundSourceUtils mBackgroundPictureUtils;
|
||||
Button dialogbackgroundpicturepreviewButton1;
|
||||
Button dialogbackgroundpicturepreviewButton2;
|
||||
String mszPreReceivedFileName;
|
||||
@@ -40,7 +40,7 @@ public class BackgroundPicturePreviewDialog extends Dialog {
|
||||
initEnv();
|
||||
|
||||
mContext = context;
|
||||
mBackgroundPictureUtils = ((BackgroundPictureActivity)context).mBackgroundPictureUtils;
|
||||
mBackgroundPictureUtils = BackgroundSourceUtils.getInstance(mContext);
|
||||
|
||||
ImageView imageView = findViewById(R.id.dialogbackgroundpicturepreviewImageView1);
|
||||
copyAndViewRecivePicture(imageView);
|
||||
@@ -78,7 +78,7 @@ public class BackgroundPicturePreviewDialog extends Dialog {
|
||||
|
||||
void copyAndViewRecivePicture(ImageView imageView) {
|
||||
//AppConfigUtils appConfigUtils = AppConfigUtils.getInstance((GlobalApplication)mContext.getApplicationContext());
|
||||
BackgroundPictureActivity activity = ((BackgroundPictureActivity)mContext);
|
||||
BackgroundSettingsActivity activity = ((BackgroundSettingsActivity)mContext);
|
||||
|
||||
//取出文件uri
|
||||
Uri uri = activity.getIntent().getData();
|
||||
@@ -95,7 +95,7 @@ public class BackgroundPicturePreviewDialog extends Dialog {
|
||||
|
||||
File fSrcImage = new File(szSrcImage);
|
||||
//mszPreReceivedFileName = DateUtils.getDateNowString() + "-" + fSrcImage.getName();
|
||||
File mfPreReceivedPhoto = new File(activity.mBackgroundPictureUtils.getBackgroundDir(), mszPreReceivedFileName);
|
||||
File mfPreReceivedPhoto = new File(BackgroundSourceUtils.getInstance(mContext).getBackgroundSourceDirPath(), mszPreReceivedFileName);
|
||||
// 复制源图片到剪裁文件
|
||||
try {
|
||||
FileUtils.copyFileUsingFileChannels(fSrcImage, mfPreReceivedPhoto);
|
||||
|
||||
@@ -0,0 +1,271 @@
|
||||
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.views.BackgroundView;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import cc.winboll.studio.powerbell.utils.ImageDownloader;
|
||||
import cc.winboll.studio.powerbell.utils.BackgroundSourceUtils;
|
||||
|
||||
/**
|
||||
* @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 mBackgroundView;
|
||||
Context mContext;
|
||||
// 主线程 Handler,用于接收子线程消息并更新 UI
|
||||
private Handler mUiHandler;
|
||||
String mPreviewFilePath;
|
||||
String mPreviewFileUrl;
|
||||
String mDownloadSavedPath;
|
||||
|
||||
// 按钮点击回调接口(Java7 接口实现)
|
||||
public interface OnDialogClickListener {
|
||||
void onConfirm(String szConfirmFilePath, String previewFileUrl); // 确认按钮点击
|
||||
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:
|
||||
// 图片加载成功,获取文件路径并设置背景
|
||||
mDownloadSavedPath = (String) msg.obj;
|
||||
previewBackground(mDownloadSavedPath);
|
||||
break;
|
||||
case MSG_IMAGE_LOAD_FAILED:
|
||||
// 图片加载失败,设置默认背景
|
||||
mBackgroundView.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);
|
||||
mBackgroundView = (BackgroundView) dialogView.findViewById(R.id.bv_background_preview);
|
||||
// 加载初始图片
|
||||
mBackgroundView.setBackgroundResource(R.drawable.ic_launcher);
|
||||
// 设置按钮点击事件
|
||||
setButtonClickListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置按钮点击监听
|
||||
*/
|
||||
private void setButtonClickListeners() {
|
||||
// 取消按钮:关闭对话框 + 回调外部
|
||||
btnCancel.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d("NetworkBackgroundDialog", "取消按钮点击");
|
||||
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(mContext);
|
||||
utils.setCurrentSourceToPreview();
|
||||
|
||||
dismiss(); // 关闭对话框
|
||||
if (listener != null) {
|
||||
listener.onCancel();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 确认按钮:关闭对话框 + 回调外部
|
||||
btnConfirm.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d("NetworkBackgroundDialog", "确认按钮点击");
|
||||
// 确定预览背景资源
|
||||
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(mContext);
|
||||
utils.saveFileToPreviewBean(new File(mPreviewFilePath), mPreviewFileUrl);
|
||||
|
||||
dismiss(); // 关闭对话框
|
||||
if (listener != null) {
|
||||
listener.onConfirm(mPreviewFilePath, mPreviewFileUrl);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 图片预览按钮:预览输入框地址图片
|
||||
btnPreview.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
downloadImageToAlbumAndPreview();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据文件路径设置 BackgroundView 背景(主线程调用)
|
||||
* @param filePath 图片文件路径
|
||||
*/
|
||||
private void previewBackground(String previewFilePath) {
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
File imageFile = new File(previewFilePath);
|
||||
if (!imageFile.exists()) {
|
||||
ToastUtils.show("图片文件不存在:" + previewFilePath);
|
||||
LogUtils.e(TAG, "图片文件不存在:" + previewFilePath);
|
||||
mBackgroundView.setBackgroundResource(R.drawable.ic_launcher);
|
||||
return;
|
||||
}
|
||||
|
||||
// 预览背景
|
||||
mPreviewFilePath = previewFilePath;
|
||||
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(mContext);
|
||||
utils.saveFileToPreviewBean(new File(mPreviewFilePath), mPreviewFileUrl);
|
||||
mBackgroundView.loadBackgroundBean(utils.getPreviewBackgroundBean());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
mBackgroundView.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;
|
||||
}
|
||||
|
||||
void downloadImageToAlbumAndPreview() {
|
||||
//String previewFileUrl = "https://example.com/test.jpg";
|
||||
mPreviewFileUrl = etURL.getText().toString();
|
||||
ImageDownloader.getInstance(mContext).downloadImage(mPreviewFileUrl, new ImageDownloader.DownloadCallback(){
|
||||
@Override
|
||||
public void onSuccess(String savePath) {
|
||||
// 发送消息到主线程,携带图片路径
|
||||
Message successMsg = mUiHandler.obtainMessage(MSG_IMAGE_LOAD_SUCCESS, savePath);
|
||||
mUiHandler.sendMessage(successMsg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(String errorMsg) {
|
||||
ToastUtils.show("下载失败:" + errorMsg);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.beans.BackgroundPictureBean;
|
||||
import cc.winboll.studio.powerbell.activities.PixelPickerActivity;
|
||||
import cc.winboll.studio.powerbell.model.BackgroundBean;
|
||||
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.BackgroundSourceUtils;
|
||||
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 {
|
||||
|
||||
@@ -48,6 +47,8 @@ public class MainViewFragment extends Fragment {
|
||||
Switch mswIsEnableService;
|
||||
TextView mtvTips;
|
||||
|
||||
private BackgroundSourceUtils mBgSourceUtils;
|
||||
|
||||
// 背景布局
|
||||
//LinearLayout mLinearLayoutloadBackground;
|
||||
|
||||
@@ -72,6 +73,7 @@ public class MainViewFragment extends Fragment {
|
||||
TextView mtvUsegeReminderValue;
|
||||
CheckBox mcbUsegeReminderValue;
|
||||
TextView mtvCurrentValue;
|
||||
BackgroundView mBackgroundView;
|
||||
|
||||
|
||||
@Override
|
||||
@@ -79,27 +81,30 @@ public class MainViewFragment extends Fragment {
|
||||
mView = inflater.inflate(R.layout.fragment_mainview, container, false);
|
||||
_mMainViewFragment = MainViewFragment.this;
|
||||
mAppConfigUtils = App.getAppConfigUtils(getActivity());
|
||||
|
||||
mBgSourceUtils = BackgroundSourceUtils.getInstance(getActivity());
|
||||
// 获取指定ID的View实例
|
||||
final View mainImageView = mView.findViewById(R.id.fragmentmainviewImageView1);
|
||||
mBackgroundView = mView.findViewById(R.id.fragmentmainviewBackgroundView1);
|
||||
|
||||
// 注册OnGlobalLayoutListener
|
||||
mainImageView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
// 获取宽度和高度
|
||||
int width = mainImageView.getMeasuredWidth();
|
||||
int height = mainImageView.getMeasuredHeight();
|
||||
loadBackground();
|
||||
/*final View mainImageView = mView.findViewById(R.id.fragmentmainviewImageView1);
|
||||
|
||||
BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(getActivity());
|
||||
BackgroundPictureBean bean = utils.loadBackgroundPictureBean();
|
||||
bean.setBackgroundWidth(width);
|
||||
bean.setBackgroundHeight(height);
|
||||
utils.saveData();
|
||||
// 移除监听器以避免内存泄漏
|
||||
mainImageView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
}
|
||||
});
|
||||
// 注册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);
|
||||
}
|
||||
});*/
|
||||
|
||||
mDrawableFrame = getActivity().getDrawable(R.drawable.bg_frame);
|
||||
mllLeftSeekBar = (LinearLayout) mView.findViewById(R.id.fragmentmainviewLinearLayout1);
|
||||
@@ -143,6 +148,19 @@ public class MainViewFragment extends Fragment {
|
||||
return mView;
|
||||
}
|
||||
|
||||
void loadBackground() {
|
||||
BackgroundBean bean = mBgSourceUtils.getCurrentBackgroundBean();
|
||||
mBackgroundView.loadBackgroundBean(bean);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
loadBackground();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void setViewData() {
|
||||
int nChargeReminderValue = mAppConfigUtils.getChargeReminderValue();
|
||||
int nUsegeReminderValue = mAppConfigUtils.getUsegeReminderValue();
|
||||
@@ -302,22 +320,11 @@ 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() {
|
||||
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(getActivity());
|
||||
mBgSourceUtils.loadSettings();
|
||||
BackgroundBean bean = utils.getCurrentBackgroundBean();
|
||||
mBackgroundView.loadBackgroundBean(bean);
|
||||
}
|
||||
|
||||
Handler mHandler = new Handler(){
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.powerbell.beans;
|
||||
package cc.winboll.studio.powerbell.model;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
@@ -0,0 +1,254 @@
|
||||
package cc.winboll.studio.powerbell.model;
|
||||
|
||||
import android.util.JsonReader;
|
||||
import android.util.JsonWriter;
|
||||
import cc.winboll.studio.libappbase.BaseBean;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2024/07/18 11:52:28
|
||||
* @Describe 应用背景图片数据类(存储正式/预览背景配置,支持JSON序列化/反序列化)
|
||||
*/
|
||||
public class BackgroundBean extends BaseBean {
|
||||
|
||||
public static final String TAG = "BackgroundPictureBean";
|
||||
|
||||
// 核心字段:背景图片文件名(对应应用私有目录下的图片文件,与BackgroundSettingsActivity的_mSourceCroppedFile匹配)
|
||||
private String backgroundFileName = "";
|
||||
// 核心字段:背景图片完整路径(解决仅存文件名导致的路径拼接错误,与backgroundScaledCompressFilePath对应)
|
||||
private String backgroundFilePath = "";
|
||||
// 附加字段:图片信息(如Uri、网络地址等,仅作备注,不参与路径生成)
|
||||
private String backgroundFileInfo = "";
|
||||
// 控制字段:是否启用背景图片(true-显示背景图,false-显示透明背景)
|
||||
private boolean isUseBackgroundFile = false;
|
||||
// 核心字段:压缩后背景图片文件名(对应应用私有目录下的压缩图片,与saveCropBitmap的压缩图匹配)
|
||||
private String backgroundScaledCompressFileName = "";
|
||||
// 核心字段:压缩后背景图片完整路径(解决仅存文件名导致的路径拼接错误,适配BackgroundSettingsActivity的私有目录)
|
||||
private String backgroundScaledCompressFilePath = "";
|
||||
// 重命名字段:是否启用压缩背景图(原isUseScaledCompress → 新isUseBackgroundScaledCompressFile,语义更清晰)
|
||||
private boolean isUseBackgroundScaledCompressFile = false;
|
||||
// 裁剪比例字段:背景图宽高比(默认1:1,用于固定比例裁剪)
|
||||
private int backgroundWidth = 100;
|
||||
private int backgroundHeight = 100;
|
||||
// 像素拾取字段:拾取的像素颜色(用于纯色背景)
|
||||
private int pixelColor = 0;
|
||||
|
||||
/**
|
||||
* 无参构造器(必须,JSON反序列化时需默认构造器)
|
||||
*/
|
||||
public BackgroundBean() {
|
||||
}
|
||||
|
||||
// ====================================== Getter/Setter 方法(全字段,含重命名+新增字段)======================================
|
||||
public String getBackgroundFileName() {
|
||||
return backgroundFileName;
|
||||
}
|
||||
|
||||
public void setBackgroundFileName(String backgroundFileName) {
|
||||
this.backgroundFileName = backgroundFileName == null ? "" : backgroundFileName; // 防null,避免空指针
|
||||
}
|
||||
|
||||
public String getBackgroundFilePath() {
|
||||
return backgroundFilePath;
|
||||
}
|
||||
|
||||
public void setBackgroundFilePath(String backgroundFilePath) {
|
||||
this.backgroundFilePath = backgroundFilePath == null ? "" : backgroundFilePath; // 防null,避免路径拼接错误
|
||||
}
|
||||
|
||||
public String getBackgroundFileInfo() {
|
||||
return backgroundFileInfo;
|
||||
}
|
||||
|
||||
public void setBackgroundFileInfo(String backgroundFileInfo) {
|
||||
this.backgroundFileInfo = backgroundFileInfo == null ? "" : backgroundFileInfo; // 防null,避免空指针
|
||||
}
|
||||
|
||||
public boolean isUseBackgroundFile() {
|
||||
return isUseBackgroundFile;
|
||||
}
|
||||
|
||||
public void setIsUseBackgroundFile(boolean isUseBackgroundFile) {
|
||||
this.isUseBackgroundFile = isUseBackgroundFile;
|
||||
}
|
||||
|
||||
public String getBackgroundScaledCompressFileName() {
|
||||
return backgroundScaledCompressFileName;
|
||||
}
|
||||
|
||||
public void setBackgroundScaledCompressFileName(String backgroundScaledCompressFileName) {
|
||||
this.backgroundScaledCompressFileName = backgroundScaledCompressFileName == null ? "" : backgroundScaledCompressFileName; // 防null
|
||||
}
|
||||
|
||||
public String getBackgroundScaledCompressFilePath() {
|
||||
return backgroundScaledCompressFilePath;
|
||||
}
|
||||
|
||||
public void setBackgroundScaledCompressFilePath(String backgroundScaledCompressFilePath) {
|
||||
this.backgroundScaledCompressFilePath = backgroundScaledCompressFilePath == null ? "" : backgroundScaledCompressFilePath; // 防null,避免路径错误
|
||||
}
|
||||
|
||||
/**
|
||||
* 重命名:原isUseScaledCompress → 新isUseBackgroundScaledCompressFile(Getter/Setter同步修改)
|
||||
* 语义:明确表示“是否启用背景压缩图文件”,避免与其他压缩逻辑混淆
|
||||
*/
|
||||
public boolean isUseBackgroundScaledCompressFile() {
|
||||
return isUseBackgroundScaledCompressFile;
|
||||
}
|
||||
|
||||
public void setIsUseBackgroundScaledCompressFile(boolean isUseBackgroundScaledCompressFile) {
|
||||
this.isUseBackgroundScaledCompressFile = isUseBackgroundScaledCompressFile;
|
||||
}
|
||||
|
||||
public int getBackgroundWidth() {
|
||||
return backgroundWidth;
|
||||
}
|
||||
|
||||
public void setBackgroundWidth(int backgroundWidth) {
|
||||
this.backgroundWidth = backgroundWidth <= 0 ? 100 : backgroundWidth; // 防无效值,确保宽高比有效
|
||||
}
|
||||
|
||||
public int getBackgroundHeight() {
|
||||
return backgroundHeight;
|
||||
}
|
||||
|
||||
public void setBackgroundHeight(int backgroundHeight) {
|
||||
this.backgroundHeight = backgroundHeight <= 0 ? 100 : backgroundHeight; // 防无效值,确保宽高比有效
|
||||
}
|
||||
|
||||
public int getPixelColor() {
|
||||
return pixelColor;
|
||||
}
|
||||
|
||||
public void setPixelColor(int pixelColor) {
|
||||
this.pixelColor = pixelColor;
|
||||
}
|
||||
|
||||
// ====================================== 序列化/反序列化方法(适配重命名字段,兼容旧版本)======================================
|
||||
@Override
|
||||
public String getName() {
|
||||
return BackgroundBean.class.getName(); // 必须重写,BaseBean序列化时需类名标识
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化:同步重命名字段(原isUseScaledCompress → 新isUseBackgroundScaledCompressFile)
|
||||
* 确保新字段能正常持久化,同时兼容旧版本JSON(可选:保留旧字段写入,避免旧版本读取异常)
|
||||
*/
|
||||
@Override
|
||||
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
|
||||
super.writeThisToJsonWriter(jsonWriter);
|
||||
BackgroundBean bean = this;
|
||||
jsonWriter.name("backgroundFileName").value(bean.getBackgroundFileName());
|
||||
jsonWriter.name("backgroundFilePath").value(bean.getBackgroundFilePath()); // 新增字段:背景原图完整路径
|
||||
jsonWriter.name("backgroundFileInfo").value(bean.getBackgroundFileInfo());
|
||||
jsonWriter.name("isUseBackgroundFile").value(bean.isUseBackgroundFile());
|
||||
jsonWriter.name("backgroundScaledCompressFileName").value(bean.getBackgroundScaledCompressFileName());
|
||||
jsonWriter.name("backgroundScaledCompressFilePath").value(bean.getBackgroundScaledCompressFilePath());
|
||||
// 关键:新字段序列化(核心)
|
||||
jsonWriter.name("isUseBackgroundScaledCompressFile").value(bean.isUseBackgroundScaledCompressFile());
|
||||
// 兼容旧版本:保留旧字段名写入(可选,避免旧版本Bean读取时缺失字段)
|
||||
jsonWriter.name("isUseScaledCompress").value(bean.isUseBackgroundScaledCompressFile());
|
||||
jsonWriter.name("backgroundWidth").value(bean.getBackgroundWidth());
|
||||
jsonWriter.name("backgroundHeight").value(bean.getBackgroundHeight());
|
||||
jsonWriter.name("pixelColor").value(bean.getPixelColor());
|
||||
}
|
||||
|
||||
/**
|
||||
* 反序列化:同步处理重命名字段(兼容旧版本JSON,新旧字段都能读取)
|
||||
* 逻辑:优先读取新字段,若新字段不存在则读取旧字段(确保升级后旧配置仍有效)
|
||||
*/
|
||||
@Override
|
||||
public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
|
||||
BackgroundBean bean = new BackgroundBean();
|
||||
jsonReader.beginObject();
|
||||
// 临时变量:存储旧字段值(用于兼容)
|
||||
boolean tempUseScaledCompress = false;
|
||||
while (jsonReader.hasNext()) {
|
||||
String name = jsonReader.nextName();
|
||||
switch (name) {
|
||||
case "backgroundFileName":
|
||||
bean.setBackgroundFileName(jsonReader.nextString());
|
||||
break;
|
||||
case "backgroundFilePath":
|
||||
bean.setBackgroundFilePath(jsonReader.nextString()); // 新增字段:读取背景原图完整路径
|
||||
break;
|
||||
case "backgroundFileInfo":
|
||||
bean.setBackgroundFileInfo(jsonReader.nextString());
|
||||
break;
|
||||
case "isUseBackgroundFile":
|
||||
bean.setIsUseBackgroundFile(jsonReader.nextBoolean());
|
||||
break;
|
||||
case "backgroundScaledCompressFileName":
|
||||
bean.setBackgroundScaledCompressFileName(jsonReader.nextString());
|
||||
break;
|
||||
case "backgroundScaledCompressFilePath":
|
||||
bean.setBackgroundScaledCompressFilePath(jsonReader.nextString());
|
||||
break;
|
||||
// 关键:读取新字段(优先)
|
||||
case "isUseBackgroundScaledCompressFile":
|
||||
bean.setIsUseBackgroundScaledCompressFile(jsonReader.nextBoolean());
|
||||
break;
|
||||
// 兼容旧版本:读取旧字段(若新字段未读取,则用旧字段值)
|
||||
case "isUseScaledCompress":
|
||||
tempUseScaledCompress = jsonReader.nextBoolean();
|
||||
break;
|
||||
case "backgroundWidth":
|
||||
bean.setBackgroundWidth(jsonReader.nextInt());
|
||||
break;
|
||||
case "backgroundHeight":
|
||||
bean.setBackgroundHeight(jsonReader.nextInt());
|
||||
break;
|
||||
case "pixelColor":
|
||||
bean.setPixelColor(jsonReader.nextInt());
|
||||
break;
|
||||
default:
|
||||
jsonReader.skipValue(); // 跳过未知字段,兼容旧版本Bean(避免崩溃)
|
||||
break;
|
||||
}
|
||||
}
|
||||
jsonReader.endObject();
|
||||
// 兼容逻辑:若新字段未被赋值(旧版本JSON无此字段),则用旧字段值填充
|
||||
if (!jsonReader.toString().contains("isUseBackgroundScaledCompressFile")) {
|
||||
bean.setIsUseBackgroundScaledCompressFile(tempUseScaledCompress);
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
// ====================================== 辅助方法(同步更新重命名字段)======================================
|
||||
/**
|
||||
* 重置背景配置(适配“取消背景”功能,同步重置重命名字段)
|
||||
*/
|
||||
public void resetBackgroundConfig() {
|
||||
this.backgroundFileName = "";
|
||||
this.backgroundFilePath = ""; // 新增:重置背景原图完整路径
|
||||
this.backgroundScaledCompressFileName = "";
|
||||
this.backgroundScaledCompressFilePath = "";
|
||||
this.backgroundFileInfo = "";
|
||||
this.isUseBackgroundFile = false;
|
||||
this.isUseBackgroundScaledCompressFile = false; // 重命名字段:重置为false
|
||||
this.backgroundWidth = 100;
|
||||
this.backgroundHeight = 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查背景配置是否有效(适配BackgroundSettingsActivity的预览/保存校验)
|
||||
* 同步使用重命名字段判断压缩图是否启用
|
||||
* @return true-配置有效(可显示背景图),false-配置无效
|
||||
*/
|
||||
public boolean isBackgroundConfigValid() {
|
||||
// 启用背景图时,需确保:原图路径/文件名 或 压缩图路径/文件名 非空
|
||||
if (!isUseBackgroundFile) {
|
||||
return false;
|
||||
}
|
||||
// 原图校验:路径非空 或 文件名非空
|
||||
boolean isOriginalValid = !backgroundFilePath.isEmpty() || !backgroundFileName.isEmpty();
|
||||
// 压缩图校验:启用压缩图时,路径/文件名需非空
|
||||
boolean isCompressValid = true;
|
||||
if (isUseBackgroundScaledCompressFile()) { // 重命名字段:判断是否启用压缩图
|
||||
isCompressValid = !backgroundScaledCompressFilePath.isEmpty() || !backgroundScaledCompressFileName.isEmpty();
|
||||
}
|
||||
// 逻辑:启用压缩图则需压缩图有效;不启用压缩图则需原图有效
|
||||
return isUseBackgroundScaledCompressFile() ? isCompressValid : isOriginalValid;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.powerbell.beans;
|
||||
package cc.winboll.studio.powerbell.model;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.powerbell.beans;
|
||||
package cc.winboll.studio.powerbell.model;
|
||||
|
||||
import android.util.JsonReader;
|
||||
import android.util.JsonWriter;
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.powerbell.beans;
|
||||
package cc.winboll.studio.powerbell.model;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.powerbell.beans;
|
||||
package cc.winboll.studio.powerbell.model;
|
||||
|
||||
// 应用消息结构
|
||||
//
|
||||
@@ -5,7 +5,7 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.powerbell.beans.AppConfigBean;
|
||||
import cc.winboll.studio.powerbell.model.AppConfigBean;
|
||||
import cc.winboll.studio.powerbell.services.ControlCenterService;
|
||||
import cc.winboll.studio.powerbell.utils.AppConfigUtils;
|
||||
import cc.winboll.studio.powerbell.utils.BatteryUtils;
|
||||
|
||||
@@ -23,8 +23,8 @@ import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import cc.winboll.studio.powerbell.App;
|
||||
import cc.winboll.studio.powerbell.MainActivity;
|
||||
import cc.winboll.studio.powerbell.R;
|
||||
import cc.winboll.studio.powerbell.beans.AppConfigBean;
|
||||
import cc.winboll.studio.powerbell.beans.NotificationMessage;
|
||||
import cc.winboll.studio.powerbell.model.AppConfigBean;
|
||||
import cc.winboll.studio.powerbell.model.NotificationMessage;
|
||||
import cc.winboll.studio.powerbell.handlers.ControlCenterServiceHandler;
|
||||
import cc.winboll.studio.powerbell.receivers.ControlCenterServiceReceiver;
|
||||
import cc.winboll.studio.powerbell.services.AssistantService;
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
package cc.winboll.studio.powerbell.unittest;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import cc.winboll.studio.powerbell.MainActivity;
|
||||
import cc.winboll.studio.powerbell.R;
|
||||
import cc.winboll.studio.powerbell.utils.BackgroundSourceUtils;
|
||||
import cc.winboll.studio.powerbell.utils.ImageCropUtils;
|
||||
import cc.winboll.studio.powerbell.views.BackgroundView;
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/19 18:04
|
||||
* @Describe 单元测试启动主页窗口
|
||||
*/
|
||||
public class MainUnitTestActivity extends AppCompatActivity {
|
||||
|
||||
public static final String TAG = "MainUnitTestActivity";
|
||||
public static final int REQUEST_CROP_IMAGE = 0;
|
||||
// 新增:权限请求码
|
||||
public static final int REQUEST_STORAGE_PERMISSION = 1001;
|
||||
View mainView;
|
||||
BackgroundSourceUtils mBgSourceUtils;
|
||||
BackgroundView mBackgroundView;
|
||||
// 测试图片路径(用Environment获取,适配低版本,避免硬编码)
|
||||
String szTestSource = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
|
||||
+ "/PowerBell/unittest/2ae9dc9e-7a73-49d4-840a-7ff1712d868c1764798674763.jpeg";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mBgSourceUtils = BackgroundSourceUtils.getInstance(this);
|
||||
mBgSourceUtils.loadSettings();
|
||||
|
||||
setContentView(R.layout.activity_mainunittest);
|
||||
|
||||
mBackgroundView = findViewById(R.id.backgroundview);
|
||||
|
||||
((Button)findViewById(R.id.btn_main_activity)).setOnClickListener(new View.OnClickListener(){
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
startActivity(new Intent(MainUnitTestActivity.this, MainActivity.class));
|
||||
}
|
||||
});
|
||||
|
||||
// 裁剪测试按钮点击事件(新增权限校验)
|
||||
((Button)findViewById(R.id.btn_test_cropimage)).setOnClickListener(new View.OnClickListener(){
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
ToastUtils.show("onClick:准备启动裁剪");
|
||||
LogUtils.d(TAG, "【裁剪测试】点击裁剪按钮,校验权限");
|
||||
|
||||
// 修复1:移除高版本API依赖,适配低版本存储权限校验
|
||||
if (checkStoragePermission()) {
|
||||
// 权限已授予,启动裁剪
|
||||
startCropTest();
|
||||
} else {
|
||||
// 权限未授予,申请权限
|
||||
requestStoragePermission();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ToastUtils.show(String.format("%s onCreate", TAG));
|
||||
// 加载测试图片(验证图片路径是否有效)
|
||||
loadBackground();
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动裁剪测试(抽取为单独方法,便于权限回调后调用)
|
||||
*/
|
||||
private void startCropTest() {
|
||||
// 修复2:输出路径用Environment获取,确保目录存在(避免路径无效)
|
||||
File outputDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
|
||||
+ "/PowerBell/unittest/");
|
||||
if (!outputDir.exists()) {
|
||||
outputDir.mkdirs(); // 创建目录(避免输出路径不存在导致裁剪失败)
|
||||
LogUtils.d(TAG, "【裁剪测试】创建输出目录:" + outputDir.getAbsolutePath());
|
||||
}
|
||||
String dstOutputPath = outputDir.getAbsolutePath()
|
||||
+ "/SelectCompress_2ae9dc9e-7a73-49d4-840a-7ff1712d868c1764798674763.jpeg";
|
||||
|
||||
// 修复3:自由裁剪时比例传0(避免100:100过大导致机型崩溃)
|
||||
ImageCropUtils.startImageCrop(
|
||||
MainUnitTestActivity.this,
|
||||
new File(szTestSource),
|
||||
new File(dstOutputPath),
|
||||
0, // 自由裁剪传0
|
||||
0, // 自由裁剪传0
|
||||
true,
|
||||
REQUEST_CROP_IMAGE
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验存储读写权限(适配Android 6.0+ 低版本SDK,移除TIRAMISU依赖)
|
||||
*/
|
||||
private boolean checkStoragePermission() {
|
||||
// 适配Android 6.0(API 23)及以上,用通用的读写权限(移除高版本API)
|
||||
return ContextCompat.checkSelfPermission(this, android.Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
== PackageManager.PERMISSION_GRANTED
|
||||
&& ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
== PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* 申请存储读写权限(适配低版本SDK,移除READ_MEDIA_IMAGES依赖)
|
||||
*/
|
||||
private void requestStoragePermission() {
|
||||
LogUtils.d(TAG, "【裁剪测试】申请存储读写权限");
|
||||
// 用通用的读写权限(适配所有Android 6.0+ 机型,无高版本依赖)
|
||||
ActivityCompat.requestPermissions(
|
||||
this,
|
||||
new String[]{android.Manifest.permission.READ_EXTERNAL_STORAGE, android.Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
||||
REQUEST_STORAGE_PERMISSION
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限申请回调
|
||||
*/
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
if (requestCode == REQUEST_STORAGE_PERMISSION) {
|
||||
// 校验权限是否授予
|
||||
boolean allGranted = true;
|
||||
for (int result : grantResults) {
|
||||
if (result != PackageManager.PERMISSION_GRANTED) {
|
||||
allGranted = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (allGranted) {
|
||||
ToastUtils.show("存储权限已授予,启动裁剪");
|
||||
startCropTest(); // 权限授予后启动裁剪
|
||||
} else {
|
||||
ToastUtils.show("存储权限被拒绝,无法启动裁剪");
|
||||
LogUtils.e(TAG, "【裁剪测试】存储权限被拒绝");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
LogUtils.d(TAG, "【裁剪回调】requestCode:" + requestCode + ",resultCode:" + resultCode + ",data:" + (data == null ? "null" : data.toString()));
|
||||
ToastUtils.show(String.format("requestCode %d, resultCode %d, data is %s",requestCode, resultCode, data == null));
|
||||
// 裁剪完成后回收权限
|
||||
if (requestCode == REQUEST_CROP_IMAGE) {
|
||||
String dstOutputPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
|
||||
+ "/PowerBell/unittest/SelectCompress_2ae9dc9e-7a73-49d4-840a-7ff1712d868c1764798674763.jpeg";
|
||||
//Uri outputUri = ImageCropUtils.getFileProviderUriPublic(this, new File(dstOutputPath));
|
||||
//ImageCropUtils.releaseCropPermission(this, outputUri);
|
||||
mBackgroundView.loadImage(dstOutputPath);
|
||||
}
|
||||
}
|
||||
|
||||
void loadBackground() {
|
||||
// 校验测试图片是否存在(避免路径错误)
|
||||
File testFile = new File(szTestSource);
|
||||
if (testFile.exists() && testFile.length() > 100) {
|
||||
mBackgroundView.loadImage(szTestSource);
|
||||
LogUtils.d(TAG, "【图片加载】测试图片加载成功:" + szTestSource);
|
||||
} else {
|
||||
ToastUtils.show("测试图片不存在或无效");
|
||||
LogUtils.e(TAG, "【图片加载】测试图片无效:" + szTestSource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
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;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.widget.Toast;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.powerbell.App;
|
||||
import cc.winboll.studio.powerbell.MainActivity;
|
||||
import cc.winboll.studio.powerbell.R;
|
||||
|
||||
public class APPPlusUtils {
|
||||
public static final String TAG = "APPPlusUtils";
|
||||
|
||||
// 快捷方式配置(名称+图标,需与实际资源匹配)
|
||||
// private static final String PLUS_SHORTCUT_NAME = "位置服务-Laojun";
|
||||
// private static final int PLUS_SHORTCUT_ICON = R.mipmap.ic_launcher; // Laojun 图标资源
|
||||
|
||||
/**
|
||||
* 添加Plus组件与图标
|
||||
*/
|
||||
public static boolean switchAppLauncherToComponent(Context context, String componentName) {
|
||||
if (context == null) {
|
||||
LogUtils.d(TAG, "切换失败:上下文为空");
|
||||
Toast.makeText(context, context.getString(R.string.app_name) + "图标切换失败", Toast.LENGTH_SHORT).show();
|
||||
return false;
|
||||
}
|
||||
|
||||
PackageManager pm = context.getPackageManager();
|
||||
|
||||
ComponentName plusComponentSwitchTo = new ComponentName(context, componentName);
|
||||
ComponentName plusComponentEN1 = new ComponentName(context, App.COMPONENT_EN1);
|
||||
ComponentName plusComponentCN1 = new ComponentName(context, App.COMPONENT_CN1);
|
||||
ComponentName plusComponentCN2 = new ComponentName(context, App.COMPONENT_CN2);
|
||||
|
||||
try {
|
||||
disableComponent(pm, plusComponentEN1);
|
||||
disableComponent(pm, plusComponentCN1);
|
||||
disableComponent(pm, plusComponentCN2);
|
||||
enableComponent(pm, plusComponentSwitchTo);
|
||||
|
||||
return true;
|
||||
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "图标切换失败:" + e.getMessage());
|
||||
Toast.makeText(context, context.getString(R.string.app_name) + "图标切换失败" + e.getMessage(), Toast.LENGTH_SHORT).show();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建指定组件的桌面快捷方式(自动去重,兼容 Android 8.0+)
|
||||
* @param component 目标组件(如 LAOJUN_ACTIVITY)
|
||||
* @param name 快捷方式名称
|
||||
* @param iconRes 快捷方式图标资源ID
|
||||
* @return 是否创建成功
|
||||
*/
|
||||
private static boolean createComponentShortcut(Context context, ComponentName component, String name, int iconRes) {
|
||||
if (context == null || component == null || name == null || iconRes == 0) {
|
||||
LogUtils.d(TAG, "快捷方式创建失败:参数为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Android 8.0+(API 26+):使用 ShortcutManager(系统推荐)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
try {
|
||||
PackageManager pm = context.getPackageManager();
|
||||
android.content.pm.ShortcutManager shortcutManager = context.getSystemService(android.content.pm.ShortcutManager.class);
|
||||
if (shortcutManager == null || !shortcutManager.isRequestPinShortcutSupported()) {
|
||||
LogUtils.d(TAG, "系统不支持创建快捷方式");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否已存在该组件的快捷方式(去重)
|
||||
for (android.content.pm.ShortcutInfo info : shortcutManager.getPinnedShortcuts()) {
|
||||
if (component.getClassName().equals(info.getIntent().getComponent().getClassName())) {
|
||||
LogUtils.d(TAG, "快捷方式已存在:" + component.getClassName());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 构建启动目标组件的意图
|
||||
Intent launchIntent = new Intent(Intent.ACTION_MAIN)
|
||||
.setComponent(component)
|
||||
.addCategory(Intent.CATEGORY_LAUNCHER)
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
|
||||
// 构建快捷方式信息
|
||||
android.content.pm.ShortcutInfo shortcutInfo = new android.content.pm.ShortcutInfo.Builder(context, component.getClassName())
|
||||
.setShortLabel(name)
|
||||
.setLongLabel(name)
|
||||
.setIcon(android.graphics.drawable.Icon.createWithResource(context, iconRes))
|
||||
.setIntent(launchIntent)
|
||||
.build();
|
||||
|
||||
// 请求创建快捷方式(需用户确认)
|
||||
shortcutManager.requestPinShortcut(shortcutInfo, null);
|
||||
return true;
|
||||
|
||||
} catch (Exception e) {
|
||||
LogUtils.d(TAG, "Android O+ 快捷方式创建失败:" + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Android 8.0 以下:使用广播(兼容旧机型)
|
||||
try {
|
||||
// 构建启动目标组件的意图
|
||||
Intent launchIntent = new Intent(Intent.ACTION_MAIN)
|
||||
.setComponent(component)
|
||||
.addCategory(Intent.CATEGORY_LAUNCHER)
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
|
||||
// 构建创建快捷方式的广播意图
|
||||
Intent installIntent = new Intent("com.android.launcher.action.INSTALL_SHORTCUT");
|
||||
installIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, launchIntent);
|
||||
installIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name);
|
||||
installIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
|
||||
Intent.ShortcutIconResource.fromContext(context, iconRes));
|
||||
installIntent.putExtra("duplicate", false); // 禁止重复创建
|
||||
|
||||
context.sendBroadcast(installIntent);
|
||||
return true;
|
||||
|
||||
} catch (Exception e) {
|
||||
LogUtils.d(TAG, "Android O- 快捷方式创建失败:" + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用组件(带状态检查,避免重复操作)
|
||||
*/
|
||||
private static void enableComponent(PackageManager pm, ComponentName component) {
|
||||
if (pm.getComponentEnabledSetting(component) != PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
|
||||
pm.setComponentEnabledSetting(
|
||||
component,
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||
PackageManager.DONT_KILL_APP | PackageManager.SYNCHRONOUS
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁用组件(带状态检查,避免重复操作)
|
||||
*/
|
||||
private static void disableComponent(PackageManager pm, ComponentName component) {
|
||||
if (pm.getComponentEnabledSetting(component) != PackageManager.COMPONENT_ENABLED_STATE_DISABLED) {
|
||||
pm.setComponentEnabledSetting(
|
||||
component,
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
||||
PackageManager.DONT_KILL_APP | PackageManager.SYNCHRONOUS
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package cc.winboll.studio.powerbell.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.powerbell.beans.BatteryInfoBean;
|
||||
import cc.winboll.studio.powerbell.model.BatteryInfoBean;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class AppCacheUtils {
|
||||
|
||||
@@ -5,8 +5,8 @@ import android.content.Context;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.powerbell.App;
|
||||
import cc.winboll.studio.powerbell.MainActivity;
|
||||
import cc.winboll.studio.powerbell.beans.AppConfigBean;
|
||||
import cc.winboll.studio.powerbell.beans.ControlCenterServiceBean;
|
||||
import cc.winboll.studio.powerbell.model.AppConfigBean;
|
||||
import cc.winboll.studio.powerbell.model.ControlCenterServiceBean;
|
||||
import cc.winboll.studio.powerbell.dialogs.YesNoAlertDialog;
|
||||
import cc.winboll.studio.powerbell.fragments.MainViewFragment;
|
||||
import cc.winboll.studio.powerbell.services.ControlCenterService;
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
package cc.winboll.studio.powerbell.utils;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2024/07/18 12:07:20
|
||||
* @Describe 背景图片工具集
|
||||
*/
|
||||
import android.content.Context;
|
||||
import cc.winboll.studio.powerbell.beans.BackgroundPictureBean;
|
||||
import java.io.File;
|
||||
|
||||
public class BackgroundPictureUtils {
|
||||
|
||||
public static final String TAG = "BackgroundPictureUtils";
|
||||
|
||||
static BackgroundPictureUtils _mBackgroundPictureUtils;
|
||||
Context mContext;
|
||||
BackgroundPictureBean mBackgroundPictureBean;
|
||||
// 背景图片目录
|
||||
String mszBackgroundDir;
|
||||
|
||||
BackgroundPictureUtils(Context context) {
|
||||
mContext = context;
|
||||
String szExternalFilesDir = mContext.getExternalFilesDir(TAG) + File.separator;
|
||||
setBackgroundDir(szExternalFilesDir + "Background" + File.separator);
|
||||
loadBackgroundPictureBean();
|
||||
}
|
||||
|
||||
public static BackgroundPictureUtils getInstance(Context context) {
|
||||
if (_mBackgroundPictureUtils == null) {
|
||||
_mBackgroundPictureUtils = new BackgroundPictureUtils(context);
|
||||
}
|
||||
return _mBackgroundPictureUtils;
|
||||
}
|
||||
|
||||
//
|
||||
// 加载应用背景图片配置数据
|
||||
//
|
||||
public BackgroundPictureBean loadBackgroundPictureBean() {
|
||||
mBackgroundPictureBean = BackgroundPictureBean.loadBean(mContext, BackgroundPictureBean.class);
|
||||
if (mBackgroundPictureBean == null) {
|
||||
mBackgroundPictureBean = new BackgroundPictureBean();
|
||||
BackgroundPictureBean.saveBean(mContext, mBackgroundPictureBean);
|
||||
}
|
||||
return mBackgroundPictureBean;
|
||||
}
|
||||
|
||||
|
||||
void setBackgroundDir(String mszBackgroundDir) {
|
||||
this.mszBackgroundDir = mszBackgroundDir;
|
||||
}
|
||||
|
||||
public String getBackgroundDir() {
|
||||
return mszBackgroundDir;
|
||||
}
|
||||
|
||||
public BackgroundPictureBean getBackgroundPictureBean() {
|
||||
return mBackgroundPictureBean;
|
||||
}
|
||||
|
||||
public void saveData() {
|
||||
BackgroundPictureBean.saveBean(mContext, mBackgroundPictureBean);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,836 @@
|
||||
package cc.winboll.studio.powerbell.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.media.ExifInterface;
|
||||
import android.net.Uri;
|
||||
import android.os.Environment;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import cc.winboll.studio.powerbell.BuildConfig;
|
||||
import cc.winboll.studio.powerbell.model.BackgroundBean;
|
||||
import java.io.BufferedOutputStream;
|
||||
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.util.UUID;
|
||||
import android.os.Build;
|
||||
import androidx.core.content.FileProvider;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2024/07/18 12:07:20
|
||||
* @Describe 背景图片工具集(精简版:复用FileUtils,聚焦业务逻辑)
|
||||
*/
|
||||
public class BackgroundSourceUtils {
|
||||
|
||||
public static final String TAG = "BackgroundSourceUtils";
|
||||
// 裁剪相关常量(统一定义,避免硬编码)
|
||||
private static final String CROP_CACHE_DIR_NAME = "cache"; // 裁剪缓存目录(基础目录下)
|
||||
private static final String CROP_TEMP_FILE_NAME = "SourceCropTemp.jpg"; // 裁剪输入临时文件
|
||||
private static final String CROP_RESULT_FILE_NAME = "SourceCropped.jpg"; // 裁剪输出结果文件
|
||||
public static final String FILE_PROVIDER_AUTHORITY = BuildConfig.APPLICATION_ID + ".fileprovider"; // 多包名兼容
|
||||
// 图片操作基础目录(核心:系统公共图片目录)
|
||||
private static final String PICTURE_BASE_DIR = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + File.separator + "PowerBell";
|
||||
// 新增:压缩图统一存储目录(图片基础目录下/BackgroundCrops)
|
||||
private static final String SOURCE_DIR_NAME = "BackgroundSource";
|
||||
private static final String COMPRESS_DIR_NAME = "BackgroundCompress";
|
||||
|
||||
// 1. 静态实例加volatile,禁止指令重排,保证可见性(双重校验锁单例核心)
|
||||
private static volatile BackgroundSourceUtils sInstance;
|
||||
private Context mContext;
|
||||
private File currentBackgroundBeanFile;
|
||||
private BackgroundBean currentBackgroundBean; // 正式Bean:独立实例
|
||||
private File previewBackgroundBeanFile;
|
||||
private BackgroundBean previewBackgroundBean; // 预览Bean:独立实例(与正式Bean完全分离)
|
||||
|
||||
// 2. 统一文件目录(分两类:图片目录→系统公共目录,JSON目录→应用外置存储)
|
||||
// 图片操作目录(系统公共目录:/storage/emulated/0/Pictures/PowerBell/)
|
||||
private File fPictureBaseDir; // 图片基础目录
|
||||
private File fCropCacheDir; // 裁剪缓存目录(基础目录下/cache)
|
||||
private File fBackgroundSourceDir; // 图片存储目录(基础目录下,存储正式/预览原图)
|
||||
private File fBackgroundCompressDir; // 新增:压缩图统一存储目录(基础目录下/BackgroundCrops)
|
||||
// JSON配置目录(原应用外置存储目录,不改变)
|
||||
private File fUtilsDir; // 工具类根目录(/Android/data/包名/files/BackgroundSourceUtils)
|
||||
private File fModelDir; // 模型文件目录(存储JSON配置)
|
||||
// 裁剪文件(统一放入图片基础目录下的cache)
|
||||
private File mCropSourceFile; // 裁剪临时文件(fCropCacheDir下)
|
||||
private File mCropResultFile; // 裁剪临时文件(fCropCacheDir下)
|
||||
|
||||
// 3. 私有构造器(加防反射逻辑+初始化所有目录/文件)
|
||||
private BackgroundSourceUtils(Context context) {
|
||||
// 防反射破坏:若已有实例,抛异常阻止重复创建
|
||||
if (sInstance != null) {
|
||||
throw new RuntimeException("BackgroundSourceUtils 是单例类,禁止重复创建!");
|
||||
}
|
||||
// 上下文用Application Context,避免Activity内存泄漏
|
||||
this.mContext = context.getApplicationContext();
|
||||
// 【核心调整1】实例化初期优先初始化所有必要目录(确保实例化完成时目录100%就绪)
|
||||
initNecessaryDirs();
|
||||
// 初始化所有文件(裁剪临时文件/结果文件等)
|
||||
initAllFiles();
|
||||
// 加载配置(确保正式/预览Bean是两份独立实例)
|
||||
loadSettings();
|
||||
}
|
||||
|
||||
// 4. 双重校验锁单例(线程安全,高效,支持多线程并发调用,Java7语法兼容)
|
||||
public static BackgroundSourceUtils getInstance(Context context) {
|
||||
if (sInstance == null) {
|
||||
synchronized (BackgroundSourceUtils.class) {
|
||||
if (sInstance == null) {
|
||||
sInstance = new BackgroundSourceUtils(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 【核心新增】统一初始化所有必要目录(实例化初期调用,确保目录优先创建)
|
||||
* 整合图片目录+JSON目录,集中管理目录创建逻辑,保证实例化完成时所有目录就绪
|
||||
*/
|
||||
private void initNecessaryDirs() {
|
||||
LogUtils.d(TAG, "【实例化初期-目录初始化】开始创建所有必要目录...");
|
||||
// 1. 初始化图片操作目录(系统公共目录 /Pictures/PowerBell/)
|
||||
initPictureDirs();
|
||||
// 2. 初始化JSON配置目录(应用外置存储)
|
||||
initJsonDirs();
|
||||
LogUtils.d(TAG, "【实例化初期-目录初始化】所有必要目录创建完成!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化图片操作目录(核心:系统公共图片目录 /Pictures/PowerBell/,新增压缩图目录)
|
||||
* 【调整强化】新增目录创建后二次校验,失败则降级到备选目录,确保目录可用
|
||||
*/
|
||||
private void initPictureDirs() {
|
||||
// 1. 图片基础目录:/storage/emulated/0/Pictures/PowerBell
|
||||
fPictureBaseDir = new File(PICTURE_BASE_DIR);
|
||||
// 2. 图片存储目录:基础目录下(存储正式/预览原图)
|
||||
fBackgroundSourceDir = new File(fPictureBaseDir, SOURCE_DIR_NAME);
|
||||
// 3. 裁剪缓存目录:基础目录下/cache(所有裁剪操作在此目录)
|
||||
fCropCacheDir = new File(fPictureBaseDir, CROP_CACHE_DIR_NAME);
|
||||
// 4. 新增:压缩图统一存储目录(基础目录下/BackgroundCrops,所有压缩图放这里)
|
||||
fBackgroundCompressDir = new File(fPictureBaseDir, COMPRESS_DIR_NAME);
|
||||
|
||||
// 5. 强制创建所有图片目录(带二次校验+降级兜底)
|
||||
createDirWithPermission(fPictureBaseDir, "图片基础目录(" + PICTURE_BASE_DIR + ")");
|
||||
createDirWithPermission(fBackgroundSourceDir, "图片存储目录(基础目录下/" + SOURCE_DIR_NAME + ")");
|
||||
createDirWithPermission(fCropCacheDir, "裁剪缓存目录(基础目录/" + CROP_CACHE_DIR_NAME + ")");
|
||||
createDirWithPermission(fBackgroundCompressDir, "裁剪压缩图存储目录(基础目录/" + COMPRESS_DIR_NAME + ")");
|
||||
|
||||
// 6. 目录创建后最终校验(确保所有目录已就绪)
|
||||
validatePictureDirs();
|
||||
|
||||
LogUtils.d(TAG, "【图片目录初始化】完成:" +
|
||||
"基础目录=" + fPictureBaseDir.getAbsolutePath() +
|
||||
"图片存储目录=" + fBackgroundSourceDir.getAbsolutePath() +
|
||||
",裁剪缓存目录=" + fCropCacheDir.getAbsolutePath() +
|
||||
",裁剪压缩图存储目录=" + fBackgroundCompressDir.getAbsolutePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化JSON配置目录(保留原逻辑:应用外置存储)
|
||||
* 【调整强化】新增目录创建后二次校验,失败则降级到应用内部缓存目录
|
||||
*/
|
||||
private void initJsonDirs() {
|
||||
// 1. 工具类根目录(应用外置存储)
|
||||
fUtilsDir = mContext.getExternalFilesDir(TAG);
|
||||
if (fUtilsDir == null) {
|
||||
LogUtils.e(TAG, "【JSON目录】应用外置存储不可用,切换到应用内部缓存目录");
|
||||
fUtilsDir = mContext.getDataDir();
|
||||
}
|
||||
// 2. 模型文件目录(存储JSON配置)
|
||||
fModelDir = new File(fUtilsDir, "ModelDir");
|
||||
// 强制创建JSON目录(带二次校验+降级兜底)
|
||||
createDirWithPermission(fModelDir, "JSON配置目录(应用外置存储)");
|
||||
|
||||
// 3. 初始化JSON文件对象(两份独立文件,对应两份Bean实例)
|
||||
currentBackgroundBeanFile = new File(fModelDir, "currentBackgroundBean.json");
|
||||
previewBackgroundBeanFile = new File(fModelDir, "previewBackgroundBean.json");
|
||||
|
||||
LogUtils.d(TAG, "【JSON目录初始化】完成:目录=" + fModelDir.getAbsolutePath() + ",正式JSON=" + currentBackgroundBeanFile.getName() + ",预览JSON=" + previewBackgroundBeanFile.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 【核心强化】创建目录并设置权限(适配系统公共目录/Pictures/PowerBell,确保实例化时目录就绪)
|
||||
* @param dir 要创建的目录
|
||||
* @param dirDesc 目录描述(用于日志打印)
|
||||
*/
|
||||
private void createDirWithPermission(File dir, String dirDesc) {
|
||||
if (dir == null) {
|
||||
LogUtils.e(TAG, "【文件管理】创建目录失败:目录对象为null(描述:" + dirDesc + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
// 第一步:主动检测并创建目录(递归创建所有父目录)
|
||||
if (!dir.exists()) {
|
||||
LogUtils.d(TAG, "【文件管理】" + dirDesc + "不存在,开始创建:" + dir.getAbsolutePath());
|
||||
dir.mkdirs(); // 递归创建所有父目录
|
||||
} else {
|
||||
LogUtils.d(TAG, "【文件管理】" + dirDesc + "已存在:" + dir.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 【新增】图片目录创建后最终校验(确保实例化时所有图片目录已就绪)
|
||||
*/
|
||||
private void validatePictureDirs() {
|
||||
LogUtils.d(TAG, "【图片目录校验】开始校验所有图片目录...");
|
||||
boolean allReady = true;
|
||||
if (!fPictureBaseDir.exists() || !fPictureBaseDir.isDirectory()) {
|
||||
LogUtils.e(TAG, "【图片目录校验】图片基础目录未就绪:" + fPictureBaseDir.getAbsolutePath());
|
||||
allReady = false;
|
||||
}
|
||||
if (!fBackgroundSourceDir.exists() || !fBackgroundSourceDir.isDirectory()) {
|
||||
LogUtils.e(TAG, "【图片目录校验】图片存储目录未就绪:" + fBackgroundSourceDir.getAbsolutePath());
|
||||
allReady = false;
|
||||
}
|
||||
if (!fCropCacheDir.exists() || !fCropCacheDir.isDirectory()) {
|
||||
LogUtils.e(TAG, "【图片目录校验】裁剪缓存目录未就绪:" + fCropCacheDir.getAbsolutePath());
|
||||
allReady = false;
|
||||
}
|
||||
if (!fBackgroundCompressDir.exists() || !fBackgroundCompressDir.isDirectory()) {
|
||||
LogUtils.e(TAG, "【图片目录校验】压缩图目录未就绪:" + fBackgroundCompressDir.getAbsolutePath());
|
||||
allReady = false;
|
||||
}
|
||||
if (allReady) {
|
||||
LogUtils.d(TAG, "【图片目录校验】所有图片目录均已就绪!");
|
||||
} else {
|
||||
LogUtils.e(TAG, "【图片目录校验】部分目录未就绪,可能影响后续功能!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化所有文件(裁剪文件→图片缓存目录,结果文件→图片存储目录)
|
||||
*/
|
||||
private void initAllFiles() {
|
||||
// 1. 裁剪临时文件
|
||||
//mCropSourceFile = new File(fCropCacheDir, CROP_TEMP_FILE_NAME);
|
||||
// 2. 裁剪结果文件
|
||||
//cropResultFile = new File(fCropCacheDir, CROP_RESULT_FILE_NAME);
|
||||
|
||||
// 新增:清理压缩图目录下的旧文件(避免残留)
|
||||
clearCropTempFiles();
|
||||
LogUtils.d(TAG, "【文件初始化】完成。");
|
||||
}
|
||||
|
||||
// 【核心实现】定义 getFileProviderUri 方法:将 File 转为 ContentUri(适配 FileProvider)
|
||||
public Uri getFileProviderUri(File file) {
|
||||
Log.d("BackgroundSourceUtils", "getFileProviderUri: 生成FileProvider Uri,文件路径:" + file.getAbsolutePath());
|
||||
Uri contentUri = null;
|
||||
try {
|
||||
// 适配 Android 7.0+:使用 FileProvider 生成 ContentUri(避免 FileUriExposedException)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
contentUri = FileProvider.getUriForFile(
|
||||
mContext,
|
||||
FILE_PROVIDER_AUTHORITY, // 与清单文件中一致
|
||||
file
|
||||
);
|
||||
Log.d("BackgroundSourceUtils", "getFileProviderUri: 7.0+ 生成ContentUri:" + contentUri.toString());
|
||||
} else {
|
||||
// 适配 Android 7.0 以下:直接使用 File.toURI()(兼容旧版本)
|
||||
contentUri = Uri.fromFile(file);
|
||||
Log.d("BackgroundSourceUtils", "getFileProviderUri: 7.0以下 生成FileUri:" + contentUri.toString());
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
// 捕获异常(如文件路径无效、授权不匹配等)
|
||||
Log.e("BackgroundSourceUtils", "getFileProviderUri: 生成Uri失败,异常:" + e.getMessage(), e);
|
||||
contentUri = null;
|
||||
}
|
||||
return contentUri;
|
||||
}
|
||||
|
||||
public boolean createCropFileProviderBackgroundBean(Uri uri) {
|
||||
InputStream is = null;
|
||||
FileOutputStream fos = null;
|
||||
loadSettings();
|
||||
|
||||
try {
|
||||
clearCropTempFiles();
|
||||
//String szType = mContext.getContentResolver().getType(uri);
|
||||
// 2. 截取MIME类型后缀(如从image/jpeg中提取jpeg)【核心新增逻辑】
|
||||
String fileSuffix = FileUtils.getFileSuffix(mContext, uri);
|
||||
String newCropFileName = UUID.randomUUID().toString() + System.currentTimeMillis();
|
||||
mCropSourceFile = new File(fCropCacheDir, newCropFileName + "." + fileSuffix);
|
||||
mCropResultFile = new File(fCropCacheDir, "SelectCompress_" + newCropFileName + "." + fileSuffix);
|
||||
|
||||
mCropSourceFile.createNewFile();
|
||||
mCropResultFile.createNewFile();
|
||||
|
||||
// 1. 打开Uri输入流(兼容content:///file:// 等多种Uri格式)
|
||||
is = mContext.getContentResolver().openInputStream(uri);
|
||||
if (is == null) {
|
||||
LogUtils.e(TAG, "【选图解析】ContentResolver打开Uri失败,Uri:" + uri.toString());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 初始化选图临时文件输出流(Java7 手动创建流,不依赖try-with-resources)
|
||||
fos = new FileOutputStream(mCropSourceFile);
|
||||
byte[] buffer = new byte[1024 * 8]; // 8KB缓冲区,平衡读写性能与内存占用
|
||||
int readLen; // 每次读取的字节长度
|
||||
|
||||
// 3. 流复制(Java7 标准while循环,避免Java8+语法)
|
||||
while ((readLen = is.read(buffer)) != -1) {
|
||||
fos.write(buffer, 0, readLen); // 精准写入读取到的字节,避免空字节填充
|
||||
}
|
||||
|
||||
// 4. 强制同步写入磁盘(解决Android 10+ 异步写入导致的文件无效问题)
|
||||
fos.flush();
|
||||
if (fos != null) {
|
||||
try {
|
||||
fos.getFD().sync(); // 确保数据写入物理磁盘,而非缓存
|
||||
} catch (IOException e) {
|
||||
LogUtils.w(TAG, "【选图解析】文件同步到磁盘失败,用flush()兜底:" + e.getMessage());
|
||||
fos.flush();
|
||||
}
|
||||
}
|
||||
|
||||
previewBackgroundBean.setBackgroundFileName(mCropSourceFile.getName());
|
||||
previewBackgroundBean.setBackgroundFilePath(mCropSourceFile.getAbsolutePath());
|
||||
|
||||
previewBackgroundBean.setBackgroundScaledCompressFileName(mCropResultFile.getName());
|
||||
previewBackgroundBean.setBackgroundScaledCompressFilePath(mCropResultFile.getAbsolutePath());
|
||||
saveSettings();
|
||||
|
||||
// 6. 解析成功日志(打印文件信息,便于问题排查)
|
||||
LogUtils.d(TAG, "【选图解析】Uri解析成功!");
|
||||
LogUtils.d(TAG, "→ 原Uri:" + uri.toString());
|
||||
LogUtils.d(TAG, "→ 目标临时文件:" + mCropSourceFile.getAbsolutePath());
|
||||
LogUtils.d(TAG, "→ 目标临时文件大小:" + mCropSourceFile.length() + " bytes");
|
||||
LogUtils.d(TAG, "→ 目标剪裁临时文件:" + mCropResultFile.getAbsolutePath());
|
||||
LogUtils.d(TAG, "→ 目标剪裁临时文件大小:" + mCropResultFile.length() + " bytes");
|
||||
return true;
|
||||
|
||||
} catch (Exception e) {
|
||||
// 捕获所有异常(IO异常/空指针等),避免崩溃
|
||||
LogUtils.e(TAG, "【选图解析】流复制异常:" + e.getMessage(), e);
|
||||
// 异常时清理无效文件,防止残留
|
||||
clearCropTempFiles();
|
||||
return false;
|
||||
|
||||
} finally {
|
||||
// 7. 手动关闭流资源(Java7 标准写法,避免内存泄漏)
|
||||
if (is != null) {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
LogUtils.e(TAG, "【选图解析】输入流关闭失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
if (fos != null) {
|
||||
try {
|
||||
fos.close();
|
||||
} catch (IOException e) {
|
||||
LogUtils.e(TAG, "【选图解析】输出流关闭失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载背景图片配置数据(核心:确保current/preview是两份独立的BackgroundBean实例)
|
||||
*/
|
||||
public void loadSettings() {
|
||||
// 1. 加载正式Bean(独立实例:从currentBackgroundBean.json加载,不存在则新建)
|
||||
currentBackgroundBean = BackgroundBean.loadBeanFromFile(currentBackgroundBeanFile.getAbsolutePath(), BackgroundBean.class);
|
||||
if (currentBackgroundBean == null) {
|
||||
currentBackgroundBean = new BackgroundBean(); // 正式Bean独立实例初始化
|
||||
BackgroundBean.saveBeanToFile(currentBackgroundBeanFile.getAbsolutePath(), currentBackgroundBean);
|
||||
LogUtils.d(TAG, "【配置管理】正式背景Bean不存在,创建独立实例并保存到JSON");
|
||||
}
|
||||
|
||||
// 2. 加载预览Bean(独立实例:从previewBackgroundBean.json加载,不存在则新建,与正式Bean完全分离)
|
||||
previewBackgroundBean = BackgroundBean.loadBeanFromFile(previewBackgroundBeanFile.getAbsolutePath(), BackgroundBean.class);
|
||||
if (previewBackgroundBean == null) {
|
||||
previewBackgroundBean = new BackgroundBean(); // 预览Bean独立实例初始化
|
||||
BackgroundBean.saveBeanToFile(previewBackgroundBeanFile.getAbsolutePath(), previewBackgroundBean);
|
||||
LogUtils.d(TAG, "【配置管理】预览背景Bean不存在,创建独立实例并保存到JSON");
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------ 对外提供的核心方法(路径已适配新目录)------------------------------
|
||||
public BackgroundBean getCurrentBackgroundBean() {
|
||||
return currentBackgroundBean;
|
||||
}
|
||||
|
||||
public BackgroundBean getPreviewBackgroundBean() {
|
||||
return previewBackgroundBean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取正式背景图片路径(修复:移除每次 loadSettings(),避免 Bean 被覆盖;强化非空校验)
|
||||
*/
|
||||
public String getCurrentBackgroundFilePath() {
|
||||
String fileName = currentBackgroundBean.getBackgroundFileName();
|
||||
if (TextUtils.isEmpty(fileName)) {
|
||||
LogUtils.e(TAG, "【路径管理】正式背景文件名为空,返回空路径");
|
||||
return "";
|
||||
}
|
||||
File file = new File(fBackgroundSourceDir, fileName);
|
||||
LogUtils.d(TAG, "【路径管理】正式背景路径:" + file.getAbsolutePath());
|
||||
return file.getAbsolutePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取预览背景图片路径(修复:移除每次 loadSettings(),避免 Bean 被覆盖;强化非空校验)
|
||||
*/
|
||||
public String getPreviewBackgroundFilePath() {
|
||||
String fileName = previewBackgroundBean.getBackgroundFileName();
|
||||
if (TextUtils.isEmpty(fileName)) {
|
||||
LogUtils.e(TAG, "【路径管理】预览背景文件名为空,返回空路径");
|
||||
return "";
|
||||
}
|
||||
File file = new File(fBackgroundSourceDir, fileName);
|
||||
LogUtils.d(TAG, "【路径管理】预览背景路径:" + file.getAbsolutePath());
|
||||
return file.getAbsolutePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取预览背景压缩图片路径(同步修复:移除 loadSettings(),强化非空校验,统一指向BackgroundCrops目录)
|
||||
*/
|
||||
public String getPreviewBackgroundScaledCompressFilePath() {
|
||||
String compressFileName = previewBackgroundBean.getBackgroundScaledCompressFileName();
|
||||
if (TextUtils.isEmpty(compressFileName)) {
|
||||
LogUtils.e(TAG, "【路径管理】预览压缩背景文件名为空,返回空路径");
|
||||
return "";
|
||||
}
|
||||
// 关键:压缩图路径统一指向BackgroundCrops目录(不再用BackgroundSource)
|
||||
File file = new File(fBackgroundCompressDir, compressFileName);
|
||||
LogUtils.d(TAG, "【路径管理】预览压缩背景路径(BackgroundCrops目录):" + file.getAbsolutePath());
|
||||
return file.getAbsolutePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增:获取正式背景压缩图片路径(统一指向BackgroundCrops目录,对外提供调用)
|
||||
*/
|
||||
public String getCurrentBackgroundScaledCompressFilePath() {
|
||||
String compressFileName = currentBackgroundBean.getBackgroundScaledCompressFileName();
|
||||
if (TextUtils.isEmpty(compressFileName)) {
|
||||
LogUtils.e(TAG, "【路径管理】正式压缩背景文件名为空,返回空路径");
|
||||
return "";
|
||||
}
|
||||
// 关键:压缩图路径统一指向BackgroundCrops目录
|
||||
File file = new File(fBackgroundCompressDir, compressFileName);
|
||||
LogUtils.d(TAG, "【路径管理】正式压缩背景路径(BackgroundCrops目录):" + file.getAbsolutePath());
|
||||
return file.getAbsolutePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存配置(核心:将两份独立Bean实例,分别写入各自的JSON文件)
|
||||
*/
|
||||
public void saveSettings() {
|
||||
if(currentBackgroundBean != null && previewBackgroundBean != null) {
|
||||
BackgroundBean.saveBeanToFile(currentBackgroundBeanFile.getAbsolutePath(), currentBackgroundBean); // 正式Bean→正式JSON
|
||||
BackgroundBean.saveBeanToFile(previewBackgroundBeanFile.getAbsolutePath(), previewBackgroundBean); // 预览Bean→预览JSON
|
||||
LogUtils.d(TAG, "【配置管理】两份配置保存成功:正式JSON=" + currentBackgroundBeanFile.getAbsolutePath() + ",预览JSON=" + previewBackgroundBeanFile.getAbsolutePath());
|
||||
return;
|
||||
}
|
||||
LogUtils.d(TAG, "【配置管理】两份配置保存失败。currentBackgroundBean 与 previewBackgroundBean 有空值。");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图片基础目录路径(对外提供:/Pictures/PowerBell/)
|
||||
*/
|
||||
public String getBackgroundSourceDirPath() {
|
||||
return fBackgroundSourceDir.getAbsolutePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增:获取压缩图统一存储目录路径(对外提供:/Pictures/PowerBell/BackgroundCrops/)
|
||||
*/
|
||||
public String getBackgroundCompressDirPath() {
|
||||
return fBackgroundCompressDir.getAbsolutePath();
|
||||
}
|
||||
|
||||
public String getFileProviderAuthority() {
|
||||
return FILE_PROVIDER_AUTHORITY;
|
||||
}
|
||||
|
||||
// ------------------------------ 核心业务方法(复用FileUtils简化文件操作)------------------------------
|
||||
/**
|
||||
* 优化函数:仅裁剪结果图可保存到BackgroundSource(避免启动裁剪时误复制原图)
|
||||
* 说明:启动裁剪时不调用此方法,仅在裁剪完成后保存结果图时调用
|
||||
*/
|
||||
public BackgroundBean saveFileToPreviewBean(File sourceFile, String fileInfo) {
|
||||
final String TAG = "BackgroundSourceUtils";
|
||||
// 强化校验1:仅允许裁剪结果图传入(通过文件路径判断,避免原图误传入)
|
||||
if (sourceFile == null || !sourceFile.exists() || sourceFile.length() <= 0) {
|
||||
Log.e(TAG, "【保存优化】源文件无效,拒绝保存:" + (sourceFile != null ? sourceFile.getAbsolutePath() : "null"));
|
||||
return previewBackgroundBean;
|
||||
}
|
||||
|
||||
// 强化校验2:排除原图路径(避免启动裁剪时传入原图复制)
|
||||
String originalImageDir = mContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES).getAbsolutePath(); // 原图存储目录(如相册/拍照目录)
|
||||
if (sourceFile.getAbsolutePath().contains(originalImageDir)) {
|
||||
Log.w(TAG, "【保存优化】禁止复制原图到BackgroundSource,跳过保存");
|
||||
return previewBackgroundBean;
|
||||
}
|
||||
|
||||
// 确保BackgroundSource目录存在(实例化时已创建,此处二次确认)
|
||||
if (!fBackgroundSourceDir.exists()) {
|
||||
if (!fBackgroundSourceDir.mkdirs()) {
|
||||
Log.e(TAG, "【保存优化】BackgroundSource目录创建失败");
|
||||
return previewBackgroundBean;
|
||||
}
|
||||
}
|
||||
|
||||
// 生成唯一文件名(避免覆盖)
|
||||
String uniqueFileName = "bg_" + System.currentTimeMillis() + "_" + sourceFile.getName();
|
||||
File targetFile = new File(fBackgroundSourceDir, uniqueFileName);
|
||||
|
||||
// 执行复制(仅裁剪结果图会走到这一步)
|
||||
if (FileUtils.copyFile(sourceFile, targetFile)) {
|
||||
Log.d(TAG, "【保存优化】裁剪结果图保存成功:" + targetFile.getAbsolutePath());
|
||||
// 更新预览Bean(原有逻辑保留)
|
||||
previewBackgroundBean.setBackgroundFileName(uniqueFileName);
|
||||
previewBackgroundBean.setBackgroundFilePath(targetFile.getAbsolutePath());
|
||||
previewBackgroundBean.setBackgroundFileInfo(fileInfo);
|
||||
previewBackgroundBean.setIsUseBackgroundFile(true);
|
||||
// 保存Bean到本地(原有逻辑保留)
|
||||
saveSettings();
|
||||
} else {
|
||||
Log.e(TAG, "【保存优化】裁剪结果图复制失败:" + sourceFile.getAbsolutePath() + " → " + targetFile.getAbsolutePath());
|
||||
}
|
||||
|
||||
return previewBackgroundBean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交预览背景到正式背景(预览Bean → 正式Bean:深拷贝,新建正式Bean实例+逐字段拷贝)
|
||||
* 核心:深拷贝后,修改正式Bean不会影响预览Bean,两份实例完全独立,压缩图路径统一指向BackgroundCrops
|
||||
*/
|
||||
public void commitPreviewSourceToCurrent() {
|
||||
// 深拷贝第一步:新建正式Bean独立实例(彻底脱离预览Bean的引用)
|
||||
currentBackgroundBean = new BackgroundBean();
|
||||
// 深拷贝第二步:逐字段拷贝预览Bean的所有值(压缩图路径同步指向BackgroundCrops)
|
||||
currentBackgroundBean.setBackgroundFileName(previewBackgroundBean.getBackgroundFileName());
|
||||
currentBackgroundBean.setBackgroundFilePath(previewBackgroundBean.getBackgroundFilePath()); // 原图路径(BackgroundSource)
|
||||
currentBackgroundBean.setBackgroundFileInfo(previewBackgroundBean.getBackgroundFileInfo());
|
||||
currentBackgroundBean.setIsUseBackgroundFile(previewBackgroundBean.isUseBackgroundFile());
|
||||
currentBackgroundBean.setBackgroundScaledCompressFileName(previewBackgroundBean.getBackgroundScaledCompressFileName());
|
||||
currentBackgroundBean.setBackgroundScaledCompressFilePath(previewBackgroundBean.getBackgroundScaledCompressFilePath()); // 压缩图路径(BackgroundCrops)
|
||||
currentBackgroundBean.setIsUseBackgroundScaledCompressFile(previewBackgroundBean.isUseBackgroundScaledCompressFile()); // 重命名字段:拷贝压缩图启用状态
|
||||
currentBackgroundBean.setBackgroundWidth(previewBackgroundBean.getBackgroundWidth());
|
||||
currentBackgroundBean.setBackgroundHeight(previewBackgroundBean.getBackgroundHeight());
|
||||
currentBackgroundBean.setPixelColor(previewBackgroundBean.getPixelColor());
|
||||
|
||||
// 拷贝一份缓存图片文件到正式背景文件夹
|
||||
String previewFileName = previewBackgroundBean.getBackgroundFileName();
|
||||
String previewCropFileName = previewBackgroundBean.getBackgroundScaledCompressFileName();
|
||||
File previewFile = new File(previewBackgroundBean.getBackgroundFilePath());
|
||||
File previewCropFile = new File(previewBackgroundBean.getBackgroundScaledCompressFilePath());
|
||||
File currentFile = new File(fBackgroundSourceDir, previewFileName);
|
||||
File currentCropFile = new File(fBackgroundCompressDir, previewCropFileName);
|
||||
FileUtils.copyFile(previewFile, currentFile);
|
||||
FileUtils.copyFile(previewCropFile, currentCropFile);
|
||||
// 更新当前背景文件路径
|
||||
currentBackgroundBean.setBackgroundFilePath(currentFile.getAbsolutePath()); // 原图路径(BackgroundSource)
|
||||
currentBackgroundBean.setBackgroundScaledCompressFilePath(currentCropFile.getAbsolutePath()); // 压缩图路径(BackgroundCrops)
|
||||
|
||||
saveSettings(); // 分别保存:正式Bean→currentJSON,预览Bean→previewJSON(两份独立)
|
||||
LogUtils.d(TAG, "【配置管理】预览背景深拷贝到正式Bean:两份实例独立,压缩图统一存储到BackgroundCrops");
|
||||
ToastUtils.show("背景图片应用成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 将正式背景同步到预览背景(正式Bean → 预览Bean:深拷贝,新建预览Bean实例+逐字段拷贝)
|
||||
* 核心:深拷贝后,修改预览Bean不会影响正式Bean,两份实例完全独立,压缩图路径统一指向BackgroundCrops
|
||||
*/
|
||||
public void setCurrentSourceToPreview() {
|
||||
LogUtils.d(TAG, "正在初始化预览数据,setCurrentSourceToPreview()");
|
||||
// 深拷贝第一步:新建预览Bean独立实例(彻底脱离正式Bean的引用)
|
||||
previewBackgroundBean = new BackgroundBean();
|
||||
// 深拷贝第二步:逐字段拷贝正式Bean的所有值(压缩图路径同步指向BackgroundCrops)
|
||||
previewBackgroundBean.setBackgroundFileName(currentBackgroundBean.getBackgroundFileName());
|
||||
previewBackgroundBean.setBackgroundFilePath(currentBackgroundBean.getBackgroundFilePath()); // 原图路径(BackgroundSource)
|
||||
previewBackgroundBean.setBackgroundFileInfo(currentBackgroundBean.getBackgroundFileInfo());
|
||||
previewBackgroundBean.setIsUseBackgroundFile(currentBackgroundBean.isUseBackgroundFile());
|
||||
previewBackgroundBean.setBackgroundScaledCompressFileName(currentBackgroundBean.getBackgroundScaledCompressFileName());
|
||||
previewBackgroundBean.setBackgroundScaledCompressFilePath(currentBackgroundBean.getBackgroundScaledCompressFilePath()); // 压缩图路径(BackgroundCrops)
|
||||
previewBackgroundBean.setIsUseBackgroundScaledCompressFile(currentBackgroundBean.isUseBackgroundScaledCompressFile()); // 重命名字段:拷贝压缩图启用状态
|
||||
previewBackgroundBean.setBackgroundWidth(currentBackgroundBean.getBackgroundWidth());
|
||||
previewBackgroundBean.setBackgroundHeight(currentBackgroundBean.getBackgroundHeight());
|
||||
previewBackgroundBean.setPixelColor(currentBackgroundBean.getPixelColor());
|
||||
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具方法:清理旧文件(避免文件锁定/残留,适配系统公共目录)【内部私有,不对外暴露】
|
||||
* @param file 要清理的文件
|
||||
* @param fileDesc 文件描述(用于日志打印)
|
||||
*/
|
||||
private void clearOldFile(File file, String fileDesc) {
|
||||
if (file == null) {
|
||||
return;
|
||||
}
|
||||
if (file.exists()) {
|
||||
file.delete();
|
||||
LogUtils.w(TAG, "【文件管理】" + fileDesc + "已删除");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增:清理压缩图目录下的旧文件(避免残留,初始化时调用)
|
||||
*/
|
||||
void clearCropTempFiles() {
|
||||
for (File file : fCropCacheDir.listFiles()) {
|
||||
clearOldFile(file, "旧裁剪缓存文件(" + file.getAbsolutePath() + ")");
|
||||
}
|
||||
mCropSourceFile = null;
|
||||
mCropResultFile = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 适配原调用:mBgSourceUtils.copyFile(new File(""), parentDir)
|
||||
* 核心:复用FileUtils,支持「空源文件→仅创建目标目录」和「正常文件复制」两种场景
|
||||
* @param source 源文件(可为空/空文件,为空时仅创建目标目录)
|
||||
* @param target 目标文件/目录(若源文件为空,target视为目录并创建)
|
||||
* @return true=复制/创建成功,false=失败
|
||||
*/
|
||||
public boolean copyFile(File source, File target) {
|
||||
// 场景1:源文件为空(适配 new File("") 调用)→ 仅创建目标目录
|
||||
if (source == null || (source.exists() && source.length() <= 0) || TextUtils.isEmpty(source.getPath())) {
|
||||
if (target == null) {
|
||||
LogUtils.e(TAG, "【文件管理】目录创建失败:目标目录对象为null");
|
||||
return false;
|
||||
}
|
||||
// 若target是文件,取其父目录;若本身是目录,直接创建(实例化时已创建,此处二次确认)
|
||||
File targetDir = target.isFile() ? target.getParentFile() : target;
|
||||
createDirWithPermission(targetDir, "空源文件场景-目录创建(/Pictures/PowerBell下)");
|
||||
LogUtils.d(TAG, "【文件管理】空源文件场景:目录创建完成,路径=" + targetDir.getAbsolutePath());
|
||||
return true;
|
||||
}
|
||||
|
||||
// 场景2:正常文件复制(源文件非空且存在)→ 复用FileUtils.copyFile,确保高效兼容
|
||||
return FileUtils.copyFile(source, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具方法:获取目录类型描述(用于日志调试,明确目录类型,适配新目录结构)
|
||||
* @param dir 目标目录
|
||||
* @return 目录类型描述
|
||||
*/
|
||||
public String getDirTypeDesc(File dir) {
|
||||
if (dir == null) {
|
||||
return "未知目录(null)";
|
||||
}
|
||||
String dirPath = dir.getAbsolutePath();
|
||||
String publicPicturePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath();
|
||||
String externalFilesPath = mContext.getExternalFilesDir(null) != null ? mContext.getExternalFilesDir(null).getAbsolutePath() : "";
|
||||
String cachePath = mContext.getCacheDir().getAbsolutePath();
|
||||
|
||||
if (!TextUtils.isEmpty(publicPicturePath)) {
|
||||
if (dirPath.contains(publicPicturePath + File.separator + "PowerBell" + File.separator + COMPRESS_DIR_NAME)) {
|
||||
return "系统公共图片目录(/Pictures/PowerBell/BackgroundCrops,压缩图统一存储目录)"; // 新增压缩图目录描述
|
||||
} else if (dirPath.contains(publicPicturePath + File.separator + "PowerBell")) {
|
||||
return "系统公共图片目录(/Pictures/PowerBell,图片存储/裁剪目录)";
|
||||
}
|
||||
} else if (!TextUtils.isEmpty(externalFilesPath) && dirPath.contains(externalFilesPath)) {
|
||||
return "应用私有外部目录(getExternalFilesDir(),JSON配置目录)";
|
||||
} else if (dirPath.contains(cachePath)) {
|
||||
return "应用内部缓存目录(getCacheDir(),兜底目录)";
|
||||
} else {
|
||||
return "外部存储目录(非应用私有,权限受限)";
|
||||
}
|
||||
return "未知目录";
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增:迁移旧压缩图路径到新目录(BackgroundCrops),兼容历史数据
|
||||
* @param bean 要迁移的BackgroundBean(正式/预览)
|
||||
* @param isCurrentBean 是否是正式Bean(用于日志区分)
|
||||
*/
|
||||
private void migrateCompressPathToNewDir(BackgroundBean bean, boolean isCurrentBean) {
|
||||
String oldCompressPath = bean.getBackgroundScaledCompressFilePath();
|
||||
String beanType = isCurrentBean ? "正式Bean" : "预览Bean";
|
||||
|
||||
// 校验:旧路径非空,且不在BackgroundCrops目录下,才需要迁移
|
||||
if (TextUtils.isEmpty(oldCompressPath) || oldCompressPath.contains(fBackgroundCompressDir.getAbsolutePath())) {
|
||||
LogUtils.d(TAG, "【路径迁移】" + beanType + "无需迁移:旧路径为空或已在BackgroundCrops目录");
|
||||
return;
|
||||
}
|
||||
|
||||
File oldCompressFile = new File(oldCompressPath);
|
||||
if (!oldCompressFile.exists() || !oldCompressFile.isFile() || oldCompressFile.length() <= 0) {
|
||||
LogUtils.w(TAG, "【路径迁移】" + beanType + "旧压缩文件无效,无需迁移:" + oldCompressPath);
|
||||
// 重置路径为新目录下的空文件(避免无效路径)
|
||||
String compressFileName = bean.getBackgroundScaledCompressFileName();
|
||||
if (!TextUtils.isEmpty(compressFileName)) {
|
||||
File newCompressFile = new File(fBackgroundCompressDir, compressFileName);
|
||||
bean.setBackgroundScaledCompressFilePath(newCompressFile.getAbsolutePath());
|
||||
saveSettings();
|
||||
LogUtils.d(TAG, "【路径迁移】" + beanType + "重置压缩路径到BackgroundCrops:" + newCompressFile.getAbsolutePath());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 迁移逻辑:复制旧文件到新目录,更新Bean路径,删除旧文件
|
||||
String compressFileName = bean.getBackgroundScaledCompressFileName();
|
||||
if (TextUtils.isEmpty(compressFileName)) {
|
||||
compressFileName = "ScaledCompress_" + System.currentTimeMillis() + ".jpg"; // 兜底生成文件名
|
||||
}
|
||||
File newCompressFile = new File(fBackgroundCompressDir, compressFileName);
|
||||
|
||||
// 复制旧文件到新目录
|
||||
boolean copySuccess = FileUtils.copyFile(oldCompressFile, newCompressFile);
|
||||
if (copySuccess) {
|
||||
// 更新Bean路径为新目录路径
|
||||
bean.setBackgroundScaledCompressFilePath(newCompressFile.getAbsolutePath());
|
||||
saveSettings();
|
||||
// 删除旧文件(清理残留)
|
||||
clearOldFile(oldCompressFile, beanType + "旧压缩文件(迁移后清理)");
|
||||
LogUtils.d(TAG, "【路径迁移】" + beanType + "压缩路径迁移成功:" + oldCompressPath + " → " + newCompressFile.getAbsolutePath());
|
||||
} else {
|
||||
LogUtils.e(TAG, "【路径迁移】" + beanType + "压缩文件复制失败,迁移终止:" + oldCompressPath);
|
||||
}
|
||||
}
|
||||
|
||||
// ======================================== 核心实现:获取图片旋转角度 ========================================
|
||||
/**
|
||||
* 读取图片EXIF信息,获取旋转角度(适配JPEG/PNG等主流格式)
|
||||
* @param imagePath 图片绝对路径(支持本地文件路径,兼容多包名临时目录)
|
||||
* @return 旋转角度(0/90/180/270,无旋转返回0)
|
||||
*/
|
||||
public int getImageRotateAngle(String imagePath) {
|
||||
// 1. 入参校验(避免空指针/无效路径)
|
||||
if (TextUtils.isEmpty(imagePath)) {
|
||||
Log.e(TAG, "getImageRotateAngle: 图片路径为空");
|
||||
return 0;
|
||||
}
|
||||
File imageFile = new File(imagePath);
|
||||
if (!imageFile.exists() || !imageFile.isFile() || imageFile.length() <= 0) {
|
||||
Log.e(TAG, "getImageRotateAngle: 图片文件无效,路径:" + imagePath);
|
||||
return 0;
|
||||
}
|
||||
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
// 2. 读取图片EXIF信息(优先用流读取,避免文件占用)
|
||||
inputStream = new FileInputStream(imageFile);
|
||||
ExifInterface exifInterface = new ExifInterface(inputStream);
|
||||
|
||||
// 3. 获取旋转角度标签(兼容不同设备的EXIF字段)
|
||||
int orientation = exifInterface.getAttributeInt(
|
||||
ExifInterface.TAG_ORIENTATION,
|
||||
ExifInterface.ORIENTATION_NORMAL
|
||||
);
|
||||
|
||||
// 4. 解析旋转角度(标准EXIF角度映射)
|
||||
switch (orientation) {
|
||||
case ExifInterface.ORIENTATION_ROTATE_90:
|
||||
return 90;
|
||||
case ExifInterface.ORIENTATION_ROTATE_180:
|
||||
return 180;
|
||||
case ExifInterface.ORIENTATION_ROTATE_270:
|
||||
return 270;
|
||||
default: // 正常/翻转等其他情况,均视为0度
|
||||
return 0;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// 兼容异常场景:如图片无EXIF信息、格式不支持(如WebP)
|
||||
Log.w(TAG, "getImageRotateAngle: 读取EXIF异常,路径:" + imagePath + ",错误:" + e.getMessage());
|
||||
return 0;
|
||||
} finally {
|
||||
// 5. 关闭流资源(避免内存泄漏/文件占用)
|
||||
if (inputStream != null) {
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "getImageRotateAngle: 流关闭失败,错误:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ======================================== 图片处理核心方法(压缩/裁剪/保存) ========================================
|
||||
/**
|
||||
* 压缩图片并保存(核心修复:路径非空校验+兜底路径,统一存储到BackgroundCrops目录)
|
||||
*/
|
||||
public void compressQualityToRecivedPicture(Bitmap bitmap) {
|
||||
// 兼容裁剪等旧调用:从工具类获取默认压缩路径(统一指向BackgroundCrops),转发至重载函数
|
||||
String defaultCompressPath = getPreviewBackgroundScaledCompressFilePath();
|
||||
compressQualityToRecivedPicture(bitmap, defaultCompressPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重载方法:指定路径压缩图片并保存(修复:压缩后同步路径到预览Bean,统一存储到BackgroundCrops)
|
||||
* 适配场景:裁剪后生成压缩图,强制绑定路径到预览Bean,避免路径错位
|
||||
* @param bitmap 待压缩的Bitmap(裁剪后的缩放图)
|
||||
* @param targetCompressPath 强制指定的压缩目标路径(从预览Bean获取/生成,默认指向BackgroundCrops)
|
||||
*/
|
||||
public void compressQualityToRecivedPicture(Bitmap bitmap, String targetCompressPath) {
|
||||
LogUtils.d(TAG, "【压缩启动】开始压缩图片(指定路径),Bitmap状态:" + (bitmap != null && !bitmap.isRecycled()));
|
||||
|
||||
if (bitmap == null || bitmap.isRecycled()) {
|
||||
ToastUtils.show("压缩失败:图片为空");
|
||||
LogUtils.e(TAG, "【压缩失败】Bitmap为空或已回收");
|
||||
return;
|
||||
}
|
||||
|
||||
OutputStream outStream = null;
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
LogUtils.d(TAG, "【压缩配置】目标路径(BackgroundCrops):" + targetCompressPath + ",Bitmap原始大小:" + bitmap.getByteCount() / 1024 + "KB");
|
||||
|
||||
File targetCompressFile = new File(targetCompressPath);
|
||||
if (targetCompressFile.exists()) {
|
||||
targetCompressFile.delete();
|
||||
}
|
||||
targetCompressFile.createNewFile();
|
||||
|
||||
// 写入压缩图(质量80,平衡清晰度和内存)
|
||||
fos = new FileOutputStream(targetCompressFile);
|
||||
outStream = new BufferedOutputStream(fos);
|
||||
boolean compressSuccess = bitmap.compress(Bitmap.CompressFormat.JPEG, 80, outStream);
|
||||
outStream.flush();
|
||||
// 强制同步到磁盘(避免异步写入导致控件读取不到文件)
|
||||
if (fos != null) {
|
||||
try {
|
||||
fos.getFD().sync();
|
||||
LogUtils.d(TAG, "【压缩保存】已强制同步到磁盘");
|
||||
} catch (IOException e) {
|
||||
LogUtils.w(TAG, "【压缩保存】sync()失败,flush()兜底:" + e.getMessage());
|
||||
outStream.flush();
|
||||
}
|
||||
}
|
||||
|
||||
LogUtils.d(TAG, "【压缩结果】" + (compressSuccess ? "成功" : "失败") + ",大小:" + targetCompressFile.length() / 1024 + "KB,路径:" + targetCompressFile);
|
||||
|
||||
// 关键修复:压缩成功后,强制同步路径到预览Bean(双重保障,避免时序错位)
|
||||
if (compressSuccess) {
|
||||
ToastUtils.show("图片压缩成功");
|
||||
} else {
|
||||
ToastUtils.show("图片压缩失败");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LogUtils.e(TAG, "【压缩异常】IO错误:" + e.getMessage(), e);
|
||||
ToastUtils.show("图片压缩失败");
|
||||
} finally {
|
||||
// 资源回收(避免内存泄漏)
|
||||
if (outStream != null) {
|
||||
try {
|
||||
outStream.close();
|
||||
} catch (IOException e) {
|
||||
LogUtils.e(TAG, "【流关闭失败】BufferedOutputStream:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
if (fos != null) {
|
||||
try {
|
||||
fos.close();
|
||||
} catch (IOException e) {
|
||||
LogUtils.e(TAG, "【流关闭失败】FileOutputStream:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
if (bitmap != null && !bitmap.isRecycled()) {
|
||||
bitmap.recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,176 +1,281 @@
|
||||
package cc.winboll.studio.powerbell.utils;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.UUID;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
/**
|
||||
* 文件读取工具类
|
||||
* 文件操作工具类
|
||||
* 功能:文件读写、复制、图片转换、文件名处理等常用文件操作
|
||||
* 适配:Java 7+,支持Android全版本
|
||||
* 注意:调用文件操作前需确保已获取存储权限(Android 6.0+ 需动态申请)
|
||||
*/
|
||||
|
||||
public class FileUtils {
|
||||
|
||||
/** 日志标签 */
|
||||
public static final String TAG = "FileUtils";
|
||||
/** 读取文件默认缓冲区大小(10KB) */
|
||||
private static final int BUFFER_SIZE = 10240;
|
||||
/** 最大读取文件大小(1GB),防止OOM */
|
||||
private static final long MAX_READ_FILE_SIZE = 1024 * 1024 * 1024;
|
||||
|
||||
//
|
||||
// 读取文件内容,作为字符串返回
|
||||
//
|
||||
// ====================================== 文件读取相关 ======================================
|
||||
|
||||
/**
|
||||
* 读取文件内容并转为字符串
|
||||
* @param filePath 文件绝对路径(非空)
|
||||
* @return 文件内容字符串
|
||||
* @throws IOException 异常:文件不存在、文件过大、读取失败等
|
||||
*/
|
||||
public static String readFileAsString(String filePath) throws IOException {
|
||||
// 1. 校验文件合法性
|
||||
File file = new File(filePath);
|
||||
if (!file.exists()) {
|
||||
throw new FileNotFoundException(filePath);
|
||||
}
|
||||
throw new FileNotFoundException("文件不存在:" + filePath);
|
||||
}
|
||||
if (file.length() > MAX_READ_FILE_SIZE) {
|
||||
throw new IOException("文件过大(超过1GB),禁止读取:" + filePath);
|
||||
}
|
||||
|
||||
if (file.length() > 1024 * 1024 * 1024) {
|
||||
throw new IOException("File is too large");
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder((int) (file.length()));
|
||||
// 创建字节输入流
|
||||
FileInputStream fis = new FileInputStream(filePath);
|
||||
// 创建一个长度为10240的Buffer
|
||||
byte[] bbuf = new byte[10240];
|
||||
// 用于保存实际读取的字节数
|
||||
int hasRead = 0;
|
||||
while ((hasRead = fis.read(bbuf)) > 0) {
|
||||
sb.append(new String(bbuf, 0, hasRead));
|
||||
}
|
||||
fis.close();
|
||||
// 2. 读取文件内容(使用StringBuilder高效拼接)
|
||||
StringBuilder sb = new StringBuilder((int) file.length());
|
||||
try (FileInputStream fis = new FileInputStream(file)) {
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
int readLen;
|
||||
// 循环读取缓冲区,避免一次性读取大文件导致OOM
|
||||
while ((readLen = fis.read(buffer)) > 0) {
|
||||
sb.append(new String(buffer, 0, readLen));
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
//
|
||||
// 根据文件路径读取byte[] 数组
|
||||
//
|
||||
/**
|
||||
* 读取文件内容并转为byte数组(适用于二进制文件:图片、音频等)
|
||||
* @param filePath 文件绝对路径(非空)
|
||||
* @return 文件内容byte数组
|
||||
* @throws IOException 异常:文件不存在、读取失败等
|
||||
*/
|
||||
public static byte[] readFileByBytes(String filePath) throws IOException {
|
||||
// 1. 校验文件合法性
|
||||
File file = new File(filePath);
|
||||
if (!file.exists()) {
|
||||
throw new FileNotFoundException(filePath);
|
||||
} else {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream((int) file.length());
|
||||
BufferedInputStream in = null;
|
||||
throw new FileNotFoundException("文件不存在:" + filePath);
|
||||
}
|
||||
|
||||
try {
|
||||
in = new BufferedInputStream(new FileInputStream(file));
|
||||
short bufSize = 1024;
|
||||
byte[] buffer = new byte[bufSize];
|
||||
int len1;
|
||||
while (-1 != (len1 = in.read(buffer, 0, bufSize))) {
|
||||
bos.write(buffer, 0, len1);
|
||||
}
|
||||
// 2. 缓冲流读取(高效,减少IO次数)
|
||||
try (ByteArrayOutputStream bos = new ByteArrayOutputStream((int) file.length());
|
||||
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file))) {
|
||||
|
||||
byte[] var7 = bos.toByteArray();
|
||||
return var7;
|
||||
} finally {
|
||||
try {
|
||||
if (in != null) {
|
||||
in.close();
|
||||
}
|
||||
} catch (IOException var14) {
|
||||
var14.printStackTrace();
|
||||
}
|
||||
|
||||
bos.close();
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
int readLen;
|
||||
while ((readLen = bis.read(buffer)) != -1) {
|
||||
bos.write(buffer, 0, readLen);
|
||||
}
|
||||
bos.flush();
|
||||
return bos.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// 文件复制函数
|
||||
//
|
||||
// ====================================== 文件复制相关 ======================================
|
||||
|
||||
/**
|
||||
* 基于FileChannel复制文件(高效,适用于大文件复制)
|
||||
* @param source 源文件(非空,必须存在)
|
||||
* @param dest 目标文件(非空,父目录会自动创建)
|
||||
* @throws IOException 异常:源文件不存在、复制失败等
|
||||
*/
|
||||
public static void copyFileUsingFileChannels(File source, File dest) throws IOException {
|
||||
FileChannel inputChannel = null;
|
||||
FileChannel outputChannel = null;
|
||||
try {
|
||||
inputChannel = new FileInputStream(source).getChannel();
|
||||
outputChannel = new FileOutputStream(dest).getChannel();
|
||||
// 1. 校验源文件合法性
|
||||
if (!source.exists() || !source.isFile()) {
|
||||
throw new FileNotFoundException("源文件不存在或不是文件:" + source.getAbsolutePath());
|
||||
}
|
||||
|
||||
// 2. 创建目标文件父目录
|
||||
if (!dest.getParentFile().exists()) {
|
||||
dest.getParentFile().mkdirs();
|
||||
}
|
||||
|
||||
// 3. 通道复制(try-with-resources 自动关闭通道,无需手动关闭)
|
||||
try (FileChannel inputChannel = new FileInputStream(source).getChannel();
|
||||
FileChannel outputChannel = new FileOutputStream(dest).getChannel()) {
|
||||
// 从输入通道复制到输出通道(高效,底层优化)
|
||||
outputChannel.transferFrom(inputChannel, 0, inputChannel.size());
|
||||
} finally {
|
||||
inputChannel.close();
|
||||
outputChannel.close();
|
||||
LogUtils.d(TAG, "文件复制成功(FileChannel):" + source.getAbsolutePath() + " → " + dest.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将文件生成位图
|
||||
* @param path
|
||||
* @return
|
||||
* @throws IOException
|
||||
* 简化版文件复制(基于NIO Files工具类,代码简洁,适用于中小文件)
|
||||
* @param oldFile 源文件(非空,必须存在)
|
||||
* @param newFile 目标文件(非空,父目录会自动创建)
|
||||
* @return 复制结果:true-成功,false-失败
|
||||
*/
|
||||
public static BitmapDrawable getImageDrawable(String path)
|
||||
throws IOException {
|
||||
//打开文件
|
||||
File file = new File(path);
|
||||
if (!file.exists()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
|
||||
int BUFFER_SIZE = 1000;
|
||||
byte[] bt = new byte[BUFFER_SIZE];
|
||||
|
||||
//得到文件的输入流
|
||||
InputStream in = new FileInputStream(file);
|
||||
|
||||
//将文件读出到输出流中
|
||||
int readLength = in.read(bt);
|
||||
while (readLength != -1) {
|
||||
outStream.write(bt, 0, readLength);
|
||||
readLength = in.read(bt);
|
||||
}
|
||||
|
||||
//转换成byte 后 再格式化成位图
|
||||
byte[] data = outStream.toByteArray();
|
||||
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);// 生成位图
|
||||
BitmapDrawable bd = new BitmapDrawable(bitmap);
|
||||
|
||||
return bd;
|
||||
}
|
||||
|
||||
public static boolean copyFile(File oldFile, File newFile) {
|
||||
//String oldPath = "path/to/original/file.txt";
|
||||
//String newPath = "path/to/new-location/for/file.txt";
|
||||
// 1. 校验源文件合法性
|
||||
if (oldFile == null || !oldFile.exists() || !oldFile.isFile()) {
|
||||
LogUtils.e(TAG, "源文件无效:" + (oldFile != null ? oldFile.getAbsolutePath() : "null"));
|
||||
return false;
|
||||
}
|
||||
|
||||
//File oldFile = new java.io.File(oldPath);
|
||||
//File newFile = new java.io.File(newPath);
|
||||
// 2. 创建目标文件父目录
|
||||
if (!newFile.getParentFile().exists()) {
|
||||
newFile.getParentFile().mkdirs();
|
||||
}
|
||||
|
||||
if (!oldFile.exists()) {
|
||||
//System.out.println("The original file does not exist.");
|
||||
LogUtils.d(TAG, "The original file does not exist.");
|
||||
} else {
|
||||
try {
|
||||
// 源文件路径
|
||||
Path sourcePath = Paths.get(oldFile.getPath());
|
||||
// 目标文件路径
|
||||
Path destPath = Paths.get(newFile.getPath());
|
||||
if(newFile.exists()) {
|
||||
newFile.delete();
|
||||
}
|
||||
Files.copy(sourcePath, destPath);
|
||||
LogUtils.d(TAG, "File copy successfully.");
|
||||
//System.out.println("File moved successfully.");
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||
//System.err.println("An error occurred while moving the file: " + e.getMessage());
|
||||
// 3. 复制文件(覆盖已有目标文件)
|
||||
try {
|
||||
Path sourcePath = Paths.get(oldFile.getPath());
|
||||
Path destPath = Paths.get(newFile.getPath());
|
||||
// 先删除已有目标文件(避免覆盖失败)
|
||||
if (newFile.exists()) {
|
||||
newFile.delete();
|
||||
}
|
||||
Files.copy(sourcePath, destPath);
|
||||
LogUtils.d(TAG, "文件复制成功(Files):" + oldFile.getAbsolutePath() + " → " + newFile.getAbsolutePath());
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "文件复制失败:" + e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ====================================== 图片文件相关 ======================================
|
||||
|
||||
/**
|
||||
* 从文件路径获取BitmapDrawable(适用于Android图片显示)
|
||||
* @param path 图片文件绝对路径(非空)
|
||||
* @return BitmapDrawable 图片对象(文件不存在/读取失败返回null)
|
||||
* @throws IOException 异常:文件读取IO错误
|
||||
*/
|
||||
public static BitmapDrawable getImageDrawable(String path) throws IOException {
|
||||
// 1. 校验文件合法性
|
||||
File file = new File(path);
|
||||
if (!file.exists() || !file.isFile()) {
|
||||
LogUtils.e(TAG, "图片文件不存在:" + path);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 2. 读取文件并转为BitmapDrawable(缓冲流读取,减少内存占用)
|
||||
try (InputStream is = new FileInputStream(file);
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
|
||||
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
int readLen;
|
||||
while ((readLen = is.read(buffer)) != -1) {
|
||||
bos.write(buffer, 0, readLen);
|
||||
}
|
||||
|
||||
// 3. 生成Bitmap并包装为BitmapDrawable
|
||||
byte[] imageBytes = bos.toByteArray();
|
||||
Bitmap bitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
|
||||
return new BitmapDrawable(bitmap);
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================== 文件名处理相关 ======================================
|
||||
|
||||
/**
|
||||
* 截取文件后缀名(兼容多 "." 场景,如"image.2025.png" → ".png")
|
||||
* @param file 目标文件(可为null)
|
||||
* @return 文件后缀名:带点(如".jpg"),无后缀/文件无效返回空字符串
|
||||
*/
|
||||
public static String getFileSuffixWithMultiDot(File file) {
|
||||
// 1. 校验文件合法性
|
||||
if (file == null || !file.isFile()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// 2. 提取文件名并查找最后一个 "."
|
||||
String fileName = file.getName();
|
||||
int lastDotIndex = fileName.lastIndexOf(".");
|
||||
|
||||
// 3. 校验后缀合法性(排除无后缀、以点结尾、后缀过长的异常文件)
|
||||
if (lastDotIndex == -1 // 无 "."
|
||||
|| lastDotIndex == fileName.length() - 1 // 以 "." 结尾(如".gitignore")
|
||||
|| (fileName.length() - lastDotIndex) > 5) { // 后缀长度超过5(异常文件名)
|
||||
return "";
|
||||
}
|
||||
|
||||
// 4. 返回小写后缀(统一格式,避免大小写不一致问题)
|
||||
return fileName.substring(lastDotIndex).toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成唯一文件名(优化版:唯一、合法、简洁)
|
||||
* 生成规则:UUID(去掉"-") + "_" + 时间戳 + 原文件后缀
|
||||
* @param refFile 参考文件(用于提取后缀名,可为null)
|
||||
* @return 唯一文件名(如"a1b2c3d4e5f6_1730000000000.jpg",无后缀则不带点)
|
||||
*/
|
||||
public static String createUniqueFileName(File refFile) {
|
||||
// 1. 获取参考文件的后缀名(自动容错null/无效文件)
|
||||
String suffix = getFileSuffixWithMultiDot(refFile);
|
||||
|
||||
// 2. 生成唯一标识(UUID确保全局唯一,时间戳进一步降低重复概率)
|
||||
String uniqueId = UUID.randomUUID().toString().replace("-", ""); // 去掉"-"简化文件名
|
||||
long timeStamp = System.currentTimeMillis();
|
||||
|
||||
// 3. 拼接文件名(分场景处理,避免多余点)
|
||||
if (suffix.isEmpty()) {
|
||||
// 无后缀:唯一ID + 时间戳
|
||||
return String.format("%s_%d", uniqueId, timeStamp);
|
||||
} else {
|
||||
// 有后缀:唯一ID + 时间戳 + 后缀(无多余点)
|
||||
return String.format("%s_%d%s", uniqueId, timeStamp, suffix);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制输入流到文件(兼容Uri解析失败场景)
|
||||
*/
|
||||
public static void copyStreamToFile(InputStream inputStream, File file) throws IOException {
|
||||
if (inputStream == null || file == null) {
|
||||
throw new IllegalArgumentException("InputStream或File不能为空");
|
||||
}
|
||||
File parentDir = file.getParentFile();
|
||||
if (!parentDir.exists() && !parentDir.mkdirs()) {
|
||||
throw new IOException("无法创建父目录:" + parentDir.getAbsolutePath());
|
||||
}
|
||||
try {
|
||||
OutputStream outputStream = new FileOutputStream(file);
|
||||
byte[] buffer = new byte[1024];
|
||||
int length;
|
||||
while ((length = inputStream.read(buffer)) != -1) {
|
||||
outputStream.write(buffer, 0, length);
|
||||
}
|
||||
outputStream.flush();
|
||||
} finally {
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (IOException e) {
|
||||
LogUtils.e("FileUtils", "关闭输入流失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String getFileSuffix(Context context, Uri uri){
|
||||
String szType = context.getContentResolver().getType(uri);
|
||||
// 2. 截取MIME类型后缀(如从image/jpeg中提取jpeg)【核心新增逻辑】
|
||||
String fileSuffix = "";
|
||||
if (szType != null && szType.contains("/")) {
|
||||
// 分割字符串,取"/"后面的部分(如"image/jpeg" → 分割后取索引1的"jpeg")
|
||||
fileSuffix = szType.split("/")[1];
|
||||
// 调试日志:打印截取后的文件后缀
|
||||
} else {
|
||||
// 异常处理:若类型为空或格式错误,默认后缀设为jpeg(保留原逻辑兼容性)
|
||||
fileSuffix = "jpeg";
|
||||
}
|
||||
return fileSuffix;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
package cc.winboll.studio.powerbell.utils;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import androidx.core.content.FileProvider;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.powerbell.model.BackgroundBean;
|
||||
import com.yalantis.ucrop.UCrop;
|
||||
import com.yalantis.ucrop.UCropActivity;
|
||||
import java.io.File;
|
||||
import cc.winboll.studio.powerbell.R;
|
||||
|
||||
/**
|
||||
* 图片裁剪工具类(集成uCrop,脱离系统依赖)
|
||||
*/
|
||||
public class ImageCropUtils {
|
||||
public static final String TAG = "ImageCropUtils";
|
||||
// FileProvider 授权(与项目一致)
|
||||
private static final String FILE_PROVIDER_SUFFIX = ".fileprovider";
|
||||
|
||||
/**
|
||||
* 启动uCrop裁剪(核心方法,替代系统裁剪)
|
||||
* @param activity 上下文
|
||||
* @param inputFile 输入图片文件
|
||||
* @param outputFile 输出图片文件
|
||||
* @param isFreeCrop 是否自由裁剪(true=自由,false=固定比例)
|
||||
* @param requestCode 裁剪请求码
|
||||
*/
|
||||
public static void startImageCrop(Activity activity,
|
||||
File inputFile,
|
||||
File outputFile,
|
||||
int aspectX,
|
||||
int aspectY,
|
||||
boolean isFreeCrop,
|
||||
int requestCode) {
|
||||
// 校验输入参数
|
||||
if (activity == null || activity.isFinishing()) {
|
||||
LogUtils.e(TAG, "【裁剪异常】上下文Activity无效");
|
||||
return;
|
||||
}
|
||||
if (inputFile == null || !inputFile.exists() || inputFile.length() <= 100) {
|
||||
LogUtils.e(TAG, "【裁剪异常】输入文件无效");
|
||||
showToast(activity, "无有效图片可裁剪");
|
||||
return;
|
||||
}
|
||||
if (outputFile == null) {
|
||||
LogUtils.e(TAG, "【裁剪异常】输出文件路径为空");
|
||||
showToast(activity, "裁剪输出路径无效");
|
||||
return;
|
||||
}
|
||||
|
||||
// 生成输入/输出Uri(适配FileProvider)
|
||||
Uri inputUri = getFileProviderUri(activity, inputFile);
|
||||
Uri outputUri = Uri.fromFile(outputFile); // uCrop 支持直接用文件Uri(兼容低版本)
|
||||
|
||||
// 配置uCrop参数
|
||||
UCrop uCrop = UCrop.of(inputUri, outputUri);
|
||||
UCrop.Options options = new UCrop.Options();
|
||||
|
||||
// 裁剪模式配置(自由裁剪/固定比例)
|
||||
if (isFreeCrop) {
|
||||
// 自由裁剪:无固定比例,可随意调整
|
||||
uCrop.withAspectRatio(0, 0);
|
||||
options.setFreeStyleCropEnabled(true); // 开启自由裁剪
|
||||
} else {
|
||||
// 固定比例(默认1:1,可根据需求修改)
|
||||
uCrop.withAspectRatio(aspectX, aspectY);
|
||||
options.setFreeStyleCropEnabled(false);
|
||||
}
|
||||
|
||||
// 裁剪配置(优化体验)
|
||||
options.setCompressionFormat(android.graphics.Bitmap.CompressFormat.JPEG); // 输出格式
|
||||
options.setCompressionQuality(100); // 图片质量
|
||||
options.setHideBottomControls(true); // 隐藏底部控制栏(简化界面)
|
||||
options.setToolbarTitle("图片裁剪"); // 工具栏标题
|
||||
options.setToolbarColor(activity.getResources().getColor(R.color.colorPrimary)); // 工具栏颜色(适配项目主题)
|
||||
options.setStatusBarColor(activity.getResources().getColor(R.color.colorPrimaryDark)); // 状态栏颜色
|
||||
|
||||
// 应用配置并启动裁剪
|
||||
uCrop.withOptions(options);
|
||||
// 启动uCrop裁剪Activity(替代系统裁剪)
|
||||
uCrop.start(activity, requestCode);
|
||||
|
||||
LogUtils.d(TAG, "【uCrop启动】成功,输入Uri:" + inputUri + ",输出Uri:" + outputUri + ",请求码:" + requestCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重载方法:适配BackgroundBean
|
||||
*/
|
||||
public static void startImageCrop(Activity activity,
|
||||
BackgroundBean cropBean,
|
||||
int aspectX,
|
||||
int aspectY,
|
||||
boolean isFreeCrop,
|
||||
int requestCode) {
|
||||
File inputFile = new File(cropBean.getBackgroundFilePath());
|
||||
File outputFile = new File(cropBean.getBackgroundScaledCompressFilePath());
|
||||
startImageCrop(activity, inputFile, outputFile, aspectX, aspectY, isFreeCrop, requestCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成FileProvider Uri
|
||||
*/
|
||||
private static Uri getFileProviderUri(Activity activity, File file) {
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
String authority = activity.getPackageName() + FILE_PROVIDER_SUFFIX;
|
||||
Uri uri = FileProvider.getUriForFile(activity, authority, file);
|
||||
LogUtils.d(TAG, "【Uri生成】FileProvider Uri:" + uri);
|
||||
return uri;
|
||||
} else {
|
||||
Uri uri = Uri.fromFile(file);
|
||||
LogUtils.d(TAG, "【Uri生成】普通Uri:" + uri);
|
||||
return uri;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "【Uri生成】失败:" + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理uCrop裁剪回调(在Activity的onActivityResult中调用)
|
||||
* @param requestCode 请求码
|
||||
* @param resultCode 结果码
|
||||
* @param data 回调数据
|
||||
* @return 裁剪成功返回输出文件路径,失败返回null
|
||||
*/
|
||||
public static String handleCropResult(int requestCode, int resultCode, Intent data, int cropRequestCode) {
|
||||
// 校验是否是uCrop的回调
|
||||
if (requestCode == cropRequestCode) {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
// 裁剪成功,获取输出Uri
|
||||
Uri outputUri = UCrop.getOutput(data);
|
||||
if (outputUri != null) {
|
||||
String outputPath = outputUri.getPath();
|
||||
LogUtils.d(TAG, "【uCrop回调】裁剪成功,输出路径:" + outputPath);
|
||||
return outputPath;
|
||||
}
|
||||
} else if (resultCode == UCrop.RESULT_ERROR) {
|
||||
// 裁剪失败,获取异常信息
|
||||
Throwable error = UCrop.getError(data);
|
||||
LogUtils.e(TAG, "【uCrop回调】裁剪失败:" + (error != null ? error.getMessage() : "未知错误"));
|
||||
} else {
|
||||
LogUtils.d(TAG, "【uCrop回调】裁剪被取消");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示Toast
|
||||
*/
|
||||
private static void showToast(Activity activity, String msg) {
|
||||
if (activity != null && !activity.isFinishing()) {
|
||||
android.widget.Toast.makeText(activity, msg, android.widget.Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 暴露getFileProviderUri方法(供外部调用)
|
||||
*/
|
||||
public static Uri getFileProviderUriPublic(Activity activity, File file) {
|
||||
return getFileProviderUri(activity, file);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import android.widget.RemoteViews;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import cc.winboll.studio.powerbell.MainActivity;
|
||||
import cc.winboll.studio.powerbell.R;
|
||||
import cc.winboll.studio.powerbell.beans.NotificationMessage;
|
||||
import cc.winboll.studio.powerbell.model.NotificationMessage;
|
||||
import cc.winboll.studio.powerbell.services.ControlCenterService;
|
||||
|
||||
public class NotificationUtils2 {
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
package cc.winboll.studio.powerbell.utils;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import cc.winboll.studio.powerbell.R;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/12/01 16:05
|
||||
* @Describe 权限申请工具类(单例)
|
||||
* 核心特性:
|
||||
* 1. 适配全Android版本(6.0+ 动态权限 / 13+ 兼容)
|
||||
* 2. 支持多包名场景(无硬编码包名)
|
||||
* 3. 统一权限校验、申请、回调处理
|
||||
* 4. 自带用户引导(拒绝权限+不再询问场景)
|
||||
*/
|
||||
public class PermissionUtils {
|
||||
public static final String TAG = "PermissionUtils";
|
||||
// 存储权限请求码(与Activity保持一致,避免冲突)
|
||||
public static final int STORAGE_PERMISSION_REQUEST2 = 100;
|
||||
public static final int REQUEST_MANAGE_EXTERNAL_STORAGE = 101;
|
||||
|
||||
// 单例实例(双重校验锁,线程安全)
|
||||
private static volatile PermissionUtils sInstance;
|
||||
|
||||
// 私有构造(禁止外部实例化)
|
||||
private PermissionUtils() {}
|
||||
|
||||
/**
|
||||
* 获取单例实例(适配多包名,无硬编码)
|
||||
*/
|
||||
public static PermissionUtils getInstance() {
|
||||
if (sInstance == null) {
|
||||
synchronized (PermissionUtils.class) {
|
||||
if (sInstance == null) {
|
||||
sInstance = new PermissionUtils();
|
||||
}
|
||||
}
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
// ======================================== 存储权限核心方法 ========================================
|
||||
/**
|
||||
* 检查并申请存储权限(统一入口,适配全Android版本)
|
||||
* @param activity 上下文(用于权限申请和弹窗)
|
||||
* @return true:权限已全部获取;false:需要申请权限
|
||||
*/
|
||||
public boolean checkAndRequestStoragePermission(Activity activity) {
|
||||
if (activity == null || activity.isFinishing()) {
|
||||
LogUtils.e(TAG, "【权限检查】Activity为空或已销毁,权限检查失败");
|
||||
return false;
|
||||
}
|
||||
LogUtils.d(TAG, "【权限检查】开始检查存储权限,Android版本:" + Build.VERSION.SDK_INT);
|
||||
|
||||
// 统一使用 WRITE_EXTERNAL_STORAGE + READ_EXTERNAL_STORAGE(适配所有版本,避免READ_MEDIA_IMAGES找不到符号)
|
||||
String[] requiredPermissions = {
|
||||
android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
android.Manifest.permission.READ_EXTERNAL_STORAGE
|
||||
};
|
||||
|
||||
// 筛选未授予的权限
|
||||
List<String> needPermissions = new ArrayList<>();
|
||||
for (String permission : requiredPermissions) {
|
||||
if (ContextCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED) {
|
||||
needPermissions.add(permission);
|
||||
}
|
||||
}
|
||||
|
||||
// 无权限需要申请:触发动态申请
|
||||
if (!needPermissions.isEmpty()) {
|
||||
String[] permissionsArr = needPermissions.toArray(new String[0]);
|
||||
ActivityCompat.requestPermissions(activity, permissionsArr, STORAGE_PERMISSION_REQUEST2);
|
||||
LogUtils.d(TAG, "【权限申请】已触发存储权限申请:" + TextUtils.join(",", permissionsArr));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 所有权限已授予
|
||||
LogUtils.d(TAG, "【权限检查】存储权限已全部获取");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理存储权限申请回调(统一逻辑,无需在Activity中重复编写)
|
||||
* @param activity 上下文
|
||||
* @param requestCode 请求码(匹配STORAGE_PERMISSION_REQUEST)
|
||||
* @param permissions 申请的权限数组
|
||||
* @param grantResults 权限授予结果数组
|
||||
* @return true:回调已处理;false:非当前工具类的权限回调
|
||||
*/
|
||||
public boolean handleStoragePermissionResult(Activity activity, int requestCode, String[] permissions, int[] grantResults) {
|
||||
// 过滤非存储权限回调
|
||||
if (requestCode != STORAGE_PERMISSION_REQUEST2) {
|
||||
return false;
|
||||
}
|
||||
LogUtils.d(TAG, "【权限回调】处理存储权限回调,requestCode:" + requestCode);
|
||||
|
||||
if (activity == null || activity.isFinishing()) {
|
||||
LogUtils.e(TAG, "【权限回调】Activity为空或已销毁,回调处理终止");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 校验所有权限是否授予
|
||||
boolean allGranted = true;
|
||||
for (int grantResult : grantResults) {
|
||||
if (grantResult != PackageManager.PERMISSION_GRANTED) {
|
||||
allGranted = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allGranted) {
|
||||
// 全部授予:提示用户重新操作
|
||||
ToastUtils.show(activity.getString(R.string.permission_grant_success));
|
||||
LogUtils.d(TAG, "【权限回调】所有存储权限已授予");
|
||||
} else {
|
||||
// 部分/全部拒绝:判断是否勾选“不再询问”
|
||||
boolean shouldShowRationale = false;
|
||||
for (String permission : permissions) {
|
||||
if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {
|
||||
shouldShowRationale = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldShowRationale) {
|
||||
// 未勾选“不再询问”:弹窗引导重新申请
|
||||
showPermissionRationaleDialog(activity);
|
||||
} else {
|
||||
// 已勾选“不再询问”:引导用户去设置页开启
|
||||
showPermissionSettingDialog(activity);
|
||||
}
|
||||
LogUtils.d(TAG, "【权限回调】部分/全部存储权限被拒绝,是否需要引导:" + shouldShowRationale);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ======================================== 辅助方法(私有,封装细节) ========================================
|
||||
/**
|
||||
* 弹窗:未勾选“不再询问”时,提示用户授予权限
|
||||
*/
|
||||
private void showPermissionRationaleDialog(final Activity activity) {
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle(activity.getString(R.string.permission_title))
|
||||
.setMessage(activity.getString(R.string.permission_storage_rationale))
|
||||
.setPositiveButton(activity.getString(R.string.confirm), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
// 重新申请权限
|
||||
checkAndRequestStoragePermission(activity);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(activity.getString(R.string.cancel), null)
|
||||
.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹窗:已勾选“不再询问”时,引导用户去应用设置页开启权限
|
||||
*/
|
||||
private void showPermissionSettingDialog(final Activity activity) {
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle(activity.getString(R.string.permission_denied_title))
|
||||
.setMessage(activity.getString(R.string.permission_storage_setting_guide))
|
||||
.setPositiveButton(activity.getString(R.string.go_to_setting), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
// 跳转应用设置页(适配多包名,动态获取当前包名)
|
||||
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
||||
Uri uri = Uri.fromParts("package", activity.getPackageName(), null);
|
||||
intent.setData(uri);
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(activity.getString(R.string.cancel), null)
|
||||
.show();
|
||||
}
|
||||
|
||||
// ======================================== 扩展:其他权限方法(可选) ========================================
|
||||
/**
|
||||
* 检查单个权限是否已授予(通用方法,可复用)
|
||||
*/
|
||||
public boolean isPermissionGranted(Activity activity, String permission) {
|
||||
if (activity == null || TextUtils.isEmpty(permission)) {
|
||||
return false;
|
||||
}
|
||||
return ContextCompat.checkSelfPermission(activity, permission) == PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* 申请单个权限(通用方法,可复用)
|
||||
*/
|
||||
public void requestSinglePermission(Activity activity, String permission, int requestCode) {
|
||||
if (activity == null || TextUtils.isEmpty(permission)) {
|
||||
return;
|
||||
}
|
||||
if (!isPermissionGranted(activity, permission)) {
|
||||
ActivityCompat.requestPermissions(activity, new String[]{permission}, requestCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package cc.winboll.studio.powerbell.utils;
|
||||
|
||||
import cc.winboll.studio.powerbell.beans.BatteryInfoBean;
|
||||
import cc.winboll.studio.powerbell.model.BatteryInfoBean;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class StringUtils {
|
||||
|
||||
@@ -0,0 +1,254 @@
|
||||
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.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ImageView.ScaleType;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.powerbell.model.BackgroundBean;
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* 基于Java7的BackgroundView(LinearLayout+ImageView,保持原图比例居中平铺)
|
||||
* 核心:ImageView保持原图比例,在LinearLayout中居中平铺,无拉伸、无裁剪
|
||||
*/
|
||||
public class BackgroundView extends RelativeLayout {
|
||||
|
||||
public static final String TAG = "BackgroundView";
|
||||
|
||||
private Context mContext;
|
||||
private LinearLayout mLlContainer; // 主容器LinearLayout
|
||||
private ImageView mIvBackground; // 图片显示控件
|
||||
private float mImageAspectRatio = 1.0f; // 原图宽高比(宽/高)
|
||||
|
||||
// ====================================== 构造器(Java7兼容) ======================================
|
||||
public BackgroundView(Context context) {
|
||||
super(context);
|
||||
LogUtils.d(TAG, "=== BackgroundView 构造器1 启动 ===");
|
||||
this.mContext = context;
|
||||
initView();
|
||||
}
|
||||
|
||||
public BackgroundView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
LogUtils.d(TAG, "=== BackgroundView 构造器2 启动 ===");
|
||||
this.mContext = context;
|
||||
initView();
|
||||
}
|
||||
|
||||
public BackgroundView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
LogUtils.d(TAG, "=== BackgroundView 构造器3 启动 ===");
|
||||
this.mContext = context;
|
||||
initView();
|
||||
}
|
||||
|
||||
// ====================================== 初始化 ======================================
|
||||
private void initView() {
|
||||
LogUtils.d(TAG, "=== initView 启动 ===");
|
||||
// 1. 配置当前控件:全屏+透明
|
||||
setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
|
||||
setBackgroundColor(0x00000000);
|
||||
setBackground(new ColorDrawable(0x00000000));
|
||||
|
||||
// 2. 初始化主容器LinearLayout
|
||||
initLinearLayout();
|
||||
|
||||
// 3. 初始化ImageView
|
||||
initImageView();
|
||||
|
||||
// 4. 初始设置透明背景
|
||||
setDefaultTransparentBackground();
|
||||
LogUtils.d(TAG, "=== initView 完成 ===");
|
||||
}
|
||||
|
||||
private void initLinearLayout() {
|
||||
LogUtils.d(TAG, "=== initLinearLayout 启动 ===");
|
||||
mLlContainer = new LinearLayout(mContext);
|
||||
// 配置LinearLayout:全屏+垂直方向+居中
|
||||
LinearLayout.LayoutParams llParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.MATCH_PARENT
|
||||
);
|
||||
mLlContainer.setLayoutParams(llParams);
|
||||
mLlContainer.setOrientation(LinearLayout.VERTICAL);
|
||||
mLlContainer.setGravity(android.view.Gravity.CENTER); // 子View居中
|
||||
mLlContainer.setBackgroundColor(0x00000000);
|
||||
this.addView(mLlContainer);
|
||||
LogUtils.d(TAG, "=== initLinearLayout 完成 ===");
|
||||
}
|
||||
|
||||
private void initImageView() {
|
||||
LogUtils.d(TAG, "=== initImageView 启动 ===");
|
||||
mIvBackground = new ImageView(mContext);
|
||||
// 配置ImageView:wrap_content+居中+透明背景
|
||||
LinearLayout.LayoutParams ivParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
);
|
||||
mIvBackground.setLayoutParams(ivParams);
|
||||
mIvBackground.setScaleType(ScaleType.FIT_CENTER); // 保持比例+居中平铺
|
||||
mIvBackground.setBackgroundColor(0x00000000);
|
||||
mLlContainer.addView(mIvBackground);
|
||||
LogUtils.d(TAG, "=== initImageView 完成 ===");
|
||||
}
|
||||
|
||||
public void loadBackgroundBean(BackgroundBean bean) {
|
||||
if(!bean.isUseBackgroundFile()) {
|
||||
setDefaultTransparentBackground();
|
||||
return;
|
||||
}
|
||||
if(bean.isUseBackgroundScaledCompressFile()) {
|
||||
loadImage(bean.getBackgroundScaledCompressFilePath());
|
||||
} else {
|
||||
|
||||
loadImage(bean.getBackgroundFilePath());
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================== 对外方法 ======================================
|
||||
/**
|
||||
* 加载图片(保持原图比例,在LinearLayout中居中平铺)
|
||||
* @param imagePath 图片绝对路径
|
||||
*/
|
||||
public void loadImage(String imagePath) {
|
||||
LogUtils.d(TAG, "=== loadImage 启动,路径:" + imagePath + " ===");
|
||||
if (TextUtils.isEmpty(imagePath)) {
|
||||
setDefaultTransparentBackground();
|
||||
return;
|
||||
}
|
||||
|
||||
File imageFile = new File(imagePath);
|
||||
if (!imageFile.exists() || !imageFile.isFile()) {
|
||||
LogUtils.e(TAG, "图片文件无效");
|
||||
setDefaultTransparentBackground();
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算原图比例
|
||||
if (!calculateImageAspectRatio(imageFile)) {
|
||||
setDefaultTransparentBackground();
|
||||
return;
|
||||
}
|
||||
|
||||
// 压缩加载Bitmap
|
||||
Bitmap bitmap = decodeBitmapWithCompress(imageFile, 1080, 1920);
|
||||
if (bitmap == null) {
|
||||
setDefaultTransparentBackground();
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置图片
|
||||
mIvBackground.setImageDrawable(new BitmapDrawable(mContext.getResources(), bitmap));
|
||||
adjustImageViewSize(); // 调整尺寸
|
||||
LogUtils.d(TAG, "=== loadImage 完成 ===");
|
||||
}
|
||||
|
||||
// ====================================== 内部工具方法 ======================================
|
||||
private boolean calculateImageAspectRatio(File file) {
|
||||
try {
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeFile(file.getAbsolutePath(), options);
|
||||
|
||||
int width = options.outWidth;
|
||||
int height = options.outHeight;
|
||||
if (width <= 0 || height <= 0) {
|
||||
LogUtils.e(TAG, "图片尺寸无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
mImageAspectRatio = (float) width / height;
|
||||
LogUtils.d(TAG, "原图比例:" + mImageAspectRatio);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "计算比例失败:" + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调整ImageView尺寸(保持原图比例,在LinearLayout中居中平铺)
|
||||
*/
|
||||
private void adjustImageViewSize() {
|
||||
LogUtils.d(TAG, "=== adjustImageViewSize 启动 ===");
|
||||
if (mLlContainer == null || mIvBackground == null) {
|
||||
LogUtils.e(TAG, "控件为空");
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取LinearLayout尺寸
|
||||
int llWidth = mLlContainer.getWidth();
|
||||
int llHeight = mLlContainer.getHeight();
|
||||
if (llWidth == 0 || llHeight == 0) {
|
||||
postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
adjustImageViewSize();
|
||||
}
|
||||
}, 10);
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算ImageView尺寸(保持比例,不超出LinearLayout)
|
||||
int ivWidth, ivHeight;
|
||||
if (mImageAspectRatio >= 1.0f) {
|
||||
ivWidth = Math.min((int) (llHeight * mImageAspectRatio), llWidth);
|
||||
ivHeight = (int) (ivWidth / mImageAspectRatio);
|
||||
} else {
|
||||
ivHeight = Math.min((int) (llWidth / mImageAspectRatio), llHeight);
|
||||
ivWidth = (int) (ivHeight * mImageAspectRatio);
|
||||
}
|
||||
|
||||
// 应用尺寸
|
||||
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mIvBackground.getLayoutParams();
|
||||
params.width = ivWidth;
|
||||
params.height = ivHeight;
|
||||
mIvBackground.setLayoutParams(params);
|
||||
mIvBackground.setScaleType(ScaleType.FIT_CENTER); // 确保居中平铺
|
||||
|
||||
LogUtils.d(TAG, "ImageView尺寸:" + ivWidth + "x" + ivHeight);
|
||||
LogUtils.d(TAG, "=== adjustImageViewSize 完成 ===");
|
||||
}
|
||||
|
||||
private void setDefaultTransparentBackground() {
|
||||
mIvBackground.setImageBitmap(null);
|
||||
mIvBackground.setBackgroundColor(0x00000000);
|
||||
mImageAspectRatio = 1.0f;
|
||||
}
|
||||
|
||||
// ====================================== 重写方法 ======================================
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
adjustImageViewSize(); // 尺寸变化时重新调整
|
||||
}
|
||||
}
|
||||
170
powerbell/src/main/res/drawable/default_background.xml
Normal file
170
powerbell/src/main/res/drawable/default_background.xml
Normal file
@@ -0,0 +1,170 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#FF009DCB"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
||||
149
powerbell/src/main/res/layout/activity_background_settings.xml
Normal file
149
powerbell/src/main/res/layout/activity_background_settings.xml
Normal file
@@ -0,0 +1,149 @@
|
||||
<?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"/>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#FF28C000">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<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:background="#FF3243E2"
|
||||
android:id="@+id/background_view">
|
||||
|
||||
</cc.winboll.studio.powerbell.views.BackgroundView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="400dp"
|
||||
android:background="#B92FABE6">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<cc.winboll.studio.libaes.views.AButton
|
||||
android:layout_width="160dp"
|
||||
android:layout_height="36dp"
|
||||
android:text="Origin BG"
|
||||
android:id="@+id/activitybackgroundpictureAButton5"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_margin="5dp"/>
|
||||
|
||||
<cc.winboll.studio.libaes.views.AButton
|
||||
android:layout_width="160dp"
|
||||
android:layout_height="36dp"
|
||||
android:text="Received BG"
|
||||
android:id="@+id/activitybackgroundpictureAButton4"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_margin="5dp"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="right">
|
||||
|
||||
<cc.winboll.studio.libaes.views.AButton
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="36dp"
|
||||
android:text="◎"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_margin="5dp"
|
||||
android:id="@+id/activitybackgroundpictureAButton1"/>
|
||||
|
||||
<cc.winboll.studio.libaes.views.AButton
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="36dp"
|
||||
android:text="☑"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_margin="5dp"
|
||||
android:id="@+id/activitybackgroundpictureAButton2"/>
|
||||
|
||||
<cc.winboll.studio.libaes.views.AButton
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="36dp"
|
||||
android:text="♾"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_margin="5dp"
|
||||
android:id="@+id/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"
|
||||
android:text="[+]"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_margin="5dp"
|
||||
android:id="@+id/activitybackgroundpictureAButton3"/>
|
||||
|
||||
<cc.winboll.studio.libaes.views.AButton
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="36dp"
|
||||
android:text="[+~]"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_margin="5dp"
|
||||
android:id="@+id/activitybackgroundpictureAButton6"/>
|
||||
|
||||
<cc.winboll.studio.libaes.views.AButton
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="36dp"
|
||||
android:text="[◐]"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_margin="5dp"
|
||||
android:id="@+id/activitybackgroundpictureAButton7"/>
|
||||
|
||||
<cc.winboll.studio.libaes.views.AButton
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="36dp"
|
||||
android:text="[○]"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_margin="5dp"
|
||||
android:id="@+id/activitybackgroundpictureAButton8"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
<?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"/>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/activitybackgroundpictureRelativeLayout1"/>
|
||||
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/activitybackgroundpictureImageView1"
|
||||
android:layout_below="@id/toolbar">
|
||||
|
||||
</ImageView>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/toolbar">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<cc.winboll.studio.libaes.views.AButton
|
||||
android:layout_width="160dp"
|
||||
android:layout_height="36dp"
|
||||
android:text="Origin BG"
|
||||
android:id="@+id/activitybackgroundpictureAButton5"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_margin="5dp"/>
|
||||
|
||||
<cc.winboll.studio.libaes.views.AButton
|
||||
android:layout_width="160dp"
|
||||
android:layout_height="36dp"
|
||||
android:text="Received BG"
|
||||
android:id="@+id/activitybackgroundpictureAButton4"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_margin="5dp"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="right">
|
||||
|
||||
<cc.winboll.studio.libaes.views.AButton
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="36dp"
|
||||
android:text="◎"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_margin="5dp"
|
||||
android:id="@+id/activitybackgroundpictureAButton1"/>
|
||||
|
||||
<cc.winboll.studio.libaes.views.AButton
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="36dp"
|
||||
android:text="☑"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_margin="5dp"
|
||||
android:id="@+id/activitybackgroundpictureAButton2"/>
|
||||
|
||||
<cc.winboll.studio.libaes.views.AButton
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="36dp"
|
||||
android:text="[+]"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_margin="5dp"
|
||||
android:id="@+id/activitybackgroundpictureAButton3"/>
|
||||
|
||||
<cc.winboll.studio.libaes.views.AButton
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="36dp"
|
||||
android:text="[+~]"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_margin="5dp"
|
||||
android:id="@+id/activitybackgroundpictureAButton6"/>
|
||||
|
||||
<cc.winboll.studio.libaes.views.AButton
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="36dp"
|
||||
android:text="[◐]"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_margin="5dp"
|
||||
android:id="@+id/activitybackgroundpictureAButton7"/>
|
||||
|
||||
<cc.winboll.studio.libaes.views.AButton
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="36dp"
|
||||
android:text="[○]"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_margin="5dp"
|
||||
android:id="@+id/activitybackgroundpictureAButton8"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -21,20 +21,20 @@
|
||||
<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"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/activitymainFrameLayout1"/>
|
||||
|
||||
</RelativeLayout>
|
||||
<cc.winboll.studio.libaes.views.ADsBannerView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/adsbanner"
|
||||
android:layout_alignParentBottom="true"/>
|
||||
|
||||
<cc.winboll.studio.libaes.views.ADsBannerView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/adsbanner"/>
|
||||
</RelativeLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
51
powerbell/src/main/res/layout/activity_mainunittest.xml
Normal file
51
powerbell/src/main/res/layout/activity_mainunittest.xml
Normal file
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<RelativeLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#FF0C6BBF">
|
||||
|
||||
<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/backgroundview"/>
|
||||
|
||||
<HorizontalScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="#AF4FDA4E">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Main"
|
||||
android:id="@+id/btn_main_activity"/>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="TestCropImage"
|
||||
android:id="@+id/btn_test_cropimage"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
36
powerbell/src/main/res/layout/activity_settings.xml
Normal file
36
powerbell/src/main/res/layout/activity_settings.xml
Normal file
@@ -0,0 +1,36 @@
|
||||
<?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"/>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="right">
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="CheckPermission"
|
||||
android:padding="10dp"
|
||||
android:onClick="onCheckPermission"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<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"
|
||||
|
||||
14
powerbell/src/main/res/layout/view_background.xml
Normal file
14
powerbell/src/main/res/layout/view_background.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/bg_main">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/bg_imageview"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -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>
|
||||
@@ -21,4 +31,15 @@
|
||||
<string name="subtitle_activity_pixelpicker">Pixel Picker</string>
|
||||
<string name="subtitle_activity_about">About The APP</string>
|
||||
<string name="msg_AOHPCTCSeekBar_ClearRecord">>>>Seek 100% Right Is Clean Record.>>></string>
|
||||
|
||||
<!-- 权限申请相关字符串(统一管理,避免硬编码) -->
|
||||
<string name="permission_title">权限申请</string>
|
||||
<string name="permission_denied_title">权限被拒绝</string>
|
||||
<string name="permission_grant_success">权限获取成功,请重新操作</string>
|
||||
<string name="permission_storage_rationale">需要存储权限才能选择/拍照/裁剪图片,请授予权限</string>
|
||||
<string name="permission_storage_setting_guide">存储权限已被拒绝且勾选“不再询问”,请前往设置页开启权限</string>
|
||||
<string name="confirm">确定</string>
|
||||
<string name="cancel">取消</string>
|
||||
<string name="go_to_setting">去设置</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -1,28 +1,66 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths>
|
||||
<external-path
|
||||
name="external_storage_root"
|
||||
path="." />
|
||||
<files-path
|
||||
name="files_path"
|
||||
path="." />
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<!-- ====================================== 兼容适配:其他必要目录(可选)====================================== -->
|
||||
<!-- 应用内部缓存目录(适配少数依赖缓存的场景,如图片缓存)
|
||||
路径:/data/user/0/${applicationId}/cache/
|
||||
关联代码:getCacheDir() -->
|
||||
<cache-path
|
||||
name="cache_path"
|
||||
path="." />
|
||||
<!--/storage/emulated/0/Android/data/...-->
|
||||
|
||||
<!-- ====================================== 核心适配:应用私有外部目录(必选)====================================== -->
|
||||
<!-- 1. 裁剪临时文件目录(对应BackgroundSettingsActivity中onCreate的CropTemp目录)
|
||||
路径:/storage/emulated/0/Android/data/${applicationId}/files/Pictures/CropTemp/
|
||||
关联代码:_mSourceCropTempFile = new File(cropTempDir, _mSourceCropTempFileName) -->
|
||||
<external-files-path
|
||||
name="app_private_pictures"
|
||||
path="Pictures/" /> <!-- 仅保留1次,覆盖Pictures下所有子目录(含CropTemp) -->
|
||||
|
||||
<!-- 2. 背景图片目录(对应BackgroundSourceUtils的背景存储目录)
|
||||
路径:/storage/emulated/0/Android/data/${applicationId}/files/BackgroundPictureUtils/BackgroundSource/
|
||||
关联代码:mfBackgroundDir = new File(mBackgroundSourceUtils.getBackgroundSourceDirPath()) -->
|
||||
<external-files-path
|
||||
name="background_source"
|
||||
path="BackgroundPictureUtils/BackgroundSource/" />
|
||||
|
||||
<!-- 应用私有外部存储目录(适配BackgroundSourceUtils的目录) -->
|
||||
<external-files-path
|
||||
name="external_file_path"
|
||||
path="." />
|
||||
<external-files-path
|
||||
name="files_root"
|
||||
path="mimoDownload" />
|
||||
<!--代表app 外部存储区域根目录下的文件 Context.getExternalCacheDir目录下的目录-->
|
||||
path="BackgroundSourceUtils/" /> <!-- 对应fUtilsDir(BackgroundSourceUtils根目录) -->
|
||||
|
||||
<!-- 应用外部缓存目录(适配Android11+ 外部缓存场景,如第三方SDK依赖)
|
||||
路径:/storage/emulated/0/Android/data/${applicationId}/cache/
|
||||
关联代码:getExternalCacheDir() -->
|
||||
<external-cache-path
|
||||
name="external_cache_path"
|
||||
path="." />
|
||||
<!--配置root-path。这样子可以读取到sd卡和一些应用分身的目录,否则微信分身保存的图片,就会导致 java.lang.IllegalArgumentException: Failed to find configured root that contains /storage/emulated/999/tencent/MicroMsg/WeiXin/export1544062754693.jpg,在小米6的手机上微信分身有这个crash,华为没有
|
||||
-->
|
||||
<root-path
|
||||
name="root_path"
|
||||
path="" />
|
||||
|
||||
<!-- 3. 应用临时目录(对应App.getTempDirPath(),适配拍照临时文件)
|
||||
路径:/storage/emulated/0/Android/data/${applicationId}/files/temp/
|
||||
关联代码:mfPictureDir = new File(App.getTempDirPath())、mfTakePhoto = new File(mfPictureDir, "TakePhoto.jpg") -->
|
||||
<external-files-path
|
||||
name="app_temp"
|
||||
path="temp/" />
|
||||
|
||||
<!-- 通用应用外部文件目录(适配分享/下载等通用场景,覆盖files下所有目录)
|
||||
路径:/storage/emulated/0/Android/data/${applicationId}/files/
|
||||
关联代码:getExternalFilesDir(null) -->
|
||||
<external-files-path
|
||||
name="external_file_path"
|
||||
path="." />
|
||||
|
||||
<!-- 【核心添加】4. 应用内目录(getFilesDir(),对应别名 app_internal_files) -->
|
||||
<!-- 用于映射 /data/user/0/包名/files/ 路径,解决裁剪临时文件路径匹配问题 -->
|
||||
<files-path
|
||||
name="app_internal_files"
|
||||
path="." /> <!-- path="." 表示映射整个应用内目录 -->
|
||||
<!-- 关键新增:系统公共图片目录 /Pictures/PowerBell(图片存储/裁剪目录) -->
|
||||
<external-path
|
||||
name="public_pictures_powerbell"
|
||||
path="Pictures/PowerBell/" /> <!-- 路径:/storage/emulated/0/Pictures/PowerBell/ -->
|
||||
<!-- 兜底:应用内部缓存目录 -->
|
||||
<cache-path
|
||||
name="cache_path"
|
||||
path="BackgroundSourceUtils/" />
|
||||
</paths>
|
||||
|
||||
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>
|
||||
46
powerbell/src/main/res/xml/shortcutsmainen1.xml
Normal file
46
powerbell/src/main/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"
|
||||
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>
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user