Compare commits

...

57 Commits

Author SHA1 Message Date
2480c8c1f0 <powerbell>APK 15.11.5 release Publish. 2025-11-21 03:39:38 +08:00
ZhanGSKen
81950699b3 改进网络图片下载与预览 2025-11-21 03:38:42 +08:00
47ea47cddc <powerbell>APK 15.11.4 release Publish. 2025-11-21 03:21:01 +08:00
ZhanGSKen
2404a9c532 更新应用介绍页 2025-11-21 03:19:29 +08:00
ZhanGSKen
82518af2d6 完成示例图片控件的引用与存储数据存取功能。 2025-11-20 20:16:40 +08:00
ZhanGSKen
bb98d6bb1b 设置beta版与stage版不同的调试入口。 2025-11-20 11:24:24 +08:00
ZhanGSKen
230038f6f3 添加下载图片预览模块(未调试) 2025-11-19 21:24:35 +08:00
ZhanGSKen
f8980446a8 添加网络图片资源下载对话框 2025-11-19 20:25:48 +08:00
ZhanGSKen
643b84aece 添加应用背景调试模块 2025-11-19 19:21:40 +08:00
ZhanGSKen
2f1c4a5cc1 合并PowerBell项目 2025-11-19 09:31:40 +08:00
ZhanGSKen
e01fb0460f 更新电子邮箱 2025-11-19 09:14:25 +08:00
e46fccf504 <powerbell>APK 15.11.3 release Publish. 2025-11-19 09:09:24 +08:00
ZhanGSKen
e6183780b3 精简源码,更新广控件 2025-11-19 09:08:02 +08:00
74a1ef7fd3 <libaes>Library Release 15.11.4 2025-11-19 09:04:36 +08:00
1414c902d0 <aes>APK 15.11.4 release Publish. 2025-11-19 09:04:27 +08:00
ZhanGSKen
f7b30bebc0 关闭不必要的吐司提示 2025-11-19 09:03:51 +08:00
564289a114 <libaes>Library Release 15.11.3 2025-11-19 08:54:09 +08:00
a08daf142b <aes>APK 15.11.3 release Publish. 2025-11-19 08:54:02 +08:00
ZhanGSKen
bb1f3fba30 调整应用存储配置 2025-11-19 08:53:23 +08:00
66549aea4d <libaes>Library Release 15.11.2 2025-11-19 08:36:28 +08:00
7c6b215d89 <aes>APK 15.11.2 release Publish. 2025-11-19 08:36:14 +08:00
ZhanGSKen
042e25a3dc 精简源码 2025-11-19 08:34:58 +08:00
ZhanGSKen
e2410531ab 更新米盟 SDK 广告显示方案 2025-11-19 08:34:24 +08:00
b1bf37b859 <powerbell>APK 15.11.2 release Publish. 2025-11-18 17:03:32 +08:00
ZhanGSKen
93891313b3 添加AES项目的米盟模块控件。 2025-11-18 17:00:53 +08:00
ZhanGSKen
2db3f2b872 移除应用内自定义米盟模块 2025-11-18 16:50:06 +08:00
1fb91e9bfa <libaes>Library Release 15.11.1 2025-11-18 16:40:28 +08:00
7e50147d0b <aes>APK 15.11.1 release Publish. 2025-11-18 16:40:13 +08:00
ZhanGSKen
6e469b2c18 米盟广告模块已测试完成 2025-11-18 16:38:14 +08:00
ZhanGSKen
23ba2bdf0c 添加横幅广告框架 2025-11-18 15:01:38 +08:00
cb81e6e9e4 <powerbell>APK 15.11.1 release Publish. 2025-11-16 14:00:04 +08:00
ZhanGSKen
e53df4e5e9 编译参数修复 2025-11-16 13:59:19 +08:00
ZhanGSKen
25c7269029 添加隐私协议检查模块,同意隐私协议就播放广告。 2025-11-16 13:51:43 +08:00
ZhanGSKen
8b290ebc3f WinBoLL 广告ID区分beta与stage版本。 2025-11-16 13:26:24 +08:00
4af10c74e9 <powerbell>APK 15.11.0 release Publish. 2025-11-16 13:12:25 +08:00
ZhanGSKen
7883ef93be 应用窗口恢复时,检查当前时间戳的奇偶性,来决定是否播放Demo自带广告还是播放WiBoLL广告。 2025-11-16 13:07:26 +08:00
ZhanGSKen
398cb33a58 示例广告加载完成 2025-11-16 09:53:58 +08:00
ZhanGSKen
ac1858fba7 米盟广告接入类库设置完成 2025-11-16 08:40:02 +08:00
ZhanGSKen
5a77a5e9e0 添加横幅广告 2025-11-14 11:45:35 +08:00
fb7a372f29 <libaes>Library Release 15.11.0 2025-11-13 07:24:25 +08:00
c3dbae9edb <aes>APK 15.11.0 release Publish. 2025-11-13 07:24:09 +08:00
ZhanGSKen
b93434887d 更新基础类库,减少类库级联引用。修改编译适配应用目标。 2025-11-13 07:22:40 +08:00
f943db17e0 <powerbell>APK 15.4.17 release Publish. 2025-10-22 20:17:00 +08:00
ZhanGSKen
d7a9cb2a20 修复应用电量消耗计算不准确的问题。 2025-10-22 20:16:26 +08:00
de34c33706 <powerbell>APK 15.4.16 release Publish. 2025-10-22 18:58:26 +08:00
ZhanGSKen
10b8da2e21 更新应用电量报告显示风格,精简冗余代码。 2025-10-22 18:36:16 +08:00
ZhanGSKen
ca4e4c7feb 更新源码识别命名 2025-10-22 18:26:54 +08:00
4108371c20 <powerbell>APK 15.4.15 release Publish. 2025-10-22 17:45:14 +08:00
ZhanGSKen
e5c8624d9b 应用电量报告中添加应用名称搜索功能 2025-10-22 17:44:12 +08:00
561330697b <powerbell>APK 15.4.14 release Publish. 2025-10-22 16:57:22 +08:00
ZhanGSKen
f7b2c0d4c0 添加应用电量使用情况报告 2025-10-22 16:56:17 +08:00
ZhanGSKen
c3978a1e3c 20251022_144647_515 2025-10-22 14:46:51 +08:00
5e198d9c68 <powerbell>APK 15.4.13 release Publish. 2025-09-28 13:09:02 +08:00
ZhanGSKen
963a3bb7cd 编译测试 2025-09-28 13:07:02 +08:00
e9bb789daa <libappbase>Library Release 15.10.9 2025-09-27 21:03:24 +08:00
dbff19e7f4 <appbase>APK 15.10.9 release Publish. 2025-09-27 21:03:08 +08:00
ZhanGSKen
44679d0c8a GlobalApplication函数参数类型调整 2025-09-27 21:02:02 +08:00
61 changed files with 3341 additions and 374 deletions

View File

@@ -18,18 +18,22 @@ def genVersionName(def versionName){
}
android {
compileSdkVersion 32
buildToolsVersion "32.0.0"
// 1. compileSdkVersion必须 ≥ targetSdkVersion建议直接等于 targetSdkVersion30
compileSdkVersion 30
// 2. buildToolsVersion需匹配 compileSdkVersion建议使用 30.x.x 最新稳定版(无需高于 compileSdkVersion
buildToolsVersion "30.0.3" // 这是 30 对应的最新稳定版,避免使用 beta 版
defaultConfig {
applicationId "cc.winboll.studio.aes"
minSdkVersion 24
minSdkVersion 23
targetSdkVersion 30
versionCode 1
// versionName 更新后需要手动设置
// 项目模块目录的 build.gradle 文件的 stageCount=0
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
versionName "15.10"
versionName "15.11"
if(true) {
versionName = genVersionName("${versionName}")
}

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Mon Sep 29 13:04:18 HKT 2025
stageCount=3
#Wed Nov 19 09:04:33 HKT 2025
stageCount=5
libraryProject=libaes
baseVersion=15.10
publishVersion=15.10.2
baseVersion=15.11
publishVersion=15.11.4
buildCount=0
baseBetaVersion=15.10.3
baseBetaVersion=15.11.5

View File

@@ -8,8 +8,7 @@ package cc.winboll.studio.aes;
import android.view.Gravity;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.libappbase.GlobalApplication;
import com.hjq.toast.ToastUtils;
import com.hjq.toast.style.WhiteToastStyle;
import cc.winboll.studio.libappbase.ToastUtils;
public class App extends GlobalApplication {
@@ -19,15 +18,16 @@ public class App extends GlobalApplication {
@Override
public void onCreate() {
super.onCreate();
setIsDebugging(BuildConfig.DEBUG);
WinBoLLActivityManager.init(this);
// 初始化 Toast 框架
ToastUtils.init(this);
// 设置 Toast 布局样式
//ToastUtils.setView(R.layout.view_toast);
ToastUtils.setStyle(new WhiteToastStyle());
ToastUtils.setGravity(Gravity.BOTTOM, 0, 200);
}
@Override
public void onTerminate() {
super.onTerminate();
ToastUtils.release();
}
}

View File

@@ -91,8 +91,8 @@ public class MainActivity extends DrawerFragmentActivity implements IWinBoLLActi
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.toolbar_library, menu);
if(App.isDebuging()) {
getMenuInflater().inflate(cc.winboll.studio.libapputils.R.menu.toolbar_studio_debug, menu);
if(App.isDebugging()) {
getMenuInflater().inflate(cc.winboll.studio.libaes.R.menu.toolbar_studio_debug, menu);
}
return super.onCreateOptionsMenu(menu);
}

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Fri Sep 26 05:36:14 HKT 2025
stageCount=9
#Sat Sep 27 21:03:20 HKT 2025
stageCount=10
libraryProject=libappbase
baseVersion=15.10
publishVersion=15.10.8
publishVersion=15.10.9
buildCount=0
baseBetaVersion=15.10.9
baseBetaVersion=15.10.10

View File

@@ -5,6 +5,16 @@ buildscript {
// 设置本地Maven仓库路径
url 'file:///sdcard/.m2/repository/'
}
//米盟通过maven接入时要做如下配置
maven {
url "https://repos.xiaomi.com/maven"
credentials {
username 'mimo-developer'
password 'AKCp8ih1PFG9tV8qaLyws67dLGZi8udFM39SfsHgihN15cgsiRvHuxj8JzFmuZjaViVeNawaA'
}
}
// Nexus Maven 库地址
// "WinBoLL Release"
maven { url "https://nexus.winboll.cc/repository/maven-public/" }
@@ -40,6 +50,15 @@ allprojects {
// 设置本地Maven仓库路径
url 'file:///sdcard/.m2/repository/'
}
//米盟通过maven接入时要做如下配置
maven {
url "https://repos.xiaomi.com/maven"
credentials {
username 'mimo-developer'
password 'AKCp8ih1PFG9tV8qaLyws67dLGZi8udFM39SfsHgihN15cgsiRvHuxj8JzFmuZjaViVeNawaA'
}
}
// Nexus Maven 库地址
// "WinBoLL Release"
@@ -117,4 +136,3 @@ task clean(type: Delete) {
delete rootProject.buildDir
}

View File

@@ -4,11 +4,15 @@ apply from: '../.winboll/winboll_lib_build.gradle'
apply from: '../.winboll/winboll_lint_build.gradle'
android {
compileSdkVersion 32
buildToolsVersion "32.0.0"
// 1. compileSdkVersion必须 ≥ targetSdkVersion建议直接等于 targetSdkVersion30
compileSdkVersion 30
// 2. buildToolsVersion需匹配 compileSdkVersion建议使用 30.x.x 最新稳定版(无需高于 compileSdkVersion
buildToolsVersion "30.0.3" // 这是 30 对应的最新稳定版,避免使用 beta 版
defaultConfig {
minSdkVersion 24
minSdkVersion 23
targetSdkVersion 30
}
buildTypes {
@@ -20,13 +24,6 @@ android {
}
dependencies {
api fileTree(dir: 'libs', include: ['*.jar'])
api 'cc.winboll.studio:libapputils:15.10.2'
api 'cc.winboll.studio:libappbase:15.10.9'
// 吐司类库
api 'com.github.getActivity:ToastUtils:10.5'
// 权限请求框架https://github.com/getActivity/XXPermissions
api 'com.github.getActivity:XXPermissions:18.63'
// 下拉控件
@@ -52,4 +49,17 @@ dependencies {
//api 'androidx.vectordrawable:vectordrawable:1.1.0'
//api 'androidx.vectordrawable:vectordrawable-animated:1.1.0'
//api 'androidx.fragment:fragment:1.1.0'
// 米盟
implementation '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'
//annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
api 'cc.winboll.studio:libappbase:15.11.0'
api fileTree(dir: 'libs', include: ['*.jar'])
}

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Mon Sep 29 13:04:07 HKT 2025
stageCount=3
#Wed Nov 19 09:04:27 HKT 2025
stageCount=5
libraryProject=libaes
baseVersion=15.10
publishVersion=15.10.2
baseVersion=15.11
publishVersion=15.11.4
buildCount=0
baseBetaVersion=15.10.3
baseBetaVersion=15.11.5

View File

@@ -1,9 +1,25 @@
<?xml version='1.0' encoding='utf-8'?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="cc.winboll.studio.libaes">
<application>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<!-- 通过GPS得到精确位置 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- 通过网络得到粗略位置 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<application
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="true"
tools:ignore="GoogleAppIndexingWarning">
<activity
android:name="cc.winboll.studio.libaes.unittests.SecondaryLibraryActivity"
@@ -33,6 +49,18 @@
android:name="cc.winboll.studio.libaes.winboll.WinBoLLMail"
android:exported="true"/>
<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>
</application>
</manifest>
</manifest>

View File

@@ -31,6 +31,9 @@ import cc.winboll.studio.libappbase.LogUtils;
import com.baoyz.widget.PullRefreshLayout;
import java.util.ArrayList;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.libaes.views.ADsBannerView;
import cc.winboll.studio.libappbase.LogActivity;
import cc.winboll.studio.libappbase.ToastUtils;
public abstract class DrawerFragmentActivity extends AppCompatActivity implements AdapterView.OnItemClickListener {
@@ -71,17 +74,22 @@ public abstract class DrawerFragmentActivity extends AppCompatActivity implement
@Override
protected void onDestroy() {
super.onDestroy();
// 修复:释放广告资源,避免内存泄漏
ADsBannerView adsBannerView = findViewById(R.id.adsbanner);
if (adsBannerView != null) {
adsBannerView.releaseAdResources();
}
}
/*@Override
public Intent getIntent() {
// TODO: Implement this method
return super.getIntent();
}
public Intent getIntent() {
// TODO: Implement this method
return super.getIntent();
}
public Context getContext() {
return this.mContext;
}*/
public Context getContext() {
return this.mContext;
}*/
@Override
public MenuInflater getMenuInflater() {
@@ -90,9 +98,9 @@ public abstract class DrawerFragmentActivity extends AppCompatActivity implement
}
/*public void setSubtitle(CharSequence context) {
// TODO: Implement this method
getSupportActionBar().setSubtitle(context);
}*/
// TODO: Implement this method
getSupportActionBar().setSubtitle(context);
}*/
@Override
public void recreate() {
@@ -100,9 +108,9 @@ public abstract class DrawerFragmentActivity extends AppCompatActivity implement
}
/*@Override
public boolean moveTaskToBack(boolean nonRoot) {
return super.moveTaskToBack(nonRoot);
}*/
public boolean moveTaskToBack(boolean nonRoot) {
return super.moveTaskToBack(nonRoot);
}*/
@Override
public void startActivity(Intent intent) {
@@ -115,24 +123,24 @@ public abstract class DrawerFragmentActivity extends AppCompatActivity implement
}
/*@Override
public FragmentManager getSupportFragmentManager() {
return super.getSupportFragmentManager();
}
public FragmentManager getSupportFragmentManager() {
return super.getSupportFragmentManager();
}
public void setSubtitle(int resId) {
// TODO: Implement this method
getSupportActionBar().setSubtitle(resId);
}
public void setSubtitle(int resId) {
// TODO: Implement this method
getSupportActionBar().setSubtitle(resId);
}
public void setTitle(CharSequence context) {
// TODO: Implement this method
getSupportActionBar().setTitle(context);
}
public void setTitle(CharSequence context) {
// TODO: Implement this method
getSupportActionBar().setTitle(context);
}
public void setTitle(int resId) {
// TODO: Implement this method
getSupportActionBar().setTitle(resId);
}*/
public void setTitle(int resId) {
// TODO: Implement this method
getSupportActionBar().setTitle(resId);
}*/
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
@@ -175,6 +183,9 @@ public abstract class DrawerFragmentActivity extends AppCompatActivity implement
for (int i = Integer.MIN_VALUE; i < Integer.MAX_VALUE; i++) {
getString(i);
}
} else if (R.id.item_log == item.getItemId()) {
ToastUtils.show("Test");
LogActivity.startLogActivity(this);
} else if (R.id.item_about == item.getItemId()) {
LogUtils.d(TAG, "onAbout");
} else if (android.R.id.home == item.getItemId()) {
@@ -189,6 +200,11 @@ public abstract class DrawerFragmentActivity extends AppCompatActivity implement
if (checkThemeStyleChange()) {
recreate();
}
ADsBannerView adsBannerView = findViewById(R.id.adsbanner);
if (adsBannerView != null) {
adsBannerView.resumeADs();
}
}
void initRootView() {

View File

@@ -13,7 +13,7 @@ import androidx.fragment.app.Fragment;
import cc.winboll.studio.libaes.R;
import cc.winboll.studio.libaes.views.AButton;
import cc.winboll.studio.libappbase.LogUtils;
import com.hjq.toast.ToastUtils;
import cc.winboll.studio.libappbase.ToastUtils;
public class TestAButtonFragment extends Fragment {

View File

@@ -12,14 +12,13 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Toast;
import androidx.fragment.app.Fragment;
import androidx.viewpager.widget.ViewPager;
import cc.winboll.studio.libaes.ImagePagerAdapter;
import cc.winboll.studio.libaes.R;
import cc.winboll.studio.libaes.views.AOHPCTCSeekBar;
import cc.winboll.studio.libappbase.LogView;
import com.hjq.toast.ToastUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import java.util.ArrayList;
import java.util.List;

View File

@@ -0,0 +1,33 @@
package cc.winboll.studio.libaes.utils;
import android.content.Context;
import android.util.DisplayMetrics;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/11/18 15:23
* @Describe 米盟 MimoUtils
*/
public final class MimoUtils {
public static final String TAG = "Utils";
public static int dpToPx(Context context, float dp) {
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
return (int) (dp * displayMetrics.density + 0.5f);
}
public static int pxToDp(Context context, float px) {
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
return (int) (px / displayMetrics.density + 0.5f);
}
public static int pxToSp(Context context, float pxValue) {
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
return (int) (pxValue / displayMetrics.scaledDensity + 0.5f);
}
public static int spToPx(Context context, float spValue) {
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
return (int) (spValue * displayMetrics.scaledDensity + 0.5f);
}
}

View File

@@ -0,0 +1,33 @@
package cc.winboll.studio.libaes.utils;
import android.content.Context;
import android.content.SharedPreferences;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/11/13 06:50
* @Describe 应用变量保存工具
*/
public class PrefUtils {
public static final String TAG = "PrefUtils";
//
// 保存字符串到SharedPreferences的函数
//
public static void saveString(Context context, String key, String value) {
SharedPreferences sharedPreferences = context.getSharedPreferences("myPrefs", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(key, value);
editor.apply();
}
//
// 从SharedPreferences读取字符串的函数
//
public static String getString(Context context, String key, String defaultValue) {
SharedPreferences sharedPreferences = context.getSharedPreferences("myPrefs", Context.MODE_PRIVATE);
return sharedPreferences.getString(key, defaultValue);
}
}

View File

@@ -0,0 +1,485 @@
package cc.winboll.studio.libaes.views;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.Display;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import cc.winboll.studio.libaes.R;
import cc.winboll.studio.libaes.utils.MimoUtils;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import com.miui.zeus.mimo.sdk.ADParams;
import com.miui.zeus.mimo.sdk.BannerAd;
import com.miui.zeus.mimo.sdk.MimoCustomController;
import com.miui.zeus.mimo.sdk.MimoLocation;
import com.miui.zeus.mimo.sdk.MimoSdk;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/11/18 14:41
* @Describe WinBoLL 横幅广告类
*/
public class ADsBannerView extends LinearLayout {
public static final String TAG = "ADsBannerView";
private static final String PRIVACY_FILE = "privacy_pfs";
private static final String PRIVACY_VALUE = "privacy_value";//0: 拒绝1赞同
private String BANNER_POS_ID = "802e356f1726f9ff39c69308bfd6f06a";
private String BANNER_POS_ID_WINBOLL_BETA = "d129ee5a263911f981a6dc7a9802e3e7";
private String BANNER_POS_ID_WINBOLL = "4ec30efdb32271765b9a4efac902828b";
/*
private String BANNER_POS_ID = "802e356f1726f9ff39c69308bfd6f06a";
private String BANNER_POS_ID_WINBOLL_BETA = "802e356f1726f9ff39c69308bfd6f06a";
private String BANNER_POS_ID_WINBOLL = "802e356f1726f9ff39c69308bfd6f06a";
*/
Context mContext;
View mMianView;
SharedPreferences mSharedPreferences;
ViewGroup mContainer;
BannerAd mBannerAd;
List<BannerAd> mAllBanners = new ArrayList<>();
// 新增主线程Handler确保广告操作在主线程执行
private Handler mMainHandler;
public ADsBannerView(Context context) {
super(context);
this.mContext = context;
initView();
}
public ADsBannerView(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
initView();
}
public ADsBannerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mContext = context;
initView();
}
public ADsBannerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
this.mContext = context;
initView();
}
void initView() {
// 初始化主线程Handler关键确保广告操作在主线程执行
mMainHandler = new Handler(Looper.getMainLooper());
// 米盟模块:隐私协议弹窗
showPrivacy();
this.mMianView = inflate(this.mContext, R.layout.view_adsbanner, null);
mContainer = this.mMianView.findViewById(R.id.ads_container);
addView(this.mMianView);
}
Activity getActivity() {
try {
Activity activity = (Activity)this.mContext;
return activity;
} catch (Exception ex) {
}
return null;
}
public void resumeADs() {
// 修复:优化广告请求逻辑(添加生命周期判断 + 主线程执行)
if (getActivity() != null && !getActivity().isFinishing() && !getActivity().isDestroyed()) {
String privacyAgreeValue = getSharedPreferences().getString(PRIVACY_VALUE, null);
if (TextUtils.equals(privacyAgreeValue, String.valueOf(1))) {
LogUtils.i(TAG, "已同意隐私协议,开始播放米盟广告...");
mMainHandler.postDelayed(new Runnable() {
@Override
public void run() {
//ToastUtils.show("ADs run");
// 再次校验生命周期避免延迟执行时Activity已销毁
if (getActivity() != null && !getActivity().isFinishing() && !getActivity().isDestroyed()) {
fetchAd();
}
}
}, 1000); // 延迟1秒请求广告提升页面加载体验
}
}
}
/**
* 释放广告资源关键避免内存泄漏和空Context调用
*/
public void releaseAdResources() {
LogUtils.d(TAG, "releaseAdResources()");
// 移除Handler回调
if (mMainHandler != null) {
mMainHandler.removeCallbacksAndMessages(null);
}
// 销毁所有广告实例
if (mAllBanners != null && !mAllBanners.isEmpty()) {
for (BannerAd ad : mAllBanners) {
if (ad != null) {
ad.destroy();
}
}
mAllBanners.clear();
}
// 置空当前广告引用
mBannerAd = null;
// 移除广告容器中的视图
if (mContainer != null) {
mContainer.removeAllViews();
}
}
/**
* 显示广告核心修复传递安全的Context + 生命周期校验)
*/
private void showAd() {
LogUtils.d(TAG, "showAd()");
// 1. 生命周期校验避免Activity已销毁时操作UI
if (getActivity() == null || getActivity().isFinishing() || getActivity().isDestroyed()) {
LogUtils.e(TAG, "showAd: Activity is finishing or destroyed");
return;
}
// 2. 非空校验:广告实例和容器
if (mBannerAd == null || mContainer == null) {
LogUtils.e(TAG, "showAd: BannerAd or Container is null");
return;
}
// 3. 创建广告容器使用ApplicationContext避免内存泄漏
final FrameLayout container = new FrameLayout(getActivity().getApplicationContext());
container.setPadding(0, 0, 0, MimoUtils.dpToPx(getActivity(), 10));
mContainer.addView(container, new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT
));
// if (mIsBiddingWin) {
// mBannerAd.setPrice(getPrice());
// }
// 4. 显示广告传递ApplicationContext避免Activity Context失效
mBannerAd.showAd(getActivity(), container, new BannerAd.BannerInteractionListener() {
@Override
public void onAdClick() {
LogUtils.d(TAG, "onAdClick");
}
@Override
public void onAdShow() {
LogUtils.d(TAG, "onAdShow");
}
@Override
public void onAdDismiss() {
LogUtils.d(TAG, "onAdDismiss");
// 修复移除容器时校验Activity状态
if (getActivity() != null && !getActivity().isFinishing() && !getActivity().isDestroyed() && mContainer != null) {
mContainer.removeView(container);
}
}
@Override
public void onRenderSuccess() {
LogUtils.d(TAG, "onRenderSuccess");
}
@Override
public void onRenderFail(int code, String msg) {
LogUtils.e(TAG, "onRenderFail errorCode " + code + " errorMsg " + msg);
// 修复:渲染失败时移除容器
if (getActivity() != null && !getActivity().isFinishing() && !getActivity().isDestroyed() && mContainer != null) {
mContainer.removeView(container);
}
}
});
}
/**
* 请求广告核心修复Context安全校验 + 异常捕获 + 资源管理)
*/
private void fetchAd() {
LogUtils.d(TAG, "fetchAd()");
// 1. 双重校验Activity未销毁 + Context非空
if (getActivity() == null || getActivity().isFinishing() || getActivity().isDestroyed() || getActivity().getApplicationContext() == null) {
LogUtils.e(TAG, "fetchAd: Invalid Context or Activity state");
return;
}
// 2. 释放之前的广告资源,避免内存泄漏
if (mBannerAd != null) {
mBannerAd.destroy();
}
// 3. 初始化广告使用ApplicationContext避免Activity Context失效
try {
mBannerAd = new BannerAd();
mAllBanners.add(mBannerAd);
} catch (Exception e) {
LogUtils.e(TAG, "fetchAd: Init BannerAd failed", e);
return;
}
// 4. 设置下载监听
mBannerAd.setDownLoadListener(new BannerAd.BannerDownloadListener() {
@Override
public void onDownloadStarted() {
LogUtils.d(TAG, "onDownloadStarted");
}
@Override
public void onDownloadPaused() {
LogUtils.d(TAG, "onDownloadPaused");
}
@Override
public void onDownloadFailed(int errorCode) {
String msg = "onDownloadFailed, errorCode = " + errorCode;
LogUtils.d(TAG, msg);
//ToastUtils.show(msg);
}
@Override
public void onDownloadFinished() {
LogUtils.d(TAG, "onDownloadFinished");
}
@Override
public void onDownloadProgressUpdated(int progress) {
LogUtils.d(TAG, "onDownloadProgressUpdated " + progress + "%");
}
@Override
public void onInstallFailed(int errorCode) {
LogUtils.d(TAG, "onInstallFailed, errorCode = " + errorCode);
}
@Override
public void onInstallStart() {
LogUtils.d(TAG, "onInstallStart");
}
@Override
public void onInstallSuccess() {
LogUtils.d(TAG, "onInstallSuccess");
}
@Override
public void onDownloadCancel() {
LogUtils.d(TAG, "onDownloadCancel");
}
});
// 5. 构建广告参数并请求
String currentAD_ID = getAD_ID();
LogUtils.d(TAG, String.format("currentAD_ID = %s", currentAD_ID));
ADParams params = new ADParams.Builder().setUpId(currentAD_ID).build();
mBannerAd.loadAd(params, new BannerAd.BannerLoadListener() {
@Override
public void onBannerAdLoadSuccess() {
LogUtils.d(TAG, "onBannerAdLoadSuccess()");
// 修复广告加载成功后校验Activity状态
if (getActivity() != null && !getActivity().isFinishing() && !getActivity().isDestroyed()) {
showAd();
}
}
@Override
public void onAdLoadFailed(int errorCode, String errorMsg) {
String msg = "onAdLoadFailed: errorCode = " + errorCode + ", errorMsg = " + errorMsg;
LogUtils.d(TAG, msg);
removeAllBanners();
}
});
}
void removeAllBanners() {
// 修复:加载失败时移除当前广告实例
if (mAllBanners.contains(mBannerAd)) {
mAllBanners.remove(mBannerAd);
}
mBannerAd.destroy();
mBannerAd = null;
}
/**
* 根据当前秒数获取广告ID原逻辑保留
*/
private String getAD_ID() {
long currentSecond = System.currentTimeMillis() / 1000;
return (currentSecond % 2 == 0) ? BANNER_POS_ID :
(GlobalApplication.isDebugging() ? BANNER_POS_ID_WINBOLL_BETA : BANNER_POS_ID_WINBOLL);
}
/**
* 获取广告价格(原逻辑保留,添加空指针校验)
*/
private long getPrice() {
if (mBannerAd == null) {
return 0;
}
Map<String, Object> map = mBannerAd.getMediaExtraInfo();
if (map == null || map.isEmpty() || !map.containsKey("price")) {
LogUtils.w(TAG, "getPrice: media extra info is null or no price key");
return 0;
}
Object priceObj = map.get("price");
if (priceObj instanceof Long) {
return (Long) priceObj;
} else if (priceObj instanceof Integer) {
return ((Integer) priceObj).longValue();
} else {
LogUtils.e(TAG, "getPrice: price type is invalid");
return 0;
}
}
/**
* 显示隐私协议弹窗原逻辑保留优化Context使用
*/
private void showPrivacy() {
// 校验Activity状态避免弹窗泄露
if (getActivity() == null || getActivity().isFinishing() || getActivity().isDestroyed()) {
return;
}
String privacyAgreeValue = getSharedPreferences().getString(PRIVACY_VALUE, null);
if (TextUtils.equals(privacyAgreeValue, String.valueOf(0))) {
LogUtils.i(TAG, "已拒绝隐私协议,广告已处于不可用状态...");
Toast.makeText(getActivity().getApplicationContext(), "已拒绝隐私协议,广告已处于不可用状态", Toast.LENGTH_SHORT).show();
return;
}
if (TextUtils.equals(privacyAgreeValue, String.valueOf(1))) {
LogUtils.i(TAG, "已同意隐私协议开始初始化米盟SDK...");
initMimoSdk();
return;
}
LogUtils.i(TAG, "开始弹出隐私协议...");
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle("用户须知");
builder.setMessage("小米广告SDK隐私政策: https://dev.mi.com/distribute/doc/details?pId=1688, 请复制到浏览器查看");
builder.setIcon(R.drawable.ic_launcher);
builder.setCancelable(false); // 点击对话框以外的区域不消失
builder.setPositiveButton("同意", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
getSharedPreferences().edit()
.putString(PRIVACY_VALUE, String.valueOf(1))
.apply();
initMimoSdk();
dialog.dismiss();
}
});
builder.setNegativeButton("拒绝", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
getSharedPreferences().edit()
.putString(PRIVACY_VALUE, String.valueOf(0))
.apply();
dialog.dismiss();
}
});
AlertDialog dialog = builder.create();
// 配置弹窗位置(底部全屏)
Window window = dialog.getWindow();
if (window != null) {
window.setGravity(Gravity.BOTTOM);
WindowManager m = getActivity().getWindowManager();
Display d = m.getDefaultDisplay();
WindowManager.LayoutParams p = window.getAttributes();
p.width = d.getWidth();
window.setAttributes(p);
}
dialog.show();
}
/**
* 初始化米盟SDK核心修复传递ApplicationContext + 异常捕获)
*/
private void initMimoSdk() {
// 1. 安全获取ApplicationContext避免Activity Context失效
Context appContext = getActivity().getApplicationContext();
if (appContext == null) {
LogUtils.e(TAG, "initMimoSdk: ApplicationContext is null");
return;
}
// 2. 初始化SDK捕获异常避免崩溃
try {
MimoSdk.init(appContext, new MimoCustomController() {
@Override
public boolean isCanUseLocation() {
return true;
}
@Override
public MimoLocation getMimoLocation() {
return null;
}
@Override
public boolean isCanUseWifiState() {
return true;
}
@Override
public boolean alist() {
return true;
}
}, new MimoSdk.InitCallback() {
@Override
public void success() {
LogUtils.d(TAG, "MimoSdk init success");
}
@Override
public void fail(int code, String msg) {
LogUtils.e(TAG, "MimoSdk init fail, code=" + code + ",msg=" + msg);
}
});
MimoSdk.setDebugOn(true);
} catch (Exception e) {
LogUtils.e(TAG, "initMimoSdk: init failed", e);
}
}
/**
* 获取SharedPreferences实例原逻辑保留添加空指针校验
*/
SharedPreferences getSharedPreferences() {
if (mSharedPreferences == null) {
// 修复使用ApplicationContext获取SharedPreferences避免Activity Context泄露
Context appContext = getActivity().getApplicationContext();
if (appContext != null) {
mSharedPreferences = appContext.getSharedPreferences(PRIVACY_FILE, Context.MODE_PRIVATE);
} else {
LogUtils.e(TAG, "getSharedPreferences: ApplicationContext is null");
// 降级方案若ApplicationContext为空使用Activity Context仅作兼容
mSharedPreferences = getActivity().getSharedPreferences(PRIVACY_FILE, Context.MODE_PRIVATE);
}
}
return mSharedPreferences;
}
}

View File

@@ -12,7 +12,6 @@ import android.content.res.TypedArray;
import android.net.Uri;
import android.os.Message;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import android.widget.LinearLayout;
@@ -20,11 +19,11 @@ import cc.winboll.studio.libaes.R;
import cc.winboll.studio.libaes.dialogs.YesNoAlertDialog;
import cc.winboll.studio.libaes.models.APPInfo;
import cc.winboll.studio.libaes.utils.AppVersionUtils;
import cc.winboll.studio.libaes.utils.PrefUtils;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libapputils.utils.PrefUtils;
import com.hjq.toast.ToastUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import java.io.IOException;
import mehdi.sakout.aboutpage.AboutPage;
import mehdi.sakout.aboutpage.Element;
@@ -108,7 +107,7 @@ public class AboutView extends LinearLayout {
mszAppDescription = mAPPInfo.getAppDescription();
mnAppIcon = mAPPInfo.getAppIcon();
mszWinBoLLServerHost = GlobalApplication.isDebuging() ? "https://yun-preivew.winboll.cc": "https://yun.winboll.cc";
mszWinBoLLServerHost = GlobalApplication.isDebugging() ? "https://yun-preivew.winboll.cc": "https://yun.winboll.cc";
try {
mszAppVersionName = _mContext.getPackageManager().getPackageInfo(_mContext.getPackageName(), 0).versionName;
@@ -229,7 +228,7 @@ public class AboutView extends LinearLayout {
// 定义应用调试按钮
//
Element elementAppMode;
if (GlobalApplication.isDebuging()) {
if (GlobalApplication.isDebugging()) {
elementAppMode = new Element(_mContext.getString(R.string.app_normal), R.drawable.ic_winboll);
elementAppMode.setOnClickListener(mAppNormalOnClickListener);
} else {
@@ -263,8 +262,8 @@ public class AboutView extends LinearLayout {
if (intent != null) {
//intent.setAction(cc.winboll.studio.libapputils.intent.action.DEBUGVIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
GlobalApplication.setIsDebuging(true);
GlobalApplication.saveDebugStatus(_mContext);
GlobalApplication.setIsDebugging(true);
GlobalApplication.saveDebugStatus((GlobalApplication)_mContext.getApplicationContext());
WinBoLLActivityManager.getInstance().finishAll();
context.startActivity(intent);
@@ -275,8 +274,8 @@ public class AboutView extends LinearLayout {
Intent intent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
if (intent != null) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
GlobalApplication.setIsDebuging(false);
GlobalApplication.saveDebugStatus(_mContext);
GlobalApplication.setIsDebugging(false);
GlobalApplication.saveDebugStatus((GlobalApplication)_mContext.getApplicationContext());
WinBoLLActivityManager.getInstance().finishAll();
context.startActivity(intent);
@@ -301,7 +300,7 @@ public class AboutView extends LinearLayout {
String szUrl = mszWinBoLLServerHost + "/studio/details.php?app=" + mszAppAPKFolderName;
// 构建包含认证信息的请求
String credential = "";
if (GlobalApplication.isDebuging()) {
if (GlobalApplication.isDebugging()) {
credential = Credentials.basic(metDevUserName.getText().toString(), metDevUserPassword.getText().toString());
PrefUtils.saveString(_mContext, "metDevUserName", metDevUserName.getText().toString());
PrefUtils.saveString(_mContext, "metDevUserPassword", metDevUserPassword.getText().toString());

View File

@@ -10,7 +10,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/activitydrawerfragmentASupportToolbar1"/>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
@@ -19,7 +19,8 @@
<androidx.drawerlayout.widget.DrawerLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0"
android:id="@+id/activitydrawerfragmentDrawerLayout1">
<FrameLayout
@@ -52,7 +53,13 @@
</androidx.drawerlayout.widget.DrawerLayout>
</LinearLayout>
<cc.winboll.studio.libaes.views.ADsBannerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/adsbanner"/>
</LinearLayout>

View File

@@ -0,0 +1,15 @@
<?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:id="@+id/ads_container"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/ads_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"/>
</LinearLayout>

View File

@@ -0,0 +1,35 @@
<?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:title="@string/app_theme">
<menu>-->
<!-- 定义一组单选按钮 -->
<!-- checkableBehavior的可选值由三个single设置为单选all为多选none为普通选项 -->
<!-- <group android:checkableBehavior="single">
<item android:id="@+id/app_defaulttheme" android:title="@string/app_defaulttheme"/>
<item android:id="@+id/app_skytheme" android:title="@string/app_skytheme"/>
<item android:id="@+id/app_goldentheme" android:title="@string/app_goldentheme"/>
</group>
</menu>
</item>-->
<item android:title="DebugTools">
<menu>
<item
android:id="@+id/item_testcrashreport"
android:title="Test Application Crash Report"/>
<item
android:id="@+id/item_unittest"
android:title="UnitTest"/>
<item
android:id="@+id/item_log"
android:title="APPLOG"/>
<item
android:id="@+id/item_info"
android:title="Info"/>
<item
android:id="@+id/item_exitdebug"
android:title="ExitDebug"/>
</menu>
</item>
</menu>

View File

@@ -12,5 +12,10 @@
<string name="text_GoldenTheme">GoldenTheme</string>
<string name="text_MemorTheme">MemorTheme</string>
<string name="text_TaoTheme">TaoTheme</string>
<string name="app_normal">Click here is switch to Normal APP</string>
<string name="app_debug">Click here is switch to APP DEBUG</string>
<string name="gitea_home">GITEA HOME</string>
<string name="app_update">APP UPDATE</string>
</resources>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path path="mimoDownload" name="files_root" />
<external-path path="." name="external_storage_root" />
</paths>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<!-- 允许 winboll.cc 及其子域名的明文流量HTTP -->
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">winboll.cc</domain>
</domain-config>
<!-- 米盟 SDK 配置 -->
<base-config cleartextTrafficPermitted="true" />
<debug-overrides>
<trust-anchors>
<!-- Trust user added CAs while debuggable only -->
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</debug-overrides>
</network-security-config>

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Fri Sep 26 05:36:14 HKT 2025
stageCount=9
#Sat Sep 27 21:03:08 HKT 2025
stageCount=10
libraryProject=libappbase
baseVersion=15.10
publishVersion=15.10.8
publishVersion=15.10.9
buildCount=0
baseBetaVersion=15.10.9
baseBetaVersion=15.10.10

View File

@@ -22,12 +22,12 @@ public class GlobalApplication extends Application {
GlobalApplication.isDebuging = isDebuging;
}
public static void saveDebugStatus(GlobalApplication application) {
APPModel.saveBeanToFile(application.getAPPModelFilePath(application), new APPModel(GlobalApplication.isDebuging));
public static void saveDebugStatus(Context context) {
APPModel.saveBeanToFile(getAPPModelFilePath(context), new APPModel(GlobalApplication.isDebuging));
}
static String getAPPModelFilePath(GlobalApplication application) {
return application.getDataDir().getPath() + "/APPModel.json";
static String getAPPModelFilePath(Context context) {
return context.getDataDir().getPath() + "/APPModel.json";
}
public static boolean isDebuging() {

View File

@@ -24,7 +24,7 @@
1. Fork 本仓库
2. 新建 Feat_xxx 分支
3. 提交代码 : ZhanGSKen(ZhanGSKen<zhangsken@188.com>)
3. 提交代码 : ZhanGSKen(ZhanGSKen<zhangsken@qq.com>)
4. 新建 Pull Request

View File

@@ -18,18 +18,22 @@ def genVersionName(def versionName){
}
android {
compileSdkVersion 32
buildToolsVersion "32.0.0"
// 1. compileSdkVersion必须 ≥ targetSdkVersion建议直接等于 targetSdkVersion30
compileSdkVersion 30
// 2. buildToolsVersion需匹配 compileSdkVersion建议使用 30.x.x 最新稳定版(无需高于 compileSdkVersion
buildToolsVersion "30.0.3" // 这是 30 对应的最新稳定版,避免使用 beta 版
defaultConfig {
applicationId "cc.winboll.studio.powerbell"
minSdkVersion 24
minSdkVersion 23
targetSdkVersion 30
versionCode 6
// versionName 更新后需要手动设置
// .winboll/winbollBuildProps.properties 文件的 stageCount=0
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
versionName "15.4"
versionName "15.11"
if(true) {
versionName = genVersionName("${versionName}")
}
@@ -41,17 +45,25 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
// 米盟
packagingOptions {
doNotStrip "*/*/libmimo_1011.so"
}
}
dependencies {
api fileTree(dir: 'libs', include: ['*.jar'])
api 'cc.winboll.studio:libaes:15.9.3'
api 'cc.winboll.studio:libapputils:15.8.5'
api 'cc.winboll.studio:libappbase:15.9.5'
// 吐司提示库
api 'com.github.getActivity:ToastUtils:10.5'
// 应用介绍页类库
// 米盟
implementation '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'
//annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
// 应用介绍页类库
api 'io.github.medyo:android-about-page:2.0.0'
// SSH
api 'com.jcraft:jsch:0.1.55'
@@ -71,23 +83,9 @@ dependencies {
//api 'androidx.vectordrawable:vectordrawable-animated:1.1.0'
//api 'androidx.fragment:fragment:1.1.0'
/*api 'cc.winboll.studio:winboll-shared:1.8.0'
api 'io.github.medyo:android-about-page:2.0.0'
api 'com.jcraft:jsch:0.1.55'
api 'org.jsoup:jsoup:1.13.1'
api 'com.squareup.okhttp3:okhttp:4.4.1'
api 'androidx.appcompat:appcompat:1.0.0'
api 'androidx.fragment:fragment:1.0.0'
api 'com.google.android.material:material:1.0.0'
api 'com.baoyz.pullrefreshlayout:library:1.2.0'
api 'com.github.getActivity:ToastUtils:10.5'
api 'io.github.medyo:android-about-page:2.0.0'
api 'org.jsoup:jsoup:1.13.1'
api 'com.squareup.okhttp3:okhttp:4.4.1'
api 'cc.winboll.studio:libaes:7.6.0'
*/
implementation 'cc.winboll.studio:libaes:15.11.5'
implementation 'cc.winboll.studio:libappbase:15.11.0'
//api fileTree(dir: 'libs', include: ['*.aar'])
api fileTree(dir: 'libs', include: ['*.jar'])
}

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Wed Sep 03 20:59:53 HKT 2025
stageCount=13
#Fri Nov 21 03:39:38 HKT 2025
stageCount=6
libraryProject=
baseVersion=15.4
publishVersion=15.4.12
baseVersion=15.11
publishVersion=15.11.5
buildCount=0
baseBetaVersion=15.4.13
baseBetaVersion=15.11.6

View File

@@ -15,3 +15,6 @@
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
## 米盟
-keep class com.miui.zeus.** { *; }

View File

@@ -6,6 +6,19 @@
tools:replace="android:icon"
android:icon="@drawable/ic_launcher_beta">
<activity
android:name="cc.winboll.studio.powerbell.MainActivity"
android:launchMode="singleTask"
android:exported="true">
</activity>
<activity android:name="cc.winboll.studio.powerbell.unittest.MainUnitTestActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="cc.winboll.studio.powerbell.beta.fileprovider"

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">能源钟</string>
<string name="app_name">PowerBell</string>
</resources>

View File

@@ -1,8 +1,15 @@
<?xml version='1.0' encoding='utf-8'?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="cc.winboll.studio.powerbell">
<!-- 只能在前台获取精确的位置信息 -->
<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"/>
@@ -24,10 +31,29 @@
<!-- 显示通知 -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<!-- PACKAGE_USAGE_STATS -->
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
<!-- BATTERY_STATS -->
<uses-permission android:name="android.permission.BATTERY_STATS"/>
<!-- 计算应用存储空间 -->
<uses-permission android:name="android.permission.GET_PACKAGE_SIZE"/>
<uses-feature android:name="android.hardware.camera"/>
<uses-feature android:name="android.hardware.camera.autofocus"/>
<uses-permission android:name="android.permission.GET_PACKAGE_SIZE"/>
<uses-permission
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission"/>
<uses-permission
android:name="android.permission.ACCESS_PACKAGE_USAGE_STATS"
tools:ignore="ProtectedPermissions"/>
<application
android:name=".App"
android:allowBackup="true"
@@ -36,22 +62,9 @@
android:theme="@style/AppTheme_Default"
android:persistent="true"
android:resizeableActivity="true"
android:requestLegacyExternalStorage="true">
<activity
android:name=".MainActivity"
android:launchMode="singleTask"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
android:requestLegacyExternalStorage="true"
android:usesCleartextTraffic="true"
tools:ignore="GoogleAppIndexingWarning">
<activity
android:name="cc.winboll.studio.powerbell.activities.ClearRecordActivity"
@@ -123,6 +136,9 @@
<activity android:name="cc.winboll.studio.powerbell.activities.PixelPickerActivity"/>
<activity android:name="cc.winboll.studio.powerbell.activities.BatteryReportActivity"/>
</application>
</manifest>
</manifest>

View File

@@ -4,10 +4,10 @@ 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;
import cc.winboll.studio.powerbell.utils.AppCacheUtils;
import cc.winboll.studio.powerbell.utils.AppConfigUtils;
import com.hjq.toast.ToastUtils;
import java.io.File;
public class App extends GlobalApplication {
@@ -27,6 +27,7 @@ public class App extends GlobalApplication {
@Override
public void onCreate() {
super.onCreate();
setIsDebugging(BuildConfig.DEBUG);
// 临时文件夹方案1
// 获取Pictures文件夹路径Android 10及以上推荐使用MediaStore此处为传统方式
@@ -50,7 +51,7 @@ public class App extends GlobalApplication {
// 设置 Toast 布局样式
//ToastUtils.setView(R.layout.toast_custom_view);
//ToastUtils.setStyle(new WhiteToastStyle());
ToastUtils.setGravity(Gravity.BOTTOM, 0, 200);
//ToastUtils.setGravity(Gravity.BOTTOM, 0, 200);
// 设置数据配置存储工具
_mAppConfigUtils = getAppConfigUtils(this);
@@ -77,5 +78,13 @@ public class App extends GlobalApplication {
public void clearBatteryHistory() {
_mAppCacheUtils.clearBatteryHistory();
}
@Override
public void onTerminate() {
super.onTerminate();
ToastUtils.release();
}
}

View File

@@ -8,36 +8,62 @@ import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.RelativeLayout;
import android.widget.Toast;
import cc.winboll.studio.libaes.views.AToolbar;
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.powerbell.MainActivity;
import cc.winboll.studio.powerbell.activities.AboutActivity;
import cc.winboll.studio.powerbell.activities.BackgroundPictureActivity;
import cc.winboll.studio.powerbell.activities.BatteryReporterActivity;
import cc.winboll.studio.powerbell.activities.BatteryReportActivity;
import cc.winboll.studio.powerbell.activities.ClearRecordActivity;
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;
/**
* 主活动类修复小米广告SDK空Context崩溃问题
* 核心修改点:
* 1. 添加全局Context安全校验
* 2. 优化广告请求的生命周期判断
* 3. 确保广告操作在主线程执行
* 4. 完善广告资源释放逻辑
*/
public class MainActivity extends WinBoLLActivity {
public static final String TAG = "MainActivity";
public static final int BACKGROUND_PICTURE_REQUEST_CODE = 0;
// private static final String PRIVACY_FILE = "privacy_pfs";
// private static final String PRIVACY_VALUE = "privacy_value";//0: 拒绝1赞同
//
// private SharedPreferences mSharedPreferences;
//
// private String BANNER_POS_ID = "802e356f1726f9ff39c69308bfd6f06a";
// private String BANNER_POS_ID_WINBOLL_BETA = "d129ee5a263911f981a6dc7a9802e3e7";
// private String BANNER_POS_ID_WINBOLL = "4ec30efdb32271765b9a4efac902828b";
// private BannerAd mBannerAd;
// private List<BannerAd> mAllBanners = new ArrayList<>();
//
// private ViewGroup mContainer;
//
// private boolean mIsBiddingWin = true;
//
// public static final int BACKGROUND_PICTURE_REQUEST_CODE = 0;
public static MainActivity _mMainActivity;
//LogView mLogView;
//ArrayList<Fragment> mlistFragment;
App mApplication;
//AppConfigUtils mAppConfigUtils;
Menu mMenu;
Fragment mCurrentShowFragment;
MainViewFragment mMainViewFragment;
AToolbar mAToolbar;
private App mApplication;
private Menu mMenu;
private Fragment mCurrentShowFragment;
private MainViewFragment mMainViewFragment;
private Toolbar mToolbar;
// 新增主线程Handler确保广告操作在主线程执行
//private Handler mMainHandler;
ADsBannerView mADsBannerView;
@Override
public Activity getActivity() {
@@ -51,26 +77,28 @@ public class MainActivity extends WinBoLLActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
//LogUtils.d(TAG, "onCreate(...)");
LogUtils.d(TAG, "onCreate(...)");
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mADsBannerView = findViewById(R.id.adsbanner);
// mContainer = findViewById(R.id.ads_container);
//
// // 初始化主线程Handler关键确保广告操作在主线程执行
// mMainHandler = new Handler(Looper.getMainLooper());
//
// // 米盟模块:隐私协议弹窗
// showPrivacy();
// 设置调试日志
// mLogView = findViewById(R.id.logview);
// mLogView.start();
// //LogUtils.d(TAG, "LogView Start.");
// mLogView.updateLogView();
_mMainActivity = MainActivity.this;
_mMainActivity = this;
mApplication = (App) getApplication();
//mAppConfigUtils = AppConfigUtils.getInstance(mApplication);
// 初始化工具栏
mAToolbar = (AToolbar) findViewById(R.id.toolbar);
setActionBar(mAToolbar);
//mAToolbar.setSubtitle("Main");
mAToolbar.setTitleTextAppearance(this, R.style.Toolbar_TitleText);
mToolbar = findViewById(R.id.toolbar);
setSupportActionBar(mToolbar);
mToolbar.setTitleTextAppearance(this, R.style.Toolbar_TitleText);
// 初始化主Fragment
if (mMainViewFragment == null) {
FragmentTransaction tx = getFragmentManager().beginTransaction();
mMainViewFragment = new MainViewFragment();
@@ -78,13 +106,46 @@ public class MainActivity extends WinBoLLActivity {
tx.commit();
}
showFragment(mMainViewFragment);
// NotificationHelper notificationUtils = new NotificationHelper(this);
// notificationUtils.createNotificationChannels();
}
@Override
protected void onDestroy() {
super.onDestroy();
// // 修复:释放广告资源,避免内存泄漏
// releaseAdResources();
// 置空静态引用,避免内存泄漏
_mMainActivity = null;
// // 移除Handler回调
// if (mMainHandler != null) {
// mMainHandler.removeCallbacksAndMessages(null);
// }
if(mADsBannerView != null) {
mADsBannerView.releaseAdResources();
}
}
//
// /**
// * 释放广告资源关键避免内存泄漏和空Context调用
// */
// private void releaseAdResources() {
// LogUtils.d(TAG, "releaseAdResources()");
// // 销毁所有广告实例
// if (mAllBanners != null && !mAllBanners.isEmpty()) {
// for (BannerAd ad : mAllBanners) {
// if (ad != null) {
// ad.destroy();
// }
// }
// mAllBanners.clear();
// }
// // 置空当前广告引用
// mBannerAd = null;
// // 移除广告容器中的视图
// if (mContainer != null) {
// mContainer.removeAllViews();
// }
// }
void showFragment(Fragment fragment) {
FragmentTransaction tx = getFragmentManager().beginTransaction();
for (Fragment item : getFragmentManager().getFragments()) {
@@ -95,7 +156,6 @@ public class MainActivity extends WinBoLLActivity {
mCurrentShowFragment = fragment;
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
@@ -103,7 +163,8 @@ public class MainActivity extends WinBoLLActivity {
}
public static void reloadBackground() {
if (_mMainActivity != null) {
// 修复添加非空校验避免Activity已销毁时调用
if (_mMainActivity != null && !_mMainActivity.isFinishing() && !_mMainActivity.isDestroyed()) {
_mMainActivity.mMainViewFragment.loadBackground();
}
}
@@ -113,94 +174,88 @@ public class MainActivity extends WinBoLLActivity {
*/
private String getRealPathFromURI(Uri contentUri) {
String[] proj = {MediaStore.MediaColumns.DATA};
Cursor cursor=getContentResolver().query(contentUri, proj, null, null, null);
if (cursor.moveToNext()) {
int nColumnIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DATA);
if (nColumnIndex > -1) {
return cursor.getString(nColumnIndex);
} else {
LogUtils.d(TAG, "getRealPathFromURI nColumnIndex is -1.");
}
Cursor cursor = getContentResolver().query(contentUri, proj, null, null, null);
if (cursor != null && cursor.moveToNext()) {
int nColumnIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DATA);
if (nColumnIndex > -1) {
String path = cursor.getString(nColumnIndex);
cursor.close();
return path;
} else {
LogUtils.d(TAG, "getRealPathFromURI nColumnIndex is -1.");
}
cursor.close();
}
cursor.close();
return null;
}
}
@Override
protected void onResume() {
super.onResume();
// 回到窗口自动取消提醒消息
//NotificationHelper.cancelRemindNotification(this);
reloadBackground();
setBackgroundColor();
setBackgroundColor();
if(mADsBannerView != null) {
mADsBannerView.resumeADs();
}
// // 修复:优化广告请求逻辑(添加生命周期判断 + 主线程执行)
// if (!isFinishing() && !isDestroyed()) {
// String privacyAgreeValue = getSharedPreferences().getString(PRIVACY_VALUE, null);
// if (TextUtils.equals(privacyAgreeValue, String.valueOf(1))) {
// LogUtils.i(TAG, "已同意隐私协议,开始播放米盟广告...");
// mMainHandler.postDelayed(new Runnable() {
// @Override
// public void run() {
// // 再次校验生命周期避免延迟执行时Activity已销毁
// if (!isFinishing() && !isDestroyed()) {
// fetchAd();
// }
// }
// }, 1000); // 延迟1秒请求广告提升页面加载体验
// }
//
// }
}
// Menu icons are inflated just as they were with actionbar
//
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
mMenu = menu;
getMenuInflater().inflate(R.menu.toolbar_main, mMenu);
return true;
}
// 回退开发者选项重设UI到初始用户状态
//
/*public void resetUI(MainActivity MainActivity) {
mMenu.clear();
getMenuInflater().inflate(R.menu.toolbar_main, mMenu);
}*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
@Override
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
int menuItemId = item.getItemId();
if (menuItemId == R.id.action_about) {
Intent intent = new Intent(this, AboutActivity.class);
startActivity(intent);
} else if (menuItemId == R.id.action_battery_reporter) {
Intent intent = new Intent();
intent.setClass(this, BatteryReporterActivity.class);
startActivity(intent);
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) {
Intent intent = new Intent();
intent.setClass(this, ClearRecordActivity.class);
startActivity(intent);
startActivity(new Intent(this, ClearRecordActivity.class));
} else if (menuItemId == R.id.action_changepicture) {
Intent intent = new Intent();
intent.setClass(this, BackgroundPictureActivity.class);
startActivity(intent);
startActivity(new Intent(this, BackgroundPictureActivity.class));
} else if (menuItemId == R.id.action_log) {
App.getWinBoLLActivityManager().startLogActivity(this);
LogActivity.startLogActivity(this);
}
return true;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
//LogUtils.d(TAG, "onActivityResult(...)");
//LogUtils.d(TAG, "requestCode is : " + Integer.toString(requestCode));
//LogUtils.d(TAG, "resultCode is : " + Integer.toString(resultCode));
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == BACKGROUND_PICTURE_REQUEST_CODE) {
// 处理选择后图片
if (resultCode == RESULT_OK) {
//mMainViewFragment.loadBackgroundPicture();
Toast.makeText(getApplication(), "OK", Toast.LENGTH_SHORT).show();
}
} else {
String sz = "Unsolved requestCode = " + Integer.toString(requestCode);
Toast.makeText(getApplication(), sz, Toast.LENGTH_SHORT).show();
LogUtils.d(TAG, sz);
}
// if (requestCode == BACKGROUND_PICTURE_REQUEST_CODE) {
// if (resultCode == RESULT_OK) {
// Toast.makeText(getApplicationContext(), "OK", Toast.LENGTH_SHORT).show();
// }
// } else {
// String sz = "Unsolved requestCode = " + Integer.toString(requestCode);
// Toast.makeText(getApplicationContext(), sz, Toast.LENGTH_SHORT).show();
// LogUtils.d(TAG, sz);
// }
}
/**
* 返回键
*/
@Override
public void onBackPressed() {
if (mCurrentShowFragment != mMainViewFragment) {
@@ -210,11 +265,349 @@ public class MainActivity extends WinBoLLActivity {
}
}
void setBackgroundColor() {
BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(MainActivity.this);
BackgroundPictureBean bean = utils.getBackgroundPictureBean();
int nPixelColor = bean.getPixelColor();
RelativeLayout mainLayout = findViewById(R.id.activitymainRelativeLayout1);
mainLayout.setBackgroundColor(nPixelColor);
}
}
void setBackgroundColor() {
// 修复添加Activity非空校验
if (isFinishing() || isDestroyed()) {
return;
}
BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(this);
BackgroundPictureBean bean = utils.getBackgroundPictureBean();
int nPixelColor = bean.getPixelColor();
RelativeLayout mainLayout = findViewById(R.id.activitymainRelativeLayout1);
if (mainLayout != null) {
mainLayout.setBackgroundColor(nPixelColor);
}
}
//
// /**
// * 显示广告核心修复传递安全的Context + 生命周期校验)
// */
// private void showAd() {
// LogUtils.d(TAG, "showAd()");
// // 1. 生命周期校验避免Activity已销毁时操作UI
// if (isFinishing() || isDestroyed()) {
// LogUtils.e(TAG, "showAd: Activity is finishing or destroyed");
// return;
// }
// // 2. 非空校验:广告实例和容器
// if (mBannerAd == null || mContainer == null) {
// LogUtils.e(TAG, "showAd: BannerAd or Container is null");
// return;
// }
// // 3. 创建广告容器使用ApplicationContext避免内存泄漏
// final FrameLayout container = new FrameLayout(getApplicationContext());
// container.setPadding(0, 0, 0, MimoUtils.dpToPx(this, 10));
// mContainer.addView(container, new FrameLayout.LayoutParams(
// FrameLayout.LayoutParams.MATCH_PARENT,
// FrameLayout.LayoutParams.WRAP_CONTENT
// ));
//
// if (mIsBiddingWin) {
// mBannerAd.setPrice(getPrice());
// }
// // 4. 显示广告传递ApplicationContext避免Activity Context失效
// mBannerAd.showAd(MainActivity.this, container, new BannerAd.BannerInteractionListener() {
// @Override
// public void onAdClick() {
// LogUtils.d(TAG, "onAdClick");
// }
//
// @Override
// public void onAdShow() {
// LogUtils.d(TAG, "onAdShow");
// }
//
// @Override
// public void onAdDismiss() {
// LogUtils.d(TAG, "onAdDismiss");
// // 修复移除容器时校验Activity状态
// if (!isFinishing() && !isDestroyed() && mContainer != null) {
// mContainer.removeView(container);
// }
// }
//
// @Override
// public void onRenderSuccess() {
// LogUtils.d(TAG, "onRenderSuccess");
// }
//
// @Override
// public void onRenderFail(int code, String msg) {
// LogUtils.e(TAG, "onRenderFail errorCode " + code + " errorMsg " + msg);
// // 修复:渲染失败时移除容器
// if (!isFinishing() && !isDestroyed() && mContainer != null) {
// mContainer.removeView(container);
// }
// }
// });
// }
//
// /**
// * 请求广告核心修复Context安全校验 + 异常捕获 + 资源管理)
// */
// private void fetchAd() {
// LogUtils.d(TAG, "fetchAd()");
// // 1. 双重校验Activity未销毁 + Context非空
// if (isFinishing() || isDestroyed() || getApplicationContext() == null) {
// LogUtils.e(TAG, "fetchAd: Invalid Context or Activity state");
// return;
// }
// // 2. 释放之前的广告资源,避免内存泄漏
// if (mBannerAd != null) {
// mBannerAd.destroy();
// }
// // 3. 初始化广告使用ApplicationContext避免Activity Context失效
// try {
// mBannerAd = new BannerAd();
// mAllBanners.add(mBannerAd);
// } catch (Exception e) {
// LogUtils.e(TAG, "fetchAd: Init BannerAd failed", e);
// return;
// }
// // 4. 设置下载监听
// mBannerAd.setDownLoadListener(new BannerAd.BannerDownloadListener() {
// @Override
// public void onDownloadStarted() {
// LogUtils.d(TAG, "onDownloadStarted");
// }
//
// @Override
// public void onDownloadPaused() {
// LogUtils.d(TAG, "onDownloadPaused");
// }
//
// @Override
// public void onDownloadFailed(int errorCode) {
// LogUtils.d(TAG, "onDownloadFailed, errorCode = " + errorCode);
// }
//
// @Override
// public void onDownloadFinished() {
// LogUtils.d(TAG, "onDownloadFinished");
// }
//
// @Override
// public void onDownloadProgressUpdated(int progress) {
// LogUtils.d(TAG, "onDownloadProgressUpdated " + progress + "%");
// }
//
// @Override
// public void onInstallFailed(int errorCode) {
// LogUtils.d(TAG, "onInstallFailed, errorCode = " + errorCode);
// }
//
// @Override
// public void onInstallStart() {
// LogUtils.d(TAG, "onInstallStart");
// }
//
// @Override
// public void onInstallSuccess() {
// LogUtils.d(TAG, "onInstallSuccess");
// }
//
// @Override
// public void onDownloadCancel() {
// LogUtils.d(TAG, "onDownloadCancel");
// }
// });
//
// // 5. 构建广告参数并请求
// String currentAD_ID = getAD_ID();
// LogUtils.d(TAG, String.format("currentAD_ID = %s", currentAD_ID));
// ADParams params = new ADParams.Builder().setUpId(currentAD_ID).build();
// mBannerAd.loadAd(params, new BannerAd.BannerLoadListener() {
// @Override
// public void onBannerAdLoadSuccess() {
// LogUtils.d(TAG, "onBannerAdLoadSuccess()");
// // 修复广告加载成功后校验Activity状态
// if (!isFinishing() && !isDestroyed()) {
// showAd();
// }
// }
//
// @Override
// public void onAdLoadFailed(int errorCode, String errorMsg) {
// LogUtils.e(TAG, "onAdLoadFailed: errorCode = " + errorCode + ", errorMsg = " + errorMsg);
// // 修复:加载失败时移除当前广告实例
// if (mAllBanners.contains(mBannerAd)) {
// mAllBanners.remove(mBannerAd);
// }
// mBannerAd.destroy();
// mBannerAd = null;
// }
// });
// }
//
// /**
// * 根据当前秒数获取广告ID原逻辑保留
// */
// private String getAD_ID() {
// long currentSecond = System.currentTimeMillis() / 1000;
// return (currentSecond % 2 == 0) ? BANNER_POS_ID :
// (BuildConfig.DEBUG ? BANNER_POS_ID_WINBOLL_BETA : BANNER_POS_ID_WINBOLL);
// }
//
// /**
// * 获取广告价格(原逻辑保留,添加空指针校验)
// */
// private long getPrice() {
// if (mBannerAd == null) {
// return 0;
// }
// Map<String, Object> map = mBannerAd.getMediaExtraInfo();
// if (map == null || map.isEmpty() || !map.containsKey("price")) {
// LogUtils.w(TAG, "getPrice: media extra info is null or no price key");
// return 0;
// }
// Object priceObj = map.get("price");
// if (priceObj instanceof Long) {
// return (Long) priceObj;
// } else if (priceObj instanceof Integer) {
// return ((Integer) priceObj).longValue();
// } else {
// LogUtils.e(TAG, "getPrice: price type is invalid");
// return 0;
// }
// }
//
// /**
// * 显示隐私协议弹窗原逻辑保留优化Context使用
// */
// private void showPrivacy() {
// // 校验Activity状态避免弹窗泄露
// if (isFinishing() || isDestroyed()) {
// return;
// }
// String privacyAgreeValue = getSharedPreferences().getString(PRIVACY_VALUE, null);
// if (TextUtils.equals(privacyAgreeValue, String.valueOf(0))) {
// LogUtils.i(TAG, "已拒绝隐私协议,广告已处于不可用状态...");
// Toast.makeText(getApplicationContext(), "已拒绝隐私协议,广告已处于不可用状态", Toast.LENGTH_SHORT).show();
// return;
// }
// if (TextUtils.equals(privacyAgreeValue, String.valueOf(1))) {
// LogUtils.i(TAG, "已同意隐私协议开始初始化米盟SDK...");
// initMimoSdk();
// return;
// }
// LogUtils.i(TAG, "开始弹出隐私协议...");
// AlertDialog.Builder builder = new AlertDialog.Builder(this);
// builder.setTitle("用户须知");
// builder.setMessage("小米广告SDK隐私政策: https://dev.mi.com/distribute/doc/details?pId=1688, 请复制到浏览器查看");
// builder.setIcon(R.drawable.ic_launcher);
// builder.setCancelable(false); // 点击对话框以外的区域不消失
// builder.setPositiveButton("同意", new DialogInterface.OnClickListener() {
// @Override
// public void onClick(DialogInterface dialog, int which) {
// getSharedPreferences().edit()
// .putString(PRIVACY_VALUE, String.valueOf(1))
// .apply();
// initMimoSdk();
// dialog.dismiss();
// }
// });
// builder.setNegativeButton("拒绝", new DialogInterface.OnClickListener() {
// @Override
// public void onClick(DialogInterface dialog, int which) {
// getSharedPreferences().edit()
// .putString(PRIVACY_VALUE, String.valueOf(0))
// .apply();
// dialog.dismiss();
// }
// });
// AlertDialog dialog = builder.create();
//
// // 配置弹窗位置(底部全屏)
// Window window = dialog.getWindow();
// if (window != null) {
// window.setGravity(Gravity.BOTTOM);
// WindowManager m = getWindowManager();
// Display d = m.getDefaultDisplay();
// WindowManager.LayoutParams p = window.getAttributes();
// p.width = d.getWidth();
// window.setAttributes(p);
// }
// dialog.show();
// }
//
// /**
// * 初始化米盟SDK核心修复传递ApplicationContext + 异常捕获)
// */
// private void initMimoSdk() {
// // 1. 安全获取ApplicationContext避免Activity Context失效
// Context appContext = getApplicationContext();
// if (appContext == null) {
// Log.e(TAG, "initMimoSdk: ApplicationContext is null");
// return;
// }
// // 2. 初始化SDK捕获异常避免崩溃
// try {
// MimoSdk.init(appContext, new MimoCustomController() {
// @Override
// public boolean isCanUseLocation() {
// return true;
// }
//
// @Override
// public MimoLocation getMimoLocation() {
// return null;
// }
//
// @Override
// public boolean isCanUseWifiState() {
// return true;
// }
//
// @Override
// public boolean alist() {
// return true;
// }
// }, new MimoSdk.InitCallback() {
// @Override
// public void success() {
// Log.d(TAG, "MimoSdk init success");
// }
//
// @Override
// public void fail(int code, String msg) {
// Log.e(TAG, "MimoSdk init fail, code=" + code + ",msg=" + msg);
// }
// });
// MimoSdk.setDebugOn(true);
// } catch (Exception e) {
// Log.e(TAG, "initMimoSdk: init failed", e);
// }
// }
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
return super.dispatchKeyEvent(event);
}
@Override
public void setupToolbar() {
super.setupToolbar();
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
}
}
//
// /**
// * 获取SharedPreferences实例原逻辑保留添加空指针校验
// */
// public SharedPreferences getSharedPreferences() {
// if (mSharedPreferences == null) {
// // 修复使用ApplicationContext获取SharedPreferences避免Activity Context泄露
// Context appContext = getApplicationContext();
// if (appContext != null) {
// mSharedPreferences = appContext.getSharedPreferences(PRIVACY_FILE, Context.MODE_PRIVATE);
// } else {
// Log.e(TAG, "getSharedPreferences: ApplicationContext is null");
// // 降级方案若ApplicationContext为空使用Activity Context仅作兼容
// mSharedPreferences = super.getSharedPreferences(PRIVACY_FILE, Context.MODE_PRIVATE);
// }
// }
// return mSharedPreferences;
// }
}

View File

@@ -10,9 +10,9 @@ import android.content.Context;
import android.os.Bundle;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import cc.winboll.studio.libaes.models.APPInfo;
import cc.winboll.studio.libaes.views.AToolbar;
import cc.winboll.studio.libaes.winboll.APPInfo;
import cc.winboll.studio.libaes.winboll.AboutView;
import cc.winboll.studio.libaes.views.AboutView;
import cc.winboll.studio.powerbell.R;
public class AboutActivity extends Activity {
@@ -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);

View File

@@ -19,7 +19,7 @@ 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.utils.ToastUtils;
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;
@@ -33,6 +33,7 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import cc.winboll.studio.powerbell.dialogs.NetworkBackgroundDialog;
public class BackgroundPictureActivity extends WinBoLLActivity implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
@@ -589,5 +590,34 @@ public class BackgroundPictureActivity extends WinBoLLActivity implements Backgr
super.onResume();
setBackgroundColor();
}
public void onNetworkBackgroundDialog(View view) {
// 在需要显示对话框的地方(如网络状态监听回调中)
NetworkBackgroundDialog dialog = new NetworkBackgroundDialog(this, new NetworkBackgroundDialog.OnDialogClickListener() {
@Override
public void onConfirm() {
ToastUtils.show("onConfirm");
// 处理确认逻辑(如允许后台网络使用)
LogUtils.d("MainActivity", "用户允许后台网络使用");
// 执行具体业务:如开启后台网络请求服务
}
@Override
public void onCancel() {
ToastUtils.show("onCancel");
// 处理取消逻辑(如禁止后台网络使用)
LogUtils.d("MainActivity", "用户禁止后台网络使用");
// 执行具体业务:如关闭后台网络请求
}
});
// 可选:修改对话框标题和内容(适配自定义场景)
dialog.setTitle("网络图片下载对话框");
dialog.setContent("是否下载地址中的图片资源,作为应用背景图片?");
// 显示对话框
dialog.show();
}
}

View File

@@ -0,0 +1,516 @@
package cc.winboll.studio.powerbell.activities;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/10/22 13:21
* @Describe BatteryReportActivity
*/
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import cc.winboll.studio.powerbell.R;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import cc.winboll.studio.libappbase.LogUtils;
public class BatteryReportActivity extends Activity {
public static final String TAG = "BatteryReportActivity";
private RecyclerView rvBatteryReport;
private BatteryReportAdapter adapter;
private List<AppBatteryModel> dataList = new ArrayList<AppBatteryModel>();
private List<AppBatteryModel> filteredList = new ArrayList<AppBatteryModel>();
private BroadcastReceiver batteryReceiver;
private int batteryCapacity = 5400; // 电池容量mAh
private float lastBatteryPercent = 100.0f;
private long lastCheckTime = System.currentTimeMillis();
private EditText etSearch;
private Map<String, Long> appRunTimeCache = new HashMap<String, Long>();
private Map<String, String> packageToAppNameCache = new HashMap<String, String>();
private PackageManager mPackageManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_battery_report);
mPackageManager = getPackageManager();
// 权限检查Java7 传统条件判断)
if (!hasUsageStatsPermission(this)) {
Toast.makeText(this, "请进入设置-应用-权限-特殊访问权限-使用情况访问权限,开启本应用的权限", Toast.LENGTH_LONG).show();
startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));
return;
}
etSearch = (EditText) findViewById(R.id.et_search);
rvBatteryReport = (RecyclerView) findViewById(R.id.rv_battery_report);
rvBatteryReport.setLayoutManager(new LinearLayoutManager(this));
// 初始化流程新增“加载24小时累计耗电”步骤
loadAllAppPackage();
preCacheAllAppNames();
appRunTimeCache = getAppRunTime();
updateAppRunTimeToModel();
calculateInitial24hTotalConsumption(); // 初始化时计算24小时累计耗电
filteredList.addAll(dataList);
adapter = new BatteryReportAdapter(this, filteredList, mPackageManager, packageToAppNameCache);
rvBatteryReport.setAdapter(adapter);
// 搜索监听(不变)
etSearch.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
filterAppsByPackageAndName(s.toString());
}
@Override
public void afterTextChanged(Editable s) {}
});
// 电池广播:调用修改后的“单次耗电计算+累计累加”方法
batteryReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
int level = intent.getIntExtra("level", 100);
int scale = intent.getIntExtra("scale", 100);
float currentPercent = (float) level / scale * 100;
LogUtils.d(TAG, "电池百分比变化:" + lastBatteryPercent + " -> " + currentPercent);
if (currentPercent < lastBatteryPercent) {
float dropPercent = lastBatteryPercent - currentPercent;
long duration = System.currentTimeMillis() - lastCheckTime;
LogUtils.d(TAG, "电池消耗:" + dropPercent + "%,时长:" + duration + "ms");
appRunTimeCache = getAppRunTime();
updateAppRunTimeToModel();
calculateSingleConsumptionAndAccumulate(dropPercent, appRunTimeCache); // 单次+累计逻辑
}
lastBatteryPercent = currentPercent;
lastCheckTime = System.currentTimeMillis();
}
};
registerReceiver(batteryReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
}
@Override
protected void onDestroy() {
super.onDestroy();
// Java7 显式非空判断
if (batteryReceiver != null) {
unregisterReceiver(batteryReceiver);
}
}
/**
* 加载所有应用仅获取包名初始化模型时单次耗电、累计耗电均设为0
*/
private void loadAllAppPackage() {
List<ApplicationInfo> appList = mPackageManager.getInstalledApplications(PackageManager.GET_META_DATA);
dataList.clear();
LogUtils.d(TAG, "开始加载应用包名列表,共找到" + appList.size() + "个应用");
for (ApplicationInfo appInfo : appList) {
String packageName = appInfo.packageName;
// 初始化单次耗电consumption=0累计耗电totalConsumption=0运行时长=0
dataList.add(new AppBatteryModel(packageName, 0.0f, 0.0f, 0));
}
LogUtils.d(TAG, "应用包名列表加载完成,共添加" + dataList.size() + "个包名。");
}
/**
* 预缓存应用名称(逻辑不变)
*/
private void preCacheAllAppNames() {
packageToAppNameCache.clear();
LogUtils.d(TAG, "开始预缓存包名-应用名称映射");
for (AppBatteryModel model : dataList) {
String packageName = model.getPackageName();
String appName = getAppNameByPackage(packageName);
packageToAppNameCache.put(packageName, appName);
}
LogUtils.d(TAG, "预缓存完成,共缓存" + packageToAppNameCache.size() + "个应用名称");
}
/**
* 通过包名获取应用名称(逻辑不变)
*/
private String getAppNameByPackage(String packageName) {
try {
ApplicationInfo appInfo = mPackageManager.getApplicationInfo(packageName, 0);
return mPackageManager.getApplicationLabel(appInfo).toString();
} catch (PackageManager.NameNotFoundException e) {
LogUtils.e(TAG, "包名" + packageName + "对应的应用未找到:" + e.getMessage());
return packageName;
} catch (Exception e) {
LogUtils.e(TAG, "查询应用名称失败(包名:" + packageName + "" + e.getMessage());
return packageName;
}
}
/**
* 更新运行时长到模型(逻辑不变)
*/
private void updateAppRunTimeToModel() {
int nCount = 0;
for (AppBatteryModel model : dataList) {
String packageName = model.getPackageName();
Long runTime;
if (appRunTimeCache.containsKey(packageName)) {
runTime = appRunTimeCache.get(packageName);
LogUtils.d(TAG, String.format("应用包 %s 运行时长已更新。", packageName));
nCount++;
} else {
runTime = 0L;
}
model.setRunTime(runTime);
}
LogUtils.d(TAG, String.format("dataList.size() %d appRunTimeCache.size() %d。", dataList.size(), appRunTimeCache.size()));
LogUtils.d(TAG, String.format("updateAppRunTimeToModel() 更新的数据量为:%d", nCount));
}
/**
* 【新增】初始化时计算24小时累计耗电赋值给totalConsumption
* 逻辑基于24小时运行时长占比分配当前电池容量的理论24小时消耗
*/
private void calculateInitial24hTotalConsumption() {
long total24hRunTime = 0;
// 1. 计算24小时内所有应用总运行时长
for (Map.Entry<String, Long> entry : appRunTimeCache.entrySet()) {
total24hRunTime += entry.getValue();
}
LogUtils.d(TAG, "24小时内所有应用总运行时长" + formatRunTime(total24hRunTime));
// 2. 按运行时长占比分配24小时累计耗电假设电池满电循环用总容量近似24小时总消耗
for (AppBatteryModel model : dataList) {
String packageName = model.getPackageName();
Long app24hRunTime = appRunTimeCache.getOrDefault(packageName, 0L);
// 计算占比与累计耗电
float ratio = (total24hRunTime > 0) ? (float) app24hRunTime / total24hRunTime : 0;
float initialTotalConsumption = batteryCapacity * ratio; // 用电池容量近似24小时总消耗
model.setTotalConsumption(initialTotalConsumption); // 初始化累计耗电
LogUtils.d(TAG, String.format("应用包 %s 24小时累计耗电初始化%.1f mAh", packageName, initialTotalConsumption));
}
}
/**
* 【核心修改】计算单次耗电赋值给consumption+ 累加至累计耗电totalConsumption = totalConsumption + consumption
*/
private void calculateSingleConsumptionAndAccumulate(float dropPercent, Map<String, Long> runTimeMap) {
long totalSingleRunTime = 0;
// 1. 计算本次电池下降期间的总运行时长
for (Map.Entry<String, Long> entry : runTimeMap.entrySet()) {
totalSingleRunTime += entry.getValue();
}
// 2. 遍历计算每个应用的“单次耗电”并“累加至累计”
for (AppBatteryModel model : dataList) {
String packageName = model.getPackageName();
Long appSingleRunTime = runTimeMap.getOrDefault(packageName, 0L);
// 步骤1计算本次单次耗电赋值给consumption
float ratio = (totalSingleRunTime > 0) ? (float) appSingleRunTime / totalSingleRunTime : 0;
float singleConsumption = batteryCapacity * dropPercent / 100 * ratio; // 单次消耗
model.setConsumption(singleConsumption); // 存储单次耗电
// 步骤2累加单次耗电到累计耗电totalConsumption = 原有累计 + 本次单次)
float newTotalConsumption = model.getTotalConsumption() + singleConsumption;
model.setTotalConsumption(newTotalConsumption); // 更新累计耗电
// 同步运行时长
model.setRunTime(appSingleRunTime);
LogUtils.d(TAG, String.format("应用包 %s单次耗电%.1f mAh累计耗电%.1f mAh",
packageName, singleConsumption, newTotalConsumption));
}
// 3. 按累计耗电排序(从高到低)
Collections.sort(dataList, new Comparator<AppBatteryModel>() {
@Override
public int compare(AppBatteryModel m1, AppBatteryModel m2) {
return Float.compare(m2.getTotalConsumption(), m1.getTotalConsumption());
}
});
// 4. 重新应用过滤并刷新列表
filterAppsByPackageAndName(etSearch.getText().toString());
}
/**
* 双维度过滤(逻辑不变)
*/
private void filterAppsByPackageAndName(String keyword) {
filteredList.clear();
if (keyword == null || keyword.isEmpty()) {
filteredList.addAll(dataList);
} else {
String lowerKeyword = keyword.toLowerCase();
for (AppBatteryModel model : dataList) {
String packageName = model.getPackageName();
String packageNameLower = packageName.toLowerCase();
String appName = packageToAppNameCache.get(packageName);
String appNameLower = appName.toLowerCase();
boolean isMatched = packageNameLower.contains(lowerKeyword)
|| appNameLower.contains(lowerKeyword);
if (isMatched) {
filteredList.add(model);
}
}
}
adapter.notifyDataSetChanged();
}
/**
* 获取应用运行时长逻辑不变返回24小时运行时长
*/
private Map<String, Long> getAppRunTime() {
Map<String, Long> runTimeMap = new HashMap<String, Long>();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
try {
android.app.usage.UsageStatsManager manager =
(android.app.usage.UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE);
long endTime = System.currentTimeMillis();
long startTime = endTime - 24 * 3600 * 1000; // 近24小时
List<android.app.usage.UsageStats> statsList = manager.queryUsageStats(
android.app.usage.UsageStatsManager.INTERVAL_DAILY, startTime, endTime);
for (android.app.usage.UsageStats stats : statsList) {
long runTimeMs = stats.getTotalTimeInForeground();
String packageName = stats.getPackageName();
LogUtils.d(TAG, "包名" + packageName + "24小时运行时长" + formatRunTime(runTimeMs));
runTimeMap.put(packageName, runTimeMs);
if (packageName.equals("aidepro.top")) {
LogUtils.d(TAG, String.format("runTimeMap.put(packageName, runTimeMs) 特殊查询 %s 查询有结果。", packageName));
}
}
} catch (Exception e) {
LogUtils.e(TAG, "获取应用运行时长失败:" + e.getMessage());
}
}
LogUtils.d(TAG, String.format("应用运行时长列表数量%d。", runTimeMap.size()));
return runTimeMap;
}
/**
* 格式化运行时长(逻辑不变)
*/
private String formatRunTime(long runTimeMs) {
if (runTimeMs <= 0) {
return "0秒";
}
long seconds = runTimeMs / 1000;
long hours = seconds / 3600;
long minutes = (seconds % 3600) / 60;
seconds = seconds % 60;
if (hours > 0) {
return String.format("%d时%d分%d秒", hours, minutes, seconds);
} else if (minutes > 0) {
return String.format("%d分%d秒", minutes, seconds);
} else {
return String.format("%d秒", seconds);
}
}
/**
* 权限检查(逻辑不变)
*/
private boolean hasUsageStatsPermission(Context context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return false;
}
android.app.usage.UsageStatsManager manager =
(android.app.usage.UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
if (manager == null) {
return false;
}
long endTime = System.currentTimeMillis();
long startTime = endTime - 1000 * 60;
List<android.app.usage.UsageStats> statsList = manager.queryUsageStats(
android.app.usage.UsageStatsManager.INTERVAL_DAILY, startTime, endTime);
return statsList != null && !statsList.isEmpty();
}
/**
* 【核心修改】数据模型:明确字段含义
* - consumption单次耗电两次电池广播间的消耗float类型便于计算
* - totalConsumption累计耗电24小时初始化值+后续单次累加,显示用)
*/
public static class AppBatteryModel {
private String packageName; // 应用包名(核心标识)
private float consumption; // 单次耗电mAhfloat类型
private float totalConsumption;// 累计耗电mAh显示+排序用)
private long runTime; // 运行时长ms
// Java7 显式构造初始化单次耗电、累计耗电为0
public AppBatteryModel(String packageName, float consumption, float totalConsumption, long runTime) {
this.packageName = packageName;
this.consumption = consumption; // 单次耗电初始为0
this.totalConsumption = totalConsumption; // 累计耗电初始为0后续初始化时赋值
this.runTime = runTime;
}
// Getter/Setter覆盖所有字段确保数据操作正常
public String getPackageName() {
return packageName;
}
public float getConsumption() {
return consumption; // 获取单次耗电
}
public void setConsumption(float consumption) {
this.consumption = consumption; // 设置单次耗电
}
public float getTotalConsumption() {
return totalConsumption; // 获取累计耗电(显示用)
}
public void setTotalConsumption(float totalConsumption) {
this.totalConsumption = totalConsumption; // 设置累计耗电(初始化/累加用)
}
public long getRunTime() {
return runTime;
}
public void setRunTime(long runTime) {
this.runTime = runTime;
}
}
/**
* RecyclerView 适配器仅显示累计耗电totalConsumption逻辑适配模型修改
*/
public static class BatteryReportAdapter extends RecyclerView.Adapter<BatteryReportAdapter.ViewHolder> {
private Context mContext;
private List<AppBatteryModel> mDataList;
private PackageManager mPm;
private Map<String, String> mPackageToNameCache;
// Java7 显式构造:接收名称缓存,确保显示时高效获取应用名
public BatteryReportAdapter(Context context, List<AppBatteryModel> dataList,
PackageManager pm, Map<String, String> packageToNameCache) {
this.mContext = context;
this.mDataList = dataList;
this.mPm = pm;
this.mPackageToNameCache = packageToNameCache;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// 加载系统列表项布局text1显示应用名text2显示累计耗电+时长)
View itemView = LayoutInflater.from(mContext)
.inflate(android.R.layout.simple_list_item_2, parent, false);
return new ViewHolder(itemView);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
// Java7 显式非空判断:避免空指针异常
if (mDataList == null || mDataList.isEmpty() || position >= mDataList.size()) {
holder.tvAppName.setText("未知应用");
holder.tvConsumption.setText("累计耗电0.0 mAh | 运行时长0秒");
return;
}
AppBatteryModel model = mDataList.get(position);
String packageName = model.getPackageName();
String appName = "";
// 优先从缓存获取应用名减少PackageManager调用提升性能
if (mPackageToNameCache != null && mPackageToNameCache.containsKey(packageName)) {
appName = mPackageToNameCache.get(packageName);
} else {
// 缓存无数据时兜底查询,并同步更新缓存
try {
ApplicationInfo appInfo = mPm.getApplicationInfo(packageName, 0);
appName = mPm.getApplicationLabel(appInfo).toString();
if (mPackageToNameCache != null) {
mPackageToNameCache.put(packageName, appName);
}
} catch (PackageManager.NameNotFoundException e) {
appName = packageName; // 包名不存在时用包名兜底
LogUtils.e("Adapter", "包名" + packageName + "对应的应用未找到:" + e.getMessage());
} catch (Exception e) {
appName = packageName; // 其他异常时用包名兜底
LogUtils.e("Adapter", "查询应用名称失败(包名:" + packageName + "" + e.getMessage());
}
}
// 显示逻辑仅展示累计耗电totalConsumption隐藏单次耗电
holder.tvAppName.setText(appName);
// 格式化运行时长 + 累计耗电保留1位小数提升可读性
String runTimeStr = ((BatteryReportActivity) mContext).formatRunTime(model.getRunTime());
String totalConsumptionText = String.format("累计耗电:%.1f mAh | 运行时长:%s",
model.getTotalConsumption(), runTimeStr);
holder.tvConsumption.setText(totalConsumptionText);
// 显示优化:文字颜色区分(避免所有应用均标蓝,仅示例可按需修改)
holder.tvAppName.setTextColor(mContext.getResources().getColor(android.R.color.black));
holder.tvConsumption.setTextColor(mContext.getResources().getColor(android.R.color.darker_gray));
// 调整文字大小:适配手机屏幕,提升可读性
holder.tvAppName.setTextSize(16);
holder.tvConsumption.setTextSize(14);
}
// 获取列表长度Java7 三元运算符判断空值,避免空指针
@Override
public int getItemCount() {
return mDataList == null ? 0 : mDataList.size();
}
/**
* ViewHolder绑定系统布局控件与显示逻辑对应
*/
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView tvAppName; // 显示应用名称
TextView tvConsumption; // 显示累计耗电 + 运行时长
// Java7 显式构造绑定控件ID系统布局固定IDtext1、text2
public ViewHolder(View itemView) {
super(itemView);
tvAppName = (TextView) itemView.findViewById(android.R.id.text1);
tvConsumption = (TextView) itemView.findViewById(android.R.id.text2);
}
}
}
}

View File

@@ -1,51 +0,0 @@
package cc.winboll.studio.powerbell.activities;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/03/22 14:20:15
*/
import android.app.Activity;
import android.os.Bundle;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
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 java.util.Arrays;
import java.util.List;
public class BatteryReporterActivity extends Activity {
public static final String TAG = "BatteryReporterActivity";
private RecyclerView rvBatteryReport;
private BatteryAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_battery_reporter);
rvBatteryReport = findViewById(R.id.rvBatteryReport);
setupRecyclerView();
loadSampleData();
}
private void setupRecyclerView() {
adapter = new BatteryAdapter();
rvBatteryReport.setLayoutManager(new LinearLayoutManager(this));
rvBatteryReport.setAdapter(adapter);
rvBatteryReport.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
}
private void loadSampleData() {
List<BatteryData> dataList = Arrays.asList(
new BatteryData(95, "01:23:45", "00:05:12"),
new BatteryData(80, "02:15:30", "00:10:00"),
new BatteryData(65, "03:45:15", "00:15:30"),
new BatteryData(50, "05:00:00", "00:20:45")
);
adapter.updateData(dataList);
}
}

View File

@@ -4,11 +4,12 @@ import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Switch;
import android.widget.TextView;
import cc.winboll.studio.libaes.views.AOHPCTCSeekBar;
import cc.winboll.studio.libaes.views.AToolbar;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.utils.ToastUtils;
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;
@@ -16,7 +17,6 @@ import cc.winboll.studio.powerbell.receivers.ControlCenterServiceReceiver;
import cc.winboll.studio.powerbell.utils.AppCacheUtils;
import cc.winboll.studio.powerbell.utils.StringUtils;
import java.util.ArrayList;
import android.widget.Switch;
public class ClearRecordActivity extends Activity {

View File

@@ -6,6 +6,7 @@ package cc.winboll.studio.powerbell.activities;
*/
import android.app.Activity;
import android.app.Dialog;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
@@ -19,11 +20,11 @@ import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
import cc.winboll.studio.libaes.views.AToolbar;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
import cc.winboll.studio.powerbell.MainActivity;
import cc.winboll.studio.powerbell.R;
import cc.winboll.studio.powerbell.activities.BackgroundPictureActivity;
import cc.winboll.studio.powerbell.activities.PixelPickerActivity;
import cc.winboll.studio.powerbell.beans.BackgroundPictureBean;
import cc.winboll.studio.powerbell.utils.BackgroundPictureUtils;
@@ -233,7 +234,10 @@ public class PixelPickerActivity extends WinBoLLActivity implements IWinBoLLActi
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(getApplicationContext(), BackgroundPictureActivity.class);
Intent intent = new Intent();
intent.setClass(this, BackgroundPictureActivity.class);
startActivity(intent);
//GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(getApplicationContext(), );
return true;
}
// 在switch语句中处理每个ID并在处理完后返回true未处理的情况返回false。
@@ -243,7 +247,10 @@ public class PixelPickerActivity extends WinBoLLActivity implements IWinBoLLActi
@Override
public void onBackPressed() {
super.onBackPressed();
GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(getApplicationContext(), BackgroundPictureActivity.class);
Intent intent = new Intent();
intent.setClass(this, BackgroundPictureActivity.class);
startActivity(intent);
//GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(getApplicationContext(), BackgroundPictureActivity.class);
}
}

View File

@@ -5,40 +5,93 @@ package cc.winboll.studio.powerbell.activities;
* @Date 2025/06/19 20:35
* @Describe 应用窗口基类
*/
import android.annotation.SuppressLint;
import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MenuItem;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
import cc.winboll.studio.powerbell.App;
import cc.winboll.studio.powerbell.MainActivity;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
import cc.winboll.studio.powerbell.BuildConfig;
import cc.winboll.studio.powerbell.R;
public abstract class WinBoLLActivity extends Activity implements IWinBoLLActivity {
@SuppressLint("SetTextI18n")
public abstract class WinBoLLActivity extends AppCompatActivity implements IWinBoLLActivity {
public static final String TAG = "WinBoLLActivity";
protected TextView mTagView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
changeFullScreen(this);
}
@Override
protected void onStart() {
super.onStart();
addVersionNameToContentView();
}
protected void addVersionNameToContentView() {
if (!isTagViewVisible()) {
return;
}
if (mTagView == null) {
mTagView = new TextView(this);
mTagView.setTextColor(Color.GRAY);
mTagView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 10);
mTagView.setText("MIMO SDK V" + BuildConfig.VERSION_NAME);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
FrameLayout frameLayout = findViewById(android.R.id.content);
frameLayout.addView(mTagView, params);
}
}
protected boolean isTagViewVisible() {
return true;
}
public void setupToolbar() {
Toolbar mToolbar = findViewById(R.id.toolbar);
if (mToolbar != null) {
setSupportActionBar(mToolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
}
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
GlobalApplication.getWinBoLLActivityManager().add(this);
//GlobalApplication.getWinBoLLActivityManager().add(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
GlobalApplication.getWinBoLLActivityManager().registeRemove(this);
//GlobalApplication.getWinBoLLActivityManager().registeRemove(this);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(getApplicationContext(), MainActivity.class);
//GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(getApplicationContext(), MainActivity.class);
return true;
}
// 在switch语句中处理每个ID并在处理完后返回true未处理的情况返回false。
@@ -48,6 +101,25 @@ public abstract class WinBoLLActivity extends Activity implements IWinBoLLActivi
@Override
public void onBackPressed() {
super.onBackPressed();
GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(getApplicationContext(), MainActivity.class);
//GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(getApplicationContext(), MainActivity.class);
}
public void changeFullScreen(Activity activity) {
Window window = activity.getWindow();
if (window == null){
return;
}
View decorView = window.getDecorView();
if (decorView == null){
return;
}
int flag = decorView.getSystemUiVisibility();
flag |= View.SYSTEM_UI_FLAG_FULLSCREEN;
flag |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
flag |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
flag |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
flag |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
decorView.setSystemUiVisibility(flag);
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
}

View File

@@ -0,0 +1,269 @@
package cc.winboll.studio.powerbell.dialogs;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Message;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.powerbell.R;
import cc.winboll.studio.powerbell.utils.ImageDownloader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import cc.winboll.studio.powerbell.views.BackgroundView;
import android.content.DialogInterface;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/11/19 20:11
* @Describe 网络后台使用提示对话框
* 继承 AndroidX AlertDialog绑定自定义布局 dialog_networkbackground.xml
*/
public class NetworkBackgroundDialog extends AlertDialog {
public static final String TAG = "NetworkBackgroundDialog";
// 消息标识:图片加载成功
private static final int MSG_IMAGE_LOAD_SUCCESS = 1001;
// 消息标识:图片加载失败
private static final int MSG_IMAGE_LOAD_FAILED = 1002;
// 控件引用
private TextView tvTitle;
private TextView tvContent;
private Button btnCancel;
private Button btnConfirm;
private Button btnPreview;
private EditText etURL;
BackgroundView bvBackgroundPreview;
Context mContext;
// 主线程 Handler用于接收子线程消息并更新 UI
private Handler mUiHandler;
// 按钮点击回调接口Java7 接口实现)
public interface OnDialogClickListener {
void onConfirm(); // 确认按钮点击
void onCancel(); // 取消按钮点击
}
private OnDialogClickListener listener;
// Java7 显式构造(必须传入 Context
public NetworkBackgroundDialog(@NonNull Context context) {
super(context);
initHandler(); // 初始化 Handler
initView(); // 初始化布局和控件
setDismissListener(); // 设置对话框消失监听
}
// 带回调的构造(便于外部处理点击事件)
public NetworkBackgroundDialog(@NonNull Context context, OnDialogClickListener listener) {
super(context);
this.listener = listener;
initHandler(); // 初始化 Handler
initView();
setDismissListener(); // 设置对话框消失监听
}
/**
* 初始化主线程 Handler用于更新 UI
*/
private void initHandler() {
mUiHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
// 对话框已消失时,不再处理 UI 消息
if (!isShowing()) {
return;
}
switch (msg.what) {
case MSG_IMAGE_LOAD_SUCCESS:
// 图片加载成功,获取文件路径并设置背景
String filePath = (String) msg.obj;
setBackgroundFromPath(filePath);
break;
case MSG_IMAGE_LOAD_FAILED:
// 图片加载失败,设置默认背景
bvBackgroundPreview.setBackgroundResource(R.drawable.ic_launcher);
ToastUtils.show("图片预览失败,请检查链接");
break;
}
}
};
}
/**
* 设置对话框消失监听:移除 Handler 消息,避免内存泄漏
*/
private void setDismissListener() {
this.setOnDismissListener(new OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
// 对话框消失时,移除所有未处理的消息和回调
if (mUiHandler != null) {
mUiHandler.removeCallbacksAndMessages(null);
}
LogUtils.d(TAG, "对话框已消失Handler 消息已清理");
}
});
}
/**
* 初始化布局和控件
*/
private void initView() {
mContext = this.getContext();
// 加载自定义布局
View dialogView = LayoutInflater.from(getContext())
.inflate(R.layout.dialog_networkbackground, null);
// 设置对话框内容视图
setView(dialogView);
// 绑定控件
tvTitle = (TextView) dialogView.findViewById(R.id.tv_dialog_title);
tvContent = (TextView) dialogView.findViewById(R.id.tv_dialog_content);
btnCancel = (Button) dialogView.findViewById(R.id.btn_cancel);
btnConfirm = (Button) dialogView.findViewById(R.id.btn_confirm);
btnPreview = (Button) dialogView.findViewById(R.id.btn_preview);
etURL = (EditText) dialogView.findViewById(R.id.et_url);
bvBackgroundPreview = (BackgroundView) dialogView.findViewById(R.id.bv_background_preview);
// 设置按钮点击事件
setButtonClickListeners();
}
/**
* 设置按钮点击监听
*/
private void setButtonClickListeners() {
// 取消按钮:关闭对话框 + 回调外部
btnCancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.d("NetworkBackgroundDialog", "取消按钮点击");
dismiss(); // 关闭对话框
if (listener != null) {
listener.onCancel();
}
}
});
// 确认按钮:关闭对话框 + 回调外部
btnConfirm.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.d("NetworkBackgroundDialog", "确认按钮点击");
dismiss(); // 关闭对话框
if (listener != null) {
listener.onConfirm();
}
}
});
// 图片预览按钮:预览输入框地址图片
btnPreview.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.d("NetworkBackgroundDialog", "确认预览点击");
String url = etURL.getText().toString().trim();
if (url.isEmpty()) {
ToastUtils.show("请输入图片链接");
return;
}
ImageDownloader.getInstance(mContext).downloadImage(url, mDownloadCallback);
}
});
}
/**
* 根据文件路径设置 BackgroundView 背景(主线程调用)
* @param filePath 图片文件路径
*/
private void setBackgroundFromPath(String filePath) {
FileInputStream fis = null;
try {
File imageFile = new File(filePath);
if (!imageFile.exists()) {
LogUtils.e(TAG, "图片文件不存在:" + filePath);
bvBackgroundPreview.setBackgroundResource(R.drawable.ic_launcher);
return;
}
fis = new FileInputStream(imageFile);
Drawable drawable = Drawable.createFromStream(fis, null);
// 设置背景(主线程安全操作)
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
bvBackgroundPreview.setBackground(drawable);
} else {
bvBackgroundPreview.setBackgroundDrawable(drawable);
}
LogUtils.d(TAG, "图片预览成功:" + filePath);
} catch (Exception e) {
e.printStackTrace();
bvBackgroundPreview.setBackgroundResource(R.drawable.ic_launcher);
LogUtils.e(TAG, "图片预览失败:" + e.getMessage());
} finally {
// Java7 手动关闭流,避免资源泄漏
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 对外提供方法:修改对话框标题(灵活适配不同场景)
*/
public void setTitle(String title) {
if (tvTitle != null) {
tvTitle.setText(title);
}
}
/**
* 对外提供方法:修改对话框内容(灵活适配不同场景)
*/
public void setContent(String content) {
if (tvContent != null) {
tvContent.setText(content);
}
}
/**
* 对外提供方法:设置按钮点击回调(替代带参构造)
*/
public void setOnDialogClickListener(OnDialogClickListener listener) {
this.listener = listener;
}
ImageDownloader.DownloadCallback mDownloadCallback = new ImageDownloader.DownloadCallback() {
@Override
public void onSuccess(String filePath) {
ToastUtils.show("图片下载成功:" + filePath);
LogUtils.d(TAG, filePath);
// 发送消息到主线程,携带图片路径
Message successMsg = mUiHandler.obtainMessage(MSG_IMAGE_LOAD_SUCCESS, filePath);
mUiHandler.sendMessage(successMsg);
}
@Override
public void onFailure(String errorMsg) {
ToastUtils.show("下载失败:" + errorMsg);
LogUtils.e(TAG, errorMsg);
// 发送图片加载失败消息
mUiHandler.sendEmptyMessage(MSG_IMAGE_LOAD_FAILED);
}
};
}

View File

@@ -14,10 +14,12 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.widget.RemoteViews;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.utils.ToastUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.powerbell.App;
import cc.winboll.studio.powerbell.MainActivity;
import cc.winboll.studio.powerbell.R;
@@ -32,8 +34,6 @@ import cc.winboll.studio.powerbell.utils.AppConfigUtils;
import cc.winboll.studio.powerbell.utils.NotificationHelper;
import cc.winboll.studio.powerbell.utils.ServiceUtils;
import cc.winboll.studio.powerbell.utils.StringUtils;
import android.os.Handler;
import android.os.Looper;
public class ControlCenterService extends Service {
@@ -73,8 +73,8 @@ public class ControlCenterService extends Service {
mAppConfigUtils = App.getAppConfigUtils(this);
mAppCacheUtils = App.getAppCacheUtils(this);
mNotificationHelper = new NotificationHelper(ControlCenterService.this);
if (mMyServiceConnection == null) {
mMyServiceConnection = new MyServiceConnection();
}
@@ -105,7 +105,7 @@ public class ControlCenterService extends Service {
Intent intent = new Intent(this, MainActivity.class);
notification = helper.showForegroundNotification(intent, getString(R.string.app_name), "Service Running, Click to open app");
startForeground(NotificationHelper.FOREGROUND_NOTIFICATION_ID, notification);
// NotificationMessage notificationMessage=createNotificationMessage();
// //Toast.makeText(getApplication(), "", Toast.LENGTH_SHORT).show();
// mNotificationUtils.createForegroundNotification(this, notificationMessage);
@@ -116,7 +116,7 @@ public class ControlCenterService extends Service {
mControlCenterServiceReceiver = new ControlCenterServiceReceiver(this);
mControlCenterServiceReceiver.registerAction(this);
}
new Handler(Looper.getMainLooper()).postDelayed(new Runnable(){
@Override
@@ -126,7 +126,7 @@ public class ControlCenterService extends Service {
LogUtils.i(TAG, "Service Is Start.");
}
}, 2000);
}
}
@@ -263,15 +263,15 @@ public class ControlCenterService extends Service {
NotificationHelper helper = new NotificationHelper(ControlCenterService.this);
Intent intent = new Intent(ControlCenterService.this, MainActivity.class);
helper.showTemporaryNotification(intent, getString(R.string.app_name), msg);
// NotificationMessage notificationMessage = createNotificationMessage();
// notificationMessage.setRemindMSG(szRemindMSG);
// //LogUtils.d(TAG, "notificationMessage : " + notificationMessage.getRemindMSG());
// updateRemindNotification(notificationMessage);
}
// 设置颜色背景
public static RemoteViews setLinearLayoutColor(RemoteViews remoteViews, int viewId, int color) {
remoteViews.setInt(viewId, "setBackgroundColor", color);

View File

@@ -0,0 +1,37 @@
package cc.winboll.studio.powerbell.unittest;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.fragment.app.Fragment;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.powerbell.R;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/11/19 18:16
* @Describe BackgroundViewTestFragment
*/
public class BackgroundViewTestFragment extends Fragment {
public static final String TAG = "BackgroundViewTestFragment";
View mainView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//super.onCreateView(inflater, container, savedInstanceState);
// 非调试状态就结束本线程
if (!GlobalApplication.isDebugging()) {
Thread.currentThread().destroy();
}
mainView = inflater.inflate(R.layout.fragment_test_backgroundview, container, false);
ToastUtils.show(String.format("%s onCreate", TAG));
return mainView;
}
}

View File

@@ -0,0 +1,39 @@
package cc.winboll.studio.powerbell.unittest;
import android.os.Bundle;
import android.widget.FrameLayout;
import androidx.appcompat.app.AppCompatActivity;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.powerbell.R;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import android.nfc.tech.TagTechnology;
import cc.winboll.studio.libappbase.ToastUtils;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/11/19 18:04
* @Describe 单元测试启动主页窗口
*/
public class MainUnitTestActivity extends AppCompatActivity {
public static final String TAG = "MainUnitTestActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 非调试状态就退出
if (!GlobalApplication.isDebugging()) {
finish();
}
setContentView(R.layout.activity_mainunittest);
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.activitymainunittestFrameLayout1, new BackgroundViewTestFragment(), BackgroundViewTestFragment.TAG);
fragmentTransaction.commit();
ToastUtils.show(String.format("%s onCreate", TAG));
}
}

View 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);
}
}

View File

@@ -0,0 +1,33 @@
package cc.winboll.studio.powerbell.utils;
import android.content.Context;
import android.util.DisplayMetrics;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/11/14 11:14
* @Describe 米盟 MimoUtils
*/
public final class MimoUtils {
public static final String TAG = "Utils";
public static int dpToPx(Context context, float dp) {
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
return (int) (dp * displayMetrics.density + 0.5f);
}
public static int pxToDp(Context context, float px) {
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
return (int) (px / displayMetrics.density + 0.5f);
}
public static int pxToSp(Context context, float pxValue) {
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
return (int) (pxValue / displayMetrics.scaledDensity + 0.5f);
}
public static int spToPx(Context context, float spValue) {
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
return (int) (spValue * displayMetrics.scaledDensity + 0.5f);
}
}

View File

@@ -0,0 +1,240 @@
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.Drawable;
import android.util.AttributeSet;
import android.view.View;
import android.widget.RelativeLayout;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.powerbell.R;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/11/19 18:01
* @Describe 背景图片视图控件(加载外置存储 Background/current.jpg
*/
public class BackgroundView extends RelativeLayout {
public static final String TAG = "BackgroundView";
Context mContext;
View mMianView;
// 外置存储背景图片路径:/storage/emulated/0/Android/data/应用包名/files/Background/current.jpg
private static String BACKGROUND_IMAGE_FOLDER = "Background";
private static String BACKGROUND_IMAGE_FILENAME = "current.data";
private static String backgroundSourceFilePath;
public BackgroundView(Context context) {
super(context);
this.mContext = context;
initView();
}
public BackgroundView(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
initView();
}
public BackgroundView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mContext = context;
initView();
}
public BackgroundView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
this.mContext = context;
initView();
}
void initView() {
// 1. 初始化外置存储背景图片路径(应用私有外置存储,无需动态权限)
initBackgroundImagePath();
// 2. 加载并设置背景图片
loadAndSetBackground();
}
/**
* 初始化外置存储背景图片路径
* 路径:/storage/emulated/0/Android/data/应用包名/files/Background/current.jpg
*/
private void initBackgroundImagePath() {
// 获取应用私有外置存储的 files 目录API 19+ 无需权限)
File externalFilesDir = mContext.getExternalFilesDir(null);
if (externalFilesDir == null) {
LogUtils.e(TAG, "外置存储不可用,无法初始化背景图片路径");
return;
}
// 拼接 Background 目录和 current.jpg 文件路径
File backgroundDir = new File(externalFilesDir, "Background");
if(!backgroundDir.exists()){
backgroundDir.mkdirs();
}
//BACKGROUND_IMAGE_PATH = new File(backgroundDir, "current.jpg").getAbsolutePath();
//BACKGROUND_IMAGE_PATH = new File(backgroundDir, "current.data").getAbsolutePath();
backgroundSourceFilePath = new File(backgroundDir, BACKGROUND_IMAGE_FILENAME).getAbsolutePath();
//LogUtils.d(TAG, "背景图片路径:" + BACKGROUND_IMAGE_PATH);
}
/**
* 拷贝图片文件到背景资源目录
* @param srcBackgroundPath 源文件路径(待拷贝的图片路径)
*/
public void saveToBackgroundSources(String srcBackgroundPath) {
// 1. 初始化目标路径(确保路径有效)
initBackgroundImagePath();
if (backgroundSourceFilePath == null) {
LogUtils.e(TAG, "目标路径初始化失败,无法保存背景图片");
return;
}
// 2. 校验源文件
File srcFile = new File(srcBackgroundPath);
if (!srcFile.exists() || !srcFile.isFile()) {
LogUtils.e(TAG, String.format("源文件不存在或不是文件:%s", srcBackgroundPath));
return;
}
// 3. 创建目标目录(若不存在)
File destFile = new File(backgroundSourceFilePath);
File destDir = destFile.getParentFile();
if (destDir != null && !destDir.exists()) {
boolean isDirCreated = destDir.mkdirs();
if (!isDirCreated) {
LogUtils.e(TAG, "目标目录创建失败:" + destDir.getAbsolutePath());
return;
}
}
// 4. 文件流拷贝Java 7 原生实现,兼容低版本)
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile); // 覆盖已有文件(如需询问用户可在此处添加逻辑)
// 缓冲区拷贝(提升效率,避免频繁 IO 操作)
byte[] buffer = new byte[4096];
int len;
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
fos.flush(); // 强制刷新缓冲区,确保数据完整写入
LogUtils.d(TAG, String.format("文件拷贝成功:%s -> %s", srcBackgroundPath, backgroundSourceFilePath));
} catch (Exception e) {
// 捕获所有异常,避免崩溃
LogUtils.e(TAG, String.format("文件拷贝失败:%s", e.getMessage()), e);
// 拷贝失败时删除目标文件(避免生成损坏文件)
if (destFile.exists()) {
destFile.delete();
LogUtils.d(TAG, "已删除损坏的目标文件");
}
} finally {
// 5. 关闭流Java 7 手动关闭,避免资源泄漏)
if (fis != null) {
try {
fis.close();
} catch (Exception e) {
LogUtils.e(TAG, "输入流关闭失败:" + e.getMessage());
}
}
if (fos != null) {
try {
fos.close();
} catch (Exception e) {
LogUtils.e(TAG, "输出流关闭失败:" + e.getMessage());
}
}
}
}
/**
* 加载外置存储的 current.jpg 并设置为控件背景
* 支持:文件压缩、版本兼容、异常兜底
*/
private void loadAndSetBackground() {
// 校验路径是否有效
if (backgroundSourceFilePath == null) {
setDefaultBackground();
return;
}
File backgroundFile = new File(backgroundSourceFilePath);
// 校验文件是否存在
if (!backgroundFile.exists() || !backgroundFile.isFile()) {
LogUtils.e(TAG, "背景图片不存在:" + backgroundSourceFilePath);
setDefaultBackground();
return;
}
// 3. 压缩加载 Bitmap避免 OOM
Bitmap bitmap = decodeBitmapWithCompress(backgroundFile, 1080, 1920); // 最大宽高限制
if (bitmap == null) {
LogUtils.e(TAG, "图片加载失败,无法解析为 Bitmap");
setDefaultBackground();
return;
}
// 4. 设置为控件背景(兼容 Android 低版本)
Drawable backgroundDrawable = new BitmapDrawable(mContext.getResources(), bitmap);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
this.setBackground(backgroundDrawable);
} else {
this.setBackgroundDrawable(backgroundDrawable);
}
LogUtils.d(TAG, "背景图片加载成功");
}
/**
* 带压缩的 Bitmap 解码(避免大图导致 OOM
* @param file 图片文件
* @param maxWidth 最大宽度限制
* @param maxHeight 最大高度限制
* @return 压缩后的 Bitmapnull 表示失败)
*/
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; // 最小缩放比例为 1
}
// 正式加载图片并压缩
options.inJustDecodeBounds = false;
options.inSampleSize = inSampleSize;
options.inPreferredConfig = Bitmap.Config.RGB_565; // 降低像素格式,减少内存占用
return BitmapFactory.decodeFile(file.getAbsolutePath(), options);
} catch (Exception e) {
LogUtils.e(TAG, "图片压缩加载失败:" + e.getMessage());
return null;
}
}
/**
* 设置默认背景(图片加载失败时兜底)
*/
private void setDefaultBackground() {
// 可替换为项目自定义的默认背景图
this.setBackgroundResource(R.drawable.default_background);
LogUtils.d(TAG, "已设置默认背景");
}
}

View 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>

View File

@@ -21,7 +21,6 @@
android:layout_height="match_parent"
android:id="@+id/activitybackgroundpictureRelativeLayout1"/>
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -80,6 +79,23 @@
android:layout_margin="5dp"
android:id="@+id/activitybackgroundpictureAButton2"/>
<cc.winboll.studio.libaes.views.AButton
android:layout_width="50dp"
android:layout_height="36dp"
android:text="♾"
android:layout_gravity="center_vertical"
android:layout_margin="5dp"
android:id="@+id/activitybackgroundpictureAButton9"
android:onClick="onNetworkBackgroundDialog"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right">
<cc.winboll.studio.libaes.views.AButton
android:layout_width="50dp"
android:layout_height="36dp"
@@ -103,7 +119,7 @@
android:layout_gravity="center_vertical"
android:layout_margin="5dp"
android:id="@+id/activitybackgroundpictureAButton7"/>
<cc.winboll.studio.libaes.views.AButton
android:layout_width="50dp"
android:layout_height="36dp"
@@ -111,11 +127,12 @@
android:layout_gravity="center_vertical"
android:layout_margin="5dp"
android:id="@+id/activitybackgroundpictureAButton8"/>
</LinearLayout>
</LinearLayout>
</RelativeLayout>
</LinearLayout>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@android:color/white">
<!-- 搜索框:提示文本改为“搜索应用名称或包名” -->
<EditText
android:id="@+id/et_search"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:padding="12dp"
android:hint="搜索应用名称或包名"
android:background="@android:drawable/btn_default_small"
android:inputType="text"
android:textSize="16sp"/>
<!-- 应用列表 -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_battery_report"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"/>
</LinearLayout>

View File

@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="电池使用报告"
android:textSize="24sp"
android:fontFamily="sans-serif-medium"
android:layout_marginBottom="16dp"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvBatteryReport"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@drawable/divider_line"
android:dividerHeight="1dp"/>
</LinearLayout>

View File

@@ -6,7 +6,7 @@
android:layout_height="match_parent"
android:orientation="vertical">
<cc.winboll.studio.libaes.views.AToolbar
<cc.winboll.studio.libaes.views.ASupportToolbar
android:layout_width="match_parent"
android:layout_height="@dimen/toolbar_height"
android:id="@+id/toolbar"
@@ -22,7 +22,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/activitymainRelativeLayout1"
android:background="#FFEE2121"/>
android:background="#FFB7B7B7"/>
<FrameLayout
android:layout_width="match_parent"
@@ -31,5 +31,10 @@
</RelativeLayout>
<cc.winboll.studio.libaes.views.ADsBannerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/adsbanner"/>
</LinearLayout>

View File

@@ -0,0 +1,16 @@
<?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">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:id="@+id/activitymainunittestFrameLayout1"
android:layout_weight="1.0"/>
</LinearLayout>

View File

@@ -0,0 +1,92 @@
<?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"/>
<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>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<cc.winboll.studio.powerbell.views.BackgroundView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FF7381FF">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="IT"/>
</cc.winboll.studio.powerbell.views.BackgroundView>

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/action_battery_reporter"
android:title="@string/item_battery_reporter"/>
android:id="@+id/action_battery_report"
android:title="@string/item_battery_report"/>
<item
android:id="@+id/action_clearrecord"
android:title="@string/item_clearrecord"/>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">能源钟</string>
<string name="app_name">PowerBell</string>
<string name="app_description">一个接收手机电量信息的应用,当电量值达到设定范围时会提醒用户。</string>
<string name="about_crashed">本应用崩溃了,作者水平有限,敬请谅解!</string>
<string name="item_mainview">Main View</string>

View File

@@ -25,16 +25,15 @@
<color name="colorShuiDark">#FF0072A4</color>
<color name="colorShuiAccent">#FF33C1FF</color>
<color name="colorXinling">#FFFCC500</color>
<color name="colorPrimary">@color/colorShuiDark</color>
<!--<color name="colorPrimary">@color/colorShuiDark</color>
<color name="colorPrimaryDark">@color/colorFeng</color>
<color name="colorAccent">@color/colorShui</color>
<color name="colorYellow">#FFFFFF00</color>
<color name="colorRed">#FFFF0000</color>
<color name="colorBlue">#FF000FFF</color>
-->
<color name="colorYellow">#FFBEBE48</color>
<color name="colorRed">#FFC85C5C</color>
<color name="colorBlue">#FF2677C7</color>
<color name="colorBlack">#FF000000</color>
<color name="colorUsege">@color/colorHuo</color>
<color name="colorCurrent">@color/colorShui</color>
<color name="colorCharge">@color/colorXinling</color>
<color name="colorText">@color/colorBlack</color>
<!-- 调试配置
<color name="colorYellow">#FF630066</color>
<color name="colorRed">#FF23244D</color>
@@ -47,15 +46,19 @@
<color name="colorBlue">#FF000DE5</color>
<color name="colorBlack">#FF000000</color>
-->
<!-- 原色配置
<color name="colorYellow">#FFFFFF00</color>
<!-- 原色配置 -->
<!--<color name="colorYellow">#FFFFFF00</color>
<color name="colorRed">#FFFF0000</color>
<color name="colorBlue">#FF000FFF</color>
<color name="colorBlack">#FF000000</color>
-->
<color name="colorPrimary">@color/colorShui</color>
<color name="colorPrimaryDark">@color/colorShuiDark</color>
<color name="colorAccent">@color/colorShuiAccent</color>
<color name="colorUsege">@color/colorRed</color>
<color name="colorCurrent">@color/colorBlue</color>
<color name="colorCharge">@color/colorYellow</color>
-->
<!--CustomSlideToUnlockView控件配置-->
<color name="colorCustomSlideToUnlockViewWhite">#FFFFFFFF</color>

View File

@@ -6,7 +6,7 @@
<string name="about_crashed">This application has crashed, the author level is limited, please understand!</string>
<string name="item_mainview">Main View</string>
<string name="item_aboutview">About</string>
<string name="item_battery_reporter">Battery Reporter</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>

View File

@@ -13,6 +13,9 @@
<external-files-path
name="external_file_path"
path="." />
<external-files-path
name="files_root"
path="mimoDownload" />
<!--代表app 外部存储区域根目录下的文件 Context.getExternalCacheDir目录下的目录-->
<external-cache-path
name="external_cache_path"

View File

@@ -4,6 +4,19 @@
<application>
<activity
android:name="cc.winboll.studio.powerbell.MainActivity"
android:launchMode="singleTask"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name="cc.winboll.studio.powerbell.unittest.MainUnitTestActivity"
android:exported="true">
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="cc.winboll.studio.powerbell.fileprovider"