Compare commits
	
		
			16 Commits
		
	
	
		
			apputils-v
			...
			powerbell
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| f943db17e0 | |||
|   | d7a9cb2a20 | ||
| de34c33706 | |||
|   | 10b8da2e21 | ||
|   | ca4e4c7feb | ||
| 4108371c20 | |||
|   | e5c8624d9b | ||
| 561330697b | |||
|   | f7b2c0d4c0 | ||
|   | c3978a1e3c | ||
| 5e198d9c68 | |||
|   | 963a3bb7cd | ||
| e9bb789daa | |||
| dbff19e7f4 | |||
|   | 44679d0c8a | ||
| 6656161903 | 
| @@ -1,8 +1,8 @@ | ||||
| #Created by .winboll/winboll_app_build.gradle | ||||
| #Mon Sep 22 06:01:08 HKT 2025 | ||||
| stageCount=8 | ||||
| #Sat Sep 27 21:03:20 HKT 2025 | ||||
| stageCount=10 | ||||
| libraryProject=libappbase | ||||
| baseVersion=15.10 | ||||
| publishVersion=15.10.7 | ||||
| publishVersion=15.10.9 | ||||
| buildCount=0 | ||||
| baseBetaVersion=15.10.8 | ||||
| baseBetaVersion=15.10.10 | ||||
|   | ||||
| @@ -1,35 +0,0 @@ | ||||
| # APPUtils | ||||
|  | ||||
| #### 介绍 | ||||
| 应用开发工具套件类 | ||||
|  | ||||
| #### 软件架构 | ||||
| 适配安卓应用 [AIDE Pro] 的 Gradle 编译结构。 | ||||
| 也适配安卓应用 [AndroidIDE] 的 Gradle 编译结构。 | ||||
|  | ||||
|  | ||||
| #### Gradle 编译说明 | ||||
| 调试版编译命令 :gradle assembleBetaDebug | ||||
| 阶段版编译命令 :git pull && bash .winboll/bashPublishAPKAddTag.sh apputils | ||||
| 阶段版类库发布命令 :git pull &&bash .winboll/bashPublishLIBAddTag.sh libapputils | ||||
|  | ||||
| #### 使用说明 | ||||
|  | ||||
| #### 参与贡献 | ||||
|  | ||||
| 1.  Fork 本仓库 | ||||
| 2.  新建 Feat_xxx 分支 | ||||
| 3.  提交代码 : ZhanGSKen(ZhanGSKen<zhangsken@188.com>) | ||||
| 4.  新建 Pull Request | ||||
|  | ||||
|  | ||||
| #### 特技 | ||||
|  | ||||
| 1.  使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md | ||||
| 2.  Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) | ||||
| 3.  你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 | ||||
| 4.  [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 | ||||
| 5.  Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) | ||||
| 6.  Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) | ||||
|  | ||||
| #### 参考文档 | ||||
| @@ -29,7 +29,7 @@ android { | ||||
|         // versionName 更新后需要手动设置  | ||||
|         // 项目模块目录的 build.gradle 文件的 stageCount=0 | ||||
|         // Gradle编译环境下合起来的 versionName 就是 "${versionName}.0" | ||||
|         versionName "15.10"  | ||||
|         versionName "15.8"  | ||||
|         if(true) { | ||||
|             versionName = genVersionName("${versionName}") | ||||
|         } | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| #Created by .winboll/winboll_app_build.gradle | ||||
| #Mon Sep 29 00:19:37 HKT 2025 | ||||
| stageCount=2 | ||||
| #Mon Sep 01 07:56:33 HKT 2025 | ||||
| stageCount=7 | ||||
| libraryProject=libapputils | ||||
| baseVersion=15.10 | ||||
| publishVersion=15.10.1 | ||||
| baseVersion=15.8 | ||||
| publishVersion=15.8.6 | ||||
| buildCount=0 | ||||
| baseBetaVersion=15.10.2 | ||||
| baseBetaVersion=15.8.7 | ||||
|   | ||||
| @@ -2,20 +2,14 @@ | ||||
| <manifest | ||||
|     xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     package="cc.winboll.studio.apputils"> | ||||
| 	 | ||||
| 	<!-- 读取外部存储权限(Android 10 以下) --> | ||||
| 	<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> | ||||
| 		 | ||||
|  | ||||
|     <application | ||||
|         android:name=".App" | ||||
|         android:allowBackup="true" | ||||
|         android:icon="@drawable/ic_winboll" | ||||
|         android:label="@string/app_name" | ||||
|         android:theme="@style/MyUtilsTheme" | ||||
|         android:supportsRtl="true" | ||||
|         android:resizeableActivity="true" | ||||
| 		android:screenOrientation="unspecified" | ||||
| 		android:requestLegacyExternalStorage="true"> | ||||
|         android:supportsRtl="true"> | ||||
|  | ||||
|         <activity | ||||
|             android:name=".MainActivity" | ||||
| @@ -41,8 +35,6 @@ | ||||
|  | ||||
|         <activity android:name=".QRCodeDecodeActivity"/> | ||||
|  | ||||
|         <activity android:name=".QRGeneratorActivity"/> | ||||
|  | ||||
|     </application> | ||||
|  | ||||
| </manifest> | ||||
| </manifest> | ||||
| @@ -15,11 +15,12 @@ import android.view.MenuItem; | ||||
| import android.widget.Toolbar; | ||||
| import cc.winboll.studio.apputils.R; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity; | ||||
| import cc.winboll.studio.libapputils.views.SimpleWebView; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
|  | ||||
| public class AssetsHtmlActivity extends Activity { | ||||
| public class AssetsHtmlActivity extends WinBoLLActivity implements IWinBoLLActivity { | ||||
|  | ||||
|     public static final String TAG = "AssetsHtmlActivity"; | ||||
|  | ||||
| @@ -31,6 +32,16 @@ public class AssetsHtmlActivity extends Activity { | ||||
|  | ||||
|     // Assets 文件夹里的 Html 文件的名称 | ||||
|     String mszHtmlFileName; | ||||
|  | ||||
|     @Override | ||||
|     public Activity getActivity() { | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getTag() { | ||||
|         return TAG; | ||||
|     } | ||||
|      | ||||
|     @Override | ||||
|     public boolean onCreateOptionsMenu(Menu menu) { | ||||
|   | ||||
| @@ -15,10 +15,9 @@ import android.widget.Toolbar; | ||||
| import cc.winboll.studio.apputils.R; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| import cc.winboll.studio.libappbase.LogView; | ||||
| import cc.winboll.studio.libappbase.ToastUtils; | ||||
| import cc.winboll.studio.libappbase.utils.ToastUtils; | ||||
| import java.util.List; | ||||
| import java.util.Set; | ||||
| import cc.winboll.studio.libappbase.LogActivity; | ||||
|  | ||||
| final public class MainActivity extends Activity { | ||||
|  | ||||
| @@ -27,21 +26,21 @@ final public class MainActivity extends Activity { | ||||
|     public static final int REQUEST_QRCODEDECODE_ACTIVITY = 0; | ||||
|  | ||||
|     Toolbar mToolbar; | ||||
|     //LogView mLogView; | ||||
|     LogView mLogView; | ||||
| // | ||||
| //    @Override | ||||
| //    public Activity getActivity() { | ||||
| //        return this; | ||||
| //    } | ||||
|  | ||||
|  | ||||
|      | ||||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         setContentView(R.layout.activity_main); | ||||
|  | ||||
| //        mLogView = findViewById(R.id.logview); | ||||
| //        mLogView.start(); | ||||
|         mLogView = findViewById(R.id.logview); | ||||
|         mLogView.start(); | ||||
|  | ||||
|         // 初始化工具栏 | ||||
|         mToolbar = findViewById(R.id.toolbar); | ||||
| @@ -146,21 +145,13 @@ final public class MainActivity extends Activity { | ||||
|     } | ||||
|  | ||||
|     public void onTestLogActivity(View view) { | ||||
| 		/* 分屏代码有效 | ||||
| 		// 1. 创建启动 SecondActivity 的 Intent | ||||
| 		Intent splitIntent = new Intent(MainActivity.this, LogActivity.class); | ||||
| //        Intent intent = new Intent(this, LogActivity.class); | ||||
| //        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); | ||||
| //        intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); | ||||
| //        startActivity(intent); | ||||
|  | ||||
| 		// 2. 添加分屏启动必需的两个标志(API 30 兼容) | ||||
| 		// FLAG_ACTIVITY_LAUNCH_ADJACENT:相邻分屏显示 | ||||
| 		// FLAG_ACTIVITY_NEW_TASK:分屏需要新任务栈 | ||||
| 		splitIntent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT  | ||||
| 							 | Intent.FLAG_ACTIVITY_NEW_TASK); | ||||
|  | ||||
| 		// 3. 启动分屏活动(若设备不支持分屏,会默认全屏启动) | ||||
| 		startActivity(splitIntent); | ||||
| 		*/ | ||||
|  | ||||
| 		LogActivity.startLogActivity(this); | ||||
|         //WinBoLLActivityManager.getInstance().printAvtivityListInfo(); | ||||
|         //WinBoLLActivityManager.getInstance(this).startWinBoLLActivity(this, LogActivity.class); | ||||
|     } | ||||
|  | ||||
|     // | ||||
| @@ -226,9 +217,10 @@ final public class MainActivity extends Activity { | ||||
|         if (item.getItemId() == R.id.item_exit) { | ||||
|             //exit(); | ||||
|             return true; | ||||
|         } else if (item.getItemId() == R.id.item_testqrgeneratoractivity) { | ||||
|             Intent intent = new Intent(this, QRGeneratorActivity.class); | ||||
|             startActivity(intent); | ||||
| //        } else if (item.getItemId() == R.id.item_teststringtoqrcodeview) { | ||||
| //            Intent intent = new Intent(this, TestStringToQRCodeViewActivity.class); | ||||
| //            startActivityForResult(intent, REQUEST_QRCODEDECODE_ACTIVITY); | ||||
| //            //WinBoLLActivityManager.getInstance(this).startWinBoLLActivity(this, TestStringToQrCodeViewActivity.class); | ||||
|         } else if (item.getItemId() == R.id.item_testqrcodedecodeactivity) { | ||||
|             Intent intent = new Intent(this, QRCodeDecodeActivity.class); | ||||
|             startActivityForResult(intent, REQUEST_QRCODEDECODE_ACTIVITY); | ||||
| @@ -276,7 +268,7 @@ final public class MainActivity extends Activity { | ||||
| //        } | ||||
|     } | ||||
|  | ||||
|  | ||||
|      | ||||
|     public void onTestAssetsHtmlActivity(View view) { | ||||
|         Intent intent = new Intent(this, AssetsHtmlActivity.class); | ||||
|         intent.putExtra(AssetsHtmlActivity.EXTRA_HTMLFILENAME, "javascript_test.html"); | ||||
| @@ -289,7 +281,7 @@ final public class MainActivity extends Activity { | ||||
|     @Override | ||||
|     protected void onResume() { | ||||
|         super.onResume(); | ||||
|         //mLogView.start(); | ||||
|         mLogView.start(); | ||||
|     } | ||||
|  | ||||
|     /*@Override | ||||
|   | ||||
| @@ -1,323 +1,89 @@ | ||||
| package cc.winboll.studio.apputils; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen&豆包大模型<zhangsken@qq.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/01/18 10:32:21 | ||||
|  * @Describe 二维码解码窗口 | ||||
|  * @Describe 二维码扫码解码窗口 | ||||
|  */ | ||||
| import android.app.Activity; | ||||
| import android.app.AlertDialog; | ||||
| import android.content.DialogInterface; | ||||
| import android.content.Intent; | ||||
| import android.content.pm.PackageManager; | ||||
| import android.graphics.Bitmap; | ||||
| import android.graphics.BitmapFactory; | ||||
| import android.graphics.Canvas; | ||||
| import android.graphics.ColorMatrix; | ||||
| import android.graphics.ColorMatrixColorFilter; | ||||
| import android.graphics.Paint; | ||||
| import android.net.Uri; | ||||
| import android.os.Bundle; | ||||
| import android.provider.MediaStore; | ||||
| import android.view.View; | ||||
| import android.widget.Button; | ||||
| import android.widget.ScrollView; | ||||
| import android.widget.TextView; | ||||
| import android.widget.Toolbar; | ||||
| import cc.winboll.studio.apputils.R; | ||||
| import com.google.zxing.BarcodeFormat; | ||||
| import com.google.zxing.BinaryBitmap; | ||||
| import com.google.zxing.DecodeHintType; | ||||
| import com.google.zxing.LuminanceSource; | ||||
| import com.google.zxing.MultiFormatReader; | ||||
| import com.google.zxing.NotFoundException; | ||||
| import com.google.zxing.RGBLuminanceSource; | ||||
| import com.google.zxing.Result; | ||||
| import com.google.zxing.ResultPoint; | ||||
| import com.google.zxing.common.HybridBinarizer; | ||||
| import com.journeyapps.barcodescanner.BarcodeCallback; | ||||
| import com.journeyapps.barcodescanner.BarcodeResult; | ||||
| import com.journeyapps.barcodescanner.DecoratedBarcodeView; | ||||
| import com.journeyapps.barcodescanner.DefaultDecoderFactory; | ||||
| import java.io.InputStream; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Hashtable; | ||||
| import java.util.List; | ||||
|  | ||||
| public class QRCodeDecodeActivity extends Activity { | ||||
|  | ||||
|     public static final String TAG = "QRCodeDecodeActivity"; | ||||
|  | ||||
|     public static final String EXTRA_RESULT = "EXTRA_RESULT"; | ||||
|     private static final int REQUEST_CAMERA_PERMISSION = 1; | ||||
|     private static final int REQUEST_PICK_IMAGE = 2; | ||||
|     private static final int REQUEST_READ_STORAGE_PERMISSION = 3; | ||||
|     // 图片压缩阈值:超过1000px时压缩,避免内存溢出和解码效率低 | ||||
|     private static final int MAX_BITMAP_SIZE = 1000; | ||||
|  | ||||
|     private TextView resultTextView; | ||||
|     private DecoratedBarcodeView barcodeView; | ||||
|     private Button btnDecodeFromAlbum; | ||||
|     TextView resultTextView; | ||||
|     DecoratedBarcodeView barcodeView; | ||||
|  | ||||
| //    @Override | ||||
| //    public Activity getActivity() { | ||||
| //        return this; | ||||
| //    } | ||||
|  | ||||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         setContentView(R.layout.activity_qrcodedecode); | ||||
|         initToolbar(); | ||||
|         initViews(); | ||||
|         checkCameraPermission(); | ||||
|         setAlbumDecodeClickListener(); | ||||
|     } | ||||
|  | ||||
|     private void initToolbar() { | ||||
|         Toolbar mToolbar = (Toolbar) findViewById(R.id.toolbar); | ||||
|         if (mToolbar != null) { | ||||
|             setActionBar(mToolbar); | ||||
|             if (getActionBar() != null) { | ||||
|                 getActionBar().setDisplayHomeAsUpEnabled(true); | ||||
|                 mToolbar.setNavigationOnClickListener(new View.OnClickListener() { | ||||
| 						@Override | ||||
| 						public void onClick(View v) { | ||||
| 							finish(); | ||||
| 						} | ||||
| 					}); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|         // 初始化工具栏 | ||||
| //        Toolbar mToolbar = findViewById(R.id.toolbar); | ||||
| //        setActionBar(mToolbar); | ||||
|  | ||||
|     private void initViews() { | ||||
|         resultTextView = (TextView) findViewById(R.id.activityqrcodedecodeTextView1); | ||||
|         barcodeView = (DecoratedBarcodeView) findViewById(R.id.activityqrcodedecodeDecoratedBarcodeView1); | ||||
|         btnDecodeFromAlbum = (Button) findViewById(R.id.btn_decode_from_album); | ||||
|         // 初始化扫码解码器(支持所有常见码制,避免仅支持QR_CODE的局限) | ||||
|         List<BarcodeFormat> formats = new ArrayList<BarcodeFormat>(); | ||||
|         formats.add(BarcodeFormat.QR_CODE); | ||||
|         formats.add(BarcodeFormat.CODE_128); | ||||
|         formats.add(BarcodeFormat.EAN_13); | ||||
|         barcodeView.getBarcodeView().setDecoderFactory(new DefaultDecoderFactory(formats)); | ||||
|     } | ||||
|  | ||||
|     private void setAlbumDecodeClickListener() { | ||||
|         btnDecodeFromAlbum.setOnClickListener(new View.OnClickListener() { | ||||
| 				@Override | ||||
| 				public void onClick(View v) { | ||||
| 					if (checkSelfPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) | ||||
|                         != PackageManager.PERMISSION_GRANTED) { | ||||
| 						requestPermissions(new String[]{android.Manifest.permission.READ_EXTERNAL_STORAGE}, | ||||
| 										   REQUEST_READ_STORAGE_PERMISSION); | ||||
| 					} else { | ||||
| 						openAlbum(); | ||||
| 					} | ||||
| 				} | ||||
| 			}); | ||||
|     } | ||||
|  | ||||
|     private void openAlbum() { | ||||
|         Intent pickImageIntent = new Intent(Intent.ACTION_PICK); | ||||
|         pickImageIntent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*"); | ||||
|         startActivityForResult(pickImageIntent, REQUEST_PICK_IMAGE); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onActivityResult(int requestCode, int resultCode, Intent data) { | ||||
|         super.onActivityResult(requestCode, resultCode, data); | ||||
|         if (requestCode == REQUEST_PICK_IMAGE && resultCode == RESULT_OK && data != null) { | ||||
|             Uri selectedImageUri = data.getData(); | ||||
|             if (selectedImageUri != null) { | ||||
|                 try { | ||||
|                     // 1. 读取图片并压缩(关键优化:避免大图片解码失败) | ||||
|                     InputStream imageStream = getContentResolver().openInputStream(selectedImageUri); | ||||
|                     BitmapFactory.Options options = new BitmapFactory.Options(); | ||||
|                     options.inJustDecodeBounds = true; // 先获取图片尺寸,不加载像素 | ||||
|                     BitmapFactory.decodeStream(imageStream, null, options); | ||||
|                     imageStream.close(); // 关闭流,重新读取 | ||||
|  | ||||
|                     // 计算压缩比例:超过MAX_BITMAP_SIZE时按比例压缩 | ||||
|                     options.inSampleSize = calculateInSampleSize(options, MAX_BITMAP_SIZE, MAX_BITMAP_SIZE); | ||||
|                     options.inJustDecodeBounds = false; // 开始加载压缩后的像素 | ||||
|                     imageStream = getContentResolver().openInputStream(selectedImageUri); | ||||
|                     Bitmap originalBitmap = BitmapFactory.decodeStream(imageStream, null, options); | ||||
|                     imageStream.close(); | ||||
|  | ||||
|                     if (originalBitmap == null) { | ||||
|                         showToast("图片损坏,无法解析"); | ||||
|                         return; | ||||
|                     } | ||||
|  | ||||
|                     // 2. 图片预处理:转为灰度图+提高对比度(解决模糊/低对比度图片识别问题) | ||||
|                     Bitmap processedBitmap = processBitmap(originalBitmap); | ||||
|  | ||||
|                     // 3. 解码预处理后的图片 | ||||
|                     String decodeResult = decodeQrFromBitmap(processedBitmap); | ||||
|  | ||||
|                     // 4. 结果处理 | ||||
|                     if (decodeResult != null && !decodeResult.isEmpty()) { | ||||
|                         resultTextView.setText("图片解码结果:" + decodeResult); | ||||
|                         showDecodeResultDialog(decodeResult); | ||||
|                         returnResultToPreviousPage(decodeResult); | ||||
|                     } else { | ||||
|                         // 尝试直接解码原图(防止预处理过度导致识别失败) | ||||
|                         String originalResult = decodeQrFromBitmap(originalBitmap); | ||||
|                         if (originalResult != null && !originalResult.isEmpty()) { | ||||
|                             resultTextView.setText("图片解码结果:" + originalResult); | ||||
|                             showDecodeResultDialog(originalResult); | ||||
|                             returnResultToPreviousPage(originalResult); | ||||
|                         } else { | ||||
|                             new AlertDialog.Builder(this) | ||||
| 								.setTitle("解码失败") | ||||
| 								.setMessage("图片中未识别到二维码/条码,建议选择清晰、完整的图片") | ||||
| 								.setPositiveButton("确定", new DialogInterface.OnClickListener() { | ||||
| 									@Override | ||||
| 									public void onClick(DialogInterface dialog, int which) { | ||||
| 										dialog.dismiss(); | ||||
| 									} | ||||
| 								}) | ||||
| 								.show(); | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                     // 回收Bitmap,避免内存泄漏 | ||||
|                     if (!originalBitmap.isRecycled()) originalBitmap.recycle(); | ||||
|                     if (!processedBitmap.isRecycled() && processedBitmap != originalBitmap) { | ||||
|                         processedBitmap.recycle(); | ||||
|                     } | ||||
|  | ||||
|                 } catch (Exception e) { | ||||
|                     e.printStackTrace(); | ||||
|                     showToast("图片处理失败:" + e.getMessage()); | ||||
|                 } | ||||
|             } else { | ||||
|                 showToast("未选择图片"); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 核心优化1:计算图片压缩比例 | ||||
|      */ | ||||
|     private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { | ||||
|         final int height = options.outHeight; | ||||
|         final int width = options.outWidth; | ||||
|         int inSampleSize = 1; | ||||
|  | ||||
|         if (height > reqHeight || width > reqWidth) { | ||||
|             final int halfHeight = height / 2; | ||||
|             final int halfWidth = width / 2; | ||||
|             // 找到最接近reqWidth/reqHeight的压缩比例(2的倍数,保证图片质量) | ||||
|             while ((halfHeight / inSampleSize) >= reqHeight | ||||
| 				   && (halfWidth / inSampleSize) >= reqWidth) { | ||||
|                 inSampleSize *= 2; | ||||
|             } | ||||
|         } | ||||
|         return inSampleSize; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 核心优化2:图片预处理(灰度化+提高对比度) | ||||
|      * 解决模糊、低亮度、低对比度图片识别率低的问题 | ||||
|      */ | ||||
|     private Bitmap processBitmap(Bitmap bitmap) { | ||||
|         int width = bitmap.getWidth(); | ||||
|         int height = bitmap.getHeight(); | ||||
|  | ||||
|         // 创建灰度图 | ||||
|         Bitmap grayBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); | ||||
|         Canvas canvas = new Canvas(grayBitmap); | ||||
|         Paint paint = new Paint(); | ||||
|  | ||||
|         // 1. 灰度化矩阵 | ||||
|         ColorMatrix grayMatrix = new ColorMatrix(); | ||||
|         grayMatrix.setSaturation(0); // 饱和度设为0,转为灰度 | ||||
|  | ||||
|         // 2. 提高对比度矩阵(alpha=1.5,亮度=0,可根据需求调整) | ||||
|         ColorMatrix contrastMatrix = new ColorMatrix(); | ||||
|         contrastMatrix.set(new float[]{ | ||||
| 							   1.5f, 0, 0, 0, 0,  // 红通道对比度 | ||||
| 							   0, 1.5f, 0, 0, 0,  // 绿通道对比度 | ||||
| 							   0, 0, 1.5f, 0, 0,  // 蓝通道对比度 | ||||
| 							   0, 0, 0, 1f, 0     // alpha通道不变 | ||||
| 						   }); | ||||
|  | ||||
|         // 合并灰度+对比度矩阵 | ||||
|         ColorMatrix combinedMatrix = new ColorMatrix(); | ||||
|         combinedMatrix.postConcat(grayMatrix); | ||||
|         combinedMatrix.postConcat(contrastMatrix); | ||||
|  | ||||
|         paint.setColorFilter(new ColorMatrixColorFilter(combinedMatrix)); | ||||
|         canvas.drawBitmap(bitmap, 0, 0, paint); | ||||
|  | ||||
|         return grayBitmap; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 核心优化3:修复解码参数,支持更多场景 | ||||
|      */ | ||||
|     private String decodeQrFromBitmap(Bitmap bitmap) { | ||||
|         if (bitmap == null) return null; | ||||
|  | ||||
|         try { | ||||
|             int width = bitmap.getWidth(); | ||||
|             int height = bitmap.getHeight(); | ||||
|             int[] pixels = new int[width * height]; | ||||
|             bitmap.getPixels(pixels, 0, width, 0, 0, width, height); | ||||
|  | ||||
|             // 修复1:使用RGBLuminanceSource,避免YUV格式导致的颜色偏差 | ||||
|             LuminanceSource source = new RGBLuminanceSource(width, height, pixels); | ||||
|             BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(source)); | ||||
|  | ||||
|             // 修复2:完善解码参数,解决模糊、变形二维码识别问题 | ||||
|             Hashtable<DecodeHintType, Object> hints = new Hashtable<DecodeHintType, Object>(); | ||||
|             // 支持所有常见码制(不仅限于QR_CODE) | ||||
|             List<BarcodeFormat> formats = new ArrayList<BarcodeFormat>(); | ||||
|             formats.add(BarcodeFormat.QR_CODE); | ||||
|             formats.add(BarcodeFormat.CODE_128); | ||||
|             formats.add(BarcodeFormat.EAN_8); | ||||
|             formats.add(BarcodeFormat.EAN_13); | ||||
|             hints.put(DecodeHintType.POSSIBLE_FORMATS, formats); | ||||
|             // 字符编码:支持中文等多语言 | ||||
|             hints.put(DecodeHintType.CHARACTER_SET, "UTF-8"); | ||||
|             // 容错模式:允许二维码有一定损坏(关键!解决轻微变形/污染的二维码) | ||||
|             hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE); | ||||
|             // 不使用纯条码模式,兼容带logo的二维码 | ||||
|             hints.put(DecodeHintType.PURE_BARCODE, Boolean.FALSE); | ||||
|  | ||||
|             MultiFormatReader reader = new MultiFormatReader(); | ||||
|             reader.setHints(hints); | ||||
|             Result result = reader.decode(binaryBitmap); | ||||
|             return result.getText(); | ||||
|  | ||||
|         } catch (NotFoundException e) { | ||||
|             // 正常未识别到,不打印异常(避免日志冗余) | ||||
|             return null; | ||||
|         } catch (Exception e) { | ||||
|             e.printStackTrace(); | ||||
|             return null; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // ==================== 原有逻辑(不变) ==================== | ||||
|     private void checkCameraPermission() { | ||||
|         if (checkSelfPermission(android.Manifest.permission.CAMERA) | ||||
| 			!= PackageManager.PERMISSION_GRANTED) { | ||||
|             requestPermissions(new String[]{android.Manifest.permission.CAMERA}, | ||||
| 							   REQUEST_CAMERA_PERMISSION); | ||||
|         } else { | ||||
|             startScanning(); | ||||
|         } | ||||
|         //resultTextView = findViewById(R.id.activityqrcodedecodeTextView1); | ||||
|         barcodeView = findViewById(R.id.activityqrcodedecodeDecoratedBarcodeView1); | ||||
|         // 请求相机权限 | ||||
| //        if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA) | ||||
| //            != PackageManager.PERMISSION_GRANTED) { | ||||
| //            ActivityCompat.requestPermissions(this, | ||||
| //                                              new String[]{android.Manifest.permission.CAMERA}, | ||||
| //                                              REQUEST_CAMERA_PERMISSION); | ||||
| //        } else { | ||||
| //            startScanning(); | ||||
| //        } | ||||
|         startScanning(); | ||||
|     } | ||||
|  | ||||
|     private void startScanning() { | ||||
|         barcodeView.getBarcodeView().setDecoderFactory(null); | ||||
|         barcodeView.decodeContinuous(barcodeCallback); | ||||
|     } | ||||
|  | ||||
|     private BarcodeCallback barcodeCallback = new BarcodeCallback() { | ||||
|     @Override | ||||
|     public void onRequestPermissionsResult(int requestCode, | ||||
|                                            String[] permissions, int[] grantResults) { | ||||
|         if (requestCode == REQUEST_CAMERA_PERMISSION) { | ||||
|             if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { | ||||
|                 startScanning(); | ||||
|             } else { | ||||
|                 // 权限被拒绝的处理 | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     BarcodeCallback barcodeCallback = new BarcodeCallback(){ | ||||
|         @Override | ||||
|         public void barcodeResult(BarcodeResult result) { | ||||
|             if (result != null && result.getText() != null) { | ||||
|             if (result.getText() != null) { | ||||
|                 //Toast.makeText(MainActivity.this, "Scanned: " + result.getText(), Toast.LENGTH_SHORT).show(); | ||||
|                 //ToastUtils.show("Scanned: " + result.getText()); | ||||
|                 barcodeView.pause(); | ||||
|                 String decodeResult = result.getText(); | ||||
|                 resultTextView.setText("扫码结果:" + decodeResult); | ||||
|                 showDecodeResultDialog(decodeResult); | ||||
|                 returnResultToPreviousPage(decodeResult); | ||||
|                 Intent intent = new Intent(); | ||||
|                 intent.putExtra(EXTRA_RESULT, result.getText()); | ||||
|                 setResult(RESULT_OK, intent); | ||||
|                 finish(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @@ -326,111 +92,16 @@ public class QRCodeDecodeActivity extends Activity { | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     private void showDecodeResultDialog(String result) { | ||||
|         ScrollView scrollView = new ScrollView(this); | ||||
|         scrollView.setPadding(dip2px(16), dip2px(16), dip2px(16), dip2px(16)); | ||||
|  | ||||
|         TextView dialogTv = new TextView(this); | ||||
|         dialogTv.setTextSize(16); | ||||
|         dialogTv.setTextColor(getResources().getColor(android.R.color.black)); | ||||
|         dialogTv.setText(result); | ||||
|         scrollView.addView(dialogTv); | ||||
|  | ||||
|         AlertDialog.Builder builder = new AlertDialog.Builder(this); | ||||
|         builder.setTitle("解码结果"); | ||||
|         builder.setView(scrollView); | ||||
|         builder.setPositiveButton("确定", new DialogInterface.OnClickListener() { | ||||
| 				@Override | ||||
| 				public void onClick(DialogInterface dialog, int which) { | ||||
| 					dialog.dismiss(); | ||||
| 					barcodeView.resume(); | ||||
| 				} | ||||
| 			}); | ||||
|         builder.setCancelable(false); | ||||
|         builder.show(); | ||||
|     } | ||||
|  | ||||
|     private void returnResultToPreviousPage(String result) { | ||||
|         Intent intent = new Intent(); | ||||
|         intent.putExtra(EXTRA_RESULT, result); | ||||
|         setResult(RESULT_OK, intent); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { | ||||
|         super.onRequestPermissionsResult(requestCode, permissions, grantResults); | ||||
|         if (requestCode == REQUEST_CAMERA_PERMISSION) { | ||||
|             if (grantResults != null && grantResults.length > 0) { | ||||
|                 if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { | ||||
|                     startScanning(); | ||||
|                 } else { | ||||
|                     new AlertDialog.Builder(this) | ||||
| 						.setTitle("权限申请") | ||||
| 						.setMessage("扫码需要相机权限,请在设置中开启") | ||||
| 						.setPositiveButton("确定", new DialogInterface.OnClickListener() { | ||||
| 							@Override | ||||
| 							public void onClick(DialogInterface dialog, int which) { | ||||
| 								dialog.dismiss(); | ||||
| 								finish(); | ||||
| 							} | ||||
| 						}) | ||||
| 						.setCancelable(false) | ||||
| 						.show(); | ||||
|                 } | ||||
|             } | ||||
|         } else if (requestCode == REQUEST_READ_STORAGE_PERMISSION) { | ||||
|             if (grantResults != null && grantResults.length > 0) { | ||||
|                 if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { | ||||
|                     openAlbum(); | ||||
|                 } else { | ||||
|                     new AlertDialog.Builder(this) | ||||
| 						.setTitle("权限申请") | ||||
| 						.setMessage("从相册解码需要存储权限,请在设置中开启") | ||||
| 						.setPositiveButton("确定", new DialogInterface.OnClickListener() { | ||||
| 							@Override | ||||
| 							public void onClick(DialogInterface dialog, int which) { | ||||
| 								dialog.dismiss(); | ||||
| 							} | ||||
| 						}) | ||||
| 						.setCancelable(false) | ||||
| 						.show(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onResume() { | ||||
|         super.onResume(); | ||||
|         if (barcodeView != null) { | ||||
|             barcodeView.resume(); | ||||
|         } | ||||
|         barcodeView.resume(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onPause() { | ||||
|         super.onPause(); | ||||
|         if (barcodeView != null) { | ||||
|             barcodeView.pause(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private int dip2px(float dpValue) { | ||||
|         final float scale = getResources().getDisplayMetrics().density; | ||||
|         return (int) (dpValue * scale + 0.5f); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean onOptionsItemSelected(android.view.MenuItem item) { | ||||
|         if (item.getItemId() == android.R.id.home) { | ||||
|             finish(); | ||||
|             return true; | ||||
|         } | ||||
|         return super.onOptionsItemSelected(item); | ||||
|     } | ||||
|  | ||||
|     private void showToast(String message) { | ||||
|         android.widget.Toast.makeText(this, message, android.widget.Toast.LENGTH_SHORT).show(); | ||||
|         barcodeView.pause(); | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,97 +0,0 @@ | ||||
| package cc.winboll.studio.apputils; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen&豆包大模型<zhangsken@qq.com> | ||||
|  * @Date 2025/09/22 07:09 | ||||
|  * @Describe 二维码生成窗口 | ||||
|  */ | ||||
| import android.app.Activity; | ||||
| import android.graphics.Bitmap; | ||||
| import android.os.Bundle; | ||||
| import android.view.View; | ||||
| import android.widget.Button; | ||||
| import android.widget.EditText; | ||||
| import android.widget.ImageView; | ||||
| import android.widget.Toast; | ||||
| import com.google.zxing.BarcodeFormat; | ||||
| import com.google.zxing.WriterException; | ||||
| import com.journeyapps.barcodescanner.BarcodeEncoder; | ||||
|  | ||||
| public class QRGeneratorActivity extends Activity { | ||||
| 	public static final String TAG = "QrGeneratorActivity"; | ||||
|  | ||||
| 	// 控件引用 | ||||
| 	private EditText etInputText; | ||||
| 	private ImageView ivQrPreview; | ||||
| 	private Button btnGenerateQr; | ||||
|  | ||||
| 	@Override | ||||
| 	protected void onCreate(Bundle savedInstanceState) { | ||||
| 		super.onCreate(savedInstanceState); | ||||
| 		setContentView(R.layout.activity_qrgenerator); | ||||
|  | ||||
| 		// 初始化控件 | ||||
| 		initViews(); | ||||
| 		// 设置按钮点击事件 | ||||
| 		setGenerateClickListener(); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 初始化布局控件 | ||||
| 	 */ | ||||
| 	private void initViews() { | ||||
| 		etInputText = findViewById(R.id.et_input_text); | ||||
| 		ivQrPreview = findViewById(R.id.iv_qr_preview); | ||||
| 		btnGenerateQr = findViewById(R.id.btn_generate_qr); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 设置生成按钮点击事件:获取输入文字 → 生成二维码 → 显示到 ImageView | ||||
| 	 */ | ||||
| 	private void setGenerateClickListener() { | ||||
| 		btnGenerateQr.setOnClickListener(new View.OnClickListener() { | ||||
| 				@Override | ||||
| 				public void onClick(View v) { | ||||
| 					// 1. 获取输入框文字(去除前后空格) | ||||
| 					String inputText = etInputText.getText().toString().trim(); | ||||
|  | ||||
| 					// 2. 空输入判断 | ||||
| 					if (inputText.isEmpty()) { | ||||
| 						Toast.makeText(QRGeneratorActivity.this, "请先输入要生成二维码的文字", Toast.LENGTH_SHORT).show(); | ||||
| 						return; | ||||
| 					} | ||||
|  | ||||
| 					// 3. 生成二维码 Bitmap(宽高 500px,可调整) | ||||
| 					Bitmap qrBitmap = generateQrCodeBitmap(inputText, 500, 500); | ||||
|  | ||||
| 					// 4. 显示二维码到 ImageView | ||||
| 					if (qrBitmap != null) { | ||||
| 						ivQrPreview.setImageBitmap(qrBitmap); | ||||
| 					} else { | ||||
| 						Toast.makeText(QRGeneratorActivity.this, "二维码生成失败,请重试", Toast.LENGTH_SHORT).show(); | ||||
| 					} | ||||
| 				} | ||||
| 			}); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 核心方法:生成二维码 Bitmap | ||||
| 	 * @param content 二维码内容(输入的文字) | ||||
| 	 * @param width 二维码宽度(px) | ||||
| 	 * @param height 二维码高度(px) | ||||
| 	 * @return 生成的二维码 Bitmap,失败返回 null | ||||
| 	 */ | ||||
| 	private Bitmap generateQrCodeBitmap(String content, int width, int height) { | ||||
| 		try { | ||||
| 			// 初始化二维码编码器(指定格式为 QR_CODE) | ||||
| 			BarcodeEncoder encoder = new BarcodeEncoder(); | ||||
| 			// 生成二维码 Bitmap(参数:内容、格式、宽、高) | ||||
| 			return encoder.encodeBitmap(content, BarcodeFormat.QR_CODE, width, height); | ||||
| 		} catch (WriterException e) { | ||||
| 			// 生成失败(如内容过长、宽高非法),打印异常信息 | ||||
| 			e.printStackTrace(); | ||||
| 			return null; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -6,9 +6,19 @@ package cc.winboll.studio.apputils; | ||||
|  * @Describe WinBoLLActivity | ||||
|  */ | ||||
| import android.app.Activity; | ||||
| import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity; | ||||
|  | ||||
| public class WinBoLLActivity extends Activity { | ||||
| public class WinBoLLActivity extends Activity implements IWinBoLLActivity { | ||||
|  | ||||
|     public static final String TAG = "WinBoLLActivity"; | ||||
|  | ||||
|     @Override | ||||
|     public Activity getActivity() { | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getTag() { | ||||
|         return TAG; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -54,6 +54,13 @@ | ||||
|  | ||||
| 		</LinearLayout> | ||||
|  | ||||
| 		<cc.winboll.studio.libappbase.LogView | ||||
| 			android:layout_width="match_parent" | ||||
| 			android:layout_height="0dp" | ||||
| 			android:text="Button" | ||||
| 			android:id="@+id/logview" | ||||
| 			android:layout_weight="1.0"/> | ||||
|  | ||||
| 	</LinearLayout> | ||||
|  | ||||
| </LinearLayout> | ||||
|   | ||||
| @@ -1,43 +1,20 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <LinearLayout  | ||||
| <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" | ||||
|     android:orientation="vertical"> | ||||
|     android:layout_height="match_parent"> | ||||
|  | ||||
|     <!-- 工具栏(原有) --> | ||||
|     <Toolbar | ||||
|         android:id="@+id/toolbar" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content"/> | ||||
|  | ||||
|     <!-- 扫码控件(原有) --> | ||||
|     <com.journeyapps.barcodescanner.DecoratedBarcodeView | ||||
|         android:id="@+id/activityqrcodedecodeDecoratedBarcodeView1" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="0dp" | ||||
|         android:layout_weight="1"/> | ||||
|  | ||||
|     <!-- 新增:从相册解码按钮 --> | ||||
|     <Button | ||||
|         android:id="@+id/btn_decode_from_album" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:text="从相册选图解码" | ||||
|         android:textSize="16sp" | ||||
|         android:textColor="@android:color/white" | ||||
|         android:background="#2196F3" | ||||
|         android:paddingVertical="12dp" | ||||
|         android:layout_margin="16dp"/> | ||||
|  | ||||
|     <!-- 结果显示TextView(原有) --> | ||||
|     <TextView | ||||
|         android:id="@+id/activityqrcodedecodeTextView1" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:padding="16dp" | ||||
|         android:text="等待扫码或选择图片..." | ||||
|         android:textSize="16sp"/> | ||||
|         android:text="QRCodeDecodeActivity"/> | ||||
|  | ||||
|     <com.journeyapps.barcodescanner.DecoratedBarcodeView | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:id="@+id/activityqrcodedecodeDecoratedBarcodeView1"/> | ||||
|  | ||||
| </LinearLayout> | ||||
|  | ||||
|   | ||||
| @@ -1,45 +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="20dp" | ||||
|     android:gravity="top|center_horizontal"> | ||||
|  | ||||
|     <!-- 文字输入框 --> | ||||
|     <EditText | ||||
|         android:id="@+id/et_input_text" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:hint="请输入要生成二维码的文字" | ||||
|         android:inputType="textMultiLine" | ||||
|         android:minLines="3" | ||||
|         android:maxLines="5" | ||||
|         android:padding="12dp" | ||||
|         android:background="@android:drawable/editbox_background_normal" | ||||
|         android:layout_marginBottom="16dp"/> | ||||
|  | ||||
|     <!-- 生成二维码按钮 --> | ||||
|     <Button | ||||
|         android:id="@+id/btn_generate_qr" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:text="生成二维码" | ||||
|         android:textSize="16sp" | ||||
|         android:background="#4CAF50" | ||||
|         android:textColor="@android:color/white" | ||||
|         android:paddingVertical="12dp" | ||||
|         android:layout_marginBottom="24dp"/> | ||||
|  | ||||
|     <!-- 二维码预览图片 --> | ||||
|     <ImageView | ||||
|         android:id="@+id/iv_qr_preview" | ||||
|         android:layout_width="280dp" | ||||
|         android:layout_height="280dp" | ||||
|         android:scaleType="fitCenter" | ||||
|         android:background="@android:drawable/dialog_holo_light_frame" | ||||
|         android:contentDescription="二维码预览"/> | ||||
|  | ||||
| </LinearLayout> | ||||
|  | ||||
| @@ -2,8 +2,8 @@ | ||||
| <menu xmlns:android="http://schemas.android.com/apk/res/android" | ||||
| 	xmlns:app="http://schemas.android.com/apk/res-auto"> | ||||
|     <item | ||||
|         android:id="@+id/item_testqrgeneratoractivity" | ||||
|         android:title="TestQRGeneratorActivity"/> | ||||
|         android:id="@+id/item_teststringtoqrcodeview" | ||||
|         android:title="TestStringToQRCodeViewActivity"/> | ||||
|     <item | ||||
|         android:id="@+id/item_testqrcodedecodeactivity" | ||||
|         android:title="TestQRCodeDecodeActivity"/> | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| #Created by .winboll/winboll_app_build.gradle | ||||
| #Mon Sep 22 02:57:06 HKT 2025 | ||||
| stageCount=8 | ||||
| #Sat Sep 27 21:03:08 HKT 2025 | ||||
| stageCount=10 | ||||
| libraryProject=libappbase | ||||
| baseVersion=15.10 | ||||
| publishVersion=15.10.7 | ||||
| publishVersion=15.10.9 | ||||
| buildCount=0 | ||||
| baseBetaVersion=15.10.8 | ||||
| baseBetaVersion=15.10.10 | ||||
|   | ||||
| @@ -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() { | ||||
|   | ||||
| @@ -21,7 +21,7 @@ android { | ||||
|  | ||||
| dependencies { | ||||
|     api fileTree(dir: 'libs', include: ['*.jar']) | ||||
|     api 'cc.winboll.studio:libappbase:15.10.7' | ||||
|     api 'cc.winboll.studio:libappbase:15.9.5' | ||||
|      | ||||
|     // 二维码类库 | ||||
|     api 'com.google.zxing:core:3.4.1' | ||||
| @@ -32,8 +32,6 @@ dependencies { | ||||
|      | ||||
|     // Html 解析 | ||||
|     api 'org.jsoup:jsoup:1.13.1' | ||||
| 	 | ||||
| 	api 'com.google.code.gson:gson:2.10.1' | ||||
|      | ||||
|     // SSH | ||||
|     //api 'com.jcraft:jsch:0.1.55' | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| #Created by .winboll/winboll_app_build.gradle | ||||
| #Mon Sep 29 00:19:37 HKT 2025 | ||||
| stageCount=2 | ||||
| #Mon Sep 01 07:56:11 HKT 2025 | ||||
| stageCount=7 | ||||
| libraryProject=libapputils | ||||
| baseVersion=15.10 | ||||
| publishVersion=15.10.1 | ||||
| baseVersion=15.8 | ||||
| publishVersion=15.8.6 | ||||
| buildCount=0 | ||||
| baseBetaVersion=15.10.2 | ||||
| baseBetaVersion=15.8.7 | ||||
|   | ||||
| @@ -1,29 +0,0 @@ | ||||
| package cc.winboll.studio.libapputils.utils; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/02/15 20:05:03 | ||||
|  * @Describe AppUtils | ||||
|  */ | ||||
| import android.content.Context; | ||||
| import android.content.pm.ApplicationInfo; | ||||
| import android.content.pm.PackageManager; | ||||
| import android.content.pm.PackageManager.NameNotFoundException; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
|  | ||||
| public class AppUtils { | ||||
|      | ||||
|     public static final String TAG = "AppUtils"; | ||||
|      | ||||
|     public static String getAppNameByPackageName(Context context, String packageName) { | ||||
|         PackageManager packageManager = context.getPackageManager(); | ||||
|         try { | ||||
|             ApplicationInfo applicationInfo = packageManager.getApplicationInfo(packageName, 0); | ||||
|             return (String) packageManager.getApplicationLabel(applicationInfo); | ||||
|         } catch (NameNotFoundException e) { | ||||
|             LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|             return ""; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -9,16 +9,10 @@ import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.content.res.AssetManager; | ||||
| import android.net.Uri; | ||||
| import android.support.v4.content.FileProvider; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| import java.io.BufferedReader; | ||||
| import java.io.BufferedWriter; | ||||
| import java.io.ByteArrayOutputStream; | ||||
| import java.io.File; | ||||
| import java.io.FileInputStream; | ||||
| import java.io.FileOutputStream; | ||||
| import java.io.FileReader; | ||||
| import java.io.FileWriter; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.io.InputStreamReader; | ||||
| @@ -28,6 +22,7 @@ import java.nio.charset.StandardCharsets; | ||||
| import java.nio.file.Files; | ||||
| import java.nio.file.Path; | ||||
| import java.nio.file.Paths; | ||||
| import android.support.v4.content.FileProvider; | ||||
|  | ||||
| public class FileUtils { | ||||
|  | ||||
| @@ -102,6 +97,36 @@ public class FileUtils { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     // 把字符串写入文件,指定 UTF-8 编码 | ||||
|     // | ||||
|     public static void writeStringToFile(String szFilePath, String szContent) throws IOException { | ||||
|         File file = new File(szFilePath); | ||||
|         if (!file.getParentFile().exists()) { | ||||
|             file.getParentFile().mkdirs(); | ||||
|         } | ||||
|         FileOutputStream outputStream = new FileOutputStream(file); | ||||
|         OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8); | ||||
|         writer.write(szContent); | ||||
|         writer.close(); | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     // 读取文件到字符串,指定 UTF-8 编码 | ||||
|     // | ||||
|     public static String readStringFromFile(String szFilePath) throws IOException { | ||||
|         File file = new File(szFilePath); | ||||
|         FileInputStream inputStream = new FileInputStream(file); | ||||
|         InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8); | ||||
|         StringBuilder content = new StringBuilder(); | ||||
|         int character; | ||||
|         while ((character = reader.read()) != -1) { | ||||
|             content.append((char) character); | ||||
|         } | ||||
|         reader.close(); | ||||
|         return content.toString(); | ||||
|     } | ||||
|  | ||||
|     public static boolean copyFile(File srcFile, File dstFile) { | ||||
|         if (!srcFile.exists()) { | ||||
|             LogUtils.d(TAG, "The original file does not exist."); | ||||
| @@ -129,113 +154,4 @@ public class FileUtils { | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
| 	 | ||||
|  | ||||
|     /** | ||||
|      * 读取文件为字节数组(Java 7 语法) | ||||
|      */ | ||||
|     public static byte[] readByteArrayFromFile(String filePath) { | ||||
|         FileInputStream fis = null; | ||||
|         ByteArrayOutputStream bos = null; | ||||
|         try { | ||||
|             fis = new FileInputStream(filePath); | ||||
|             bos = new ByteArrayOutputStream(); | ||||
|             byte[] buffer = new byte[4096]; | ||||
|             int bytesRead; | ||||
|             while ((bytesRead = fis.read(buffer)) != -1) { | ||||
|                 bos.write(buffer, 0, bytesRead); | ||||
|             } | ||||
|             return bos.toByteArray(); | ||||
|         } catch (IOException e) { | ||||
|             e.printStackTrace(); | ||||
|             return null; | ||||
|         } finally { | ||||
|             // 手动关闭流(Java 7 不支持 try-with-resources) | ||||
|             if (fis != null) { | ||||
|                 try { | ||||
|                     fis.close(); | ||||
|                 } catch (IOException e) { | ||||
|                     e.printStackTrace(); | ||||
|                 } | ||||
|             } | ||||
|             if (bos != null) { | ||||
|                 try { | ||||
|                     bos.close(); | ||||
|                 } catch (IOException e) { | ||||
|                     e.printStackTrace(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 写入字节数组到文件(Java 7 语法) | ||||
|      */ | ||||
|     public static boolean writeByteArrayToFile(byte[] data, String filePath) { | ||||
|         FileOutputStream fos = null; | ||||
|         try { | ||||
|             fos = new FileOutputStream(filePath); | ||||
|             fos.write(data); | ||||
|             return true; | ||||
|         } catch (IOException e) { | ||||
|             e.printStackTrace(); | ||||
|             return false; | ||||
|         } finally { | ||||
|             if (fos != null) { | ||||
|                 try { | ||||
|                     fos.close(); | ||||
|                 } catch (IOException e) { | ||||
|                     e.printStackTrace(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static String readStringFromFile(String filePath) { | ||||
|         BufferedReader reader = null; | ||||
|         try { | ||||
|             reader = new BufferedReader(new FileReader(filePath)); | ||||
|             StringBuilder content = new StringBuilder(); | ||||
|             String line; | ||||
|             while ((line = reader.readLine()) != null) { | ||||
|                 content.append(line).append(System.getProperty("line.separator")); | ||||
|             } | ||||
|             // 去除最后一个换行符(可选) | ||||
|             if (content.length() > 0) { | ||||
|                 content.deleteCharAt(content.length() - 1); | ||||
|             } | ||||
|             return content.toString(); | ||||
|         } catch (IOException e) { | ||||
|             e.printStackTrace(); | ||||
|             return null; | ||||
|         } finally { | ||||
|             if (reader != null) { | ||||
|                 try { | ||||
|                     reader.close(); | ||||
|                 } catch (IOException e) { | ||||
|                     e.printStackTrace(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 	 | ||||
|     public static boolean writeStringToFile(String content, String filePath, boolean append) { | ||||
|         BufferedWriter writer = null; | ||||
|         try { | ||||
|             writer = new BufferedWriter(new FileWriter(filePath, append)); | ||||
|             writer.write(content); | ||||
|             return true; | ||||
|         } catch (IOException e) { | ||||
|             e.printStackTrace(); | ||||
|             return false; | ||||
|         } finally { | ||||
|             if (writer != null) { | ||||
|                 try { | ||||
|                     writer.close(); | ||||
|                 } catch (IOException e) { | ||||
|                     e.printStackTrace(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -41,6 +41,17 @@ android { | ||||
|             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | ||||
|         } | ||||
|     } | ||||
| 	 | ||||
| 	// 允许使用系统隐藏API | ||||
|     lintOptions { | ||||
|         checkReleaseBuilds false | ||||
|         abortOnError false | ||||
|     } | ||||
|     // 针对PowerProfile的依赖配置 | ||||
|     dependenciesInfo { | ||||
|         includeInApk = false | ||||
|         includeInBundle = false | ||||
|     } | ||||
| } | ||||
|  | ||||
| dependencies { | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| #Created by .winboll/winboll_app_build.gradle | ||||
| #Wed Sep 03 20:59:53 HKT 2025 | ||||
| stageCount=13 | ||||
| #Wed Oct 22 20:17:00 HKT 2025 | ||||
| stageCount=18 | ||||
| libraryProject= | ||||
| baseVersion=15.4 | ||||
| publishVersion=15.4.12 | ||||
| publishVersion=15.4.17 | ||||
| buildCount=0 | ||||
| baseBetaVersion=15.4.13 | ||||
| baseBetaVersion=15.4.18 | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| <?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"> | ||||
|  | ||||
|     <!-- 拍摄照片和视频 --> | ||||
| @@ -24,10 +25,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.POST_NOTIFICATIONS"/> | ||||
|  | ||||
|     <uses-feature android:name="android.hardware.camera"/> | ||||
|  | ||||
|     <uses-feature android:name="android.hardware.camera.autofocus"/> | ||||
| 		 | ||||
| 	<!-- 1. 基础应用信息读取权限(Android 11 及以下) --> | ||||
| 	<uses-permission android:name="android.permission.GET_PACKAGE_SIZE" /> | ||||
|  | ||||
| 	<!-- 2. Android 11+ 应用列表读取权限(必须声明,否则无法获取全部应用) --> | ||||
| 	<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" | ||||
| 		tools:ignore="QueryAllPackagesPermission" /> | ||||
|  | ||||
| 	<!-- 3. 可选:若需读取系统应用,添加此权限(部分机型需要) --> | ||||
| 	<uses-permission android:name="android.permission.ACCESS_PACKAGE_USAGE_STATS" | ||||
| 		tools:ignore="ProtectedPermissions" /> | ||||
| 	 | ||||
|     <application | ||||
|         android:name=".App" | ||||
|         android:allowBackup="true" | ||||
| @@ -123,6 +143,8 @@ | ||||
|  | ||||
|         <activity android:name="cc.winboll.studio.powerbell.activities.PixelPickerActivity"/> | ||||
|  | ||||
|         <activity android:name="cc.winboll.studio.powerbell.activities.BatteryReportActivity"/> | ||||
|  | ||||
|     </application> | ||||
|  | ||||
| </manifest> | ||||
| </manifest> | ||||
|   | ||||
| @@ -17,7 +17,7 @@ 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; | ||||
| @@ -159,9 +159,9 @@ public class MainActivity extends WinBoLLActivity { | ||||
|         if (menuItemId == R.id.action_about) { | ||||
|             Intent intent = new Intent(this, AboutActivity.class); | ||||
|             startActivity(intent); | ||||
|         } else if (menuItemId == R.id.action_battery_reporter) { | ||||
|         } else if (menuItemId == R.id.action_battery_report) { | ||||
|             Intent intent = new Intent(); | ||||
|             intent.setClass(this, BatteryReporterActivity.class); | ||||
|             intent.setClass(this, BatteryReportActivity.class); | ||||
|             startActivity(intent); | ||||
|         } else if (menuItemId == R.id.action_clearrecord) { | ||||
|             Intent intent = new Intent(); | ||||
|   | ||||
| @@ -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;     // 单次耗电(mAh,float类型) | ||||
|         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(系统布局固定ID:text1、text2) | ||||
|             public ViewHolder(View itemView) { | ||||
|                 super(itemView); | ||||
|                 tvAppName = (TextView) itemView.findViewById(android.R.id.text1); | ||||
|                 tvConsumption = (TextView) itemView.findViewById(android.R.id.text2); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -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); | ||||
|     } | ||||
| } | ||||
|  | ||||
							
								
								
									
										30
									
								
								powerbell/src/main/res/layout/activity_battery_report.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								powerbell/src/main/res/layout/activity_battery_report.xml
									
									
									
									
									
										Normal 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> | ||||
| @@ -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> | ||||
| @@ -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"/> | ||||
|   | ||||
| @@ -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> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user