Compare commits
	
		
			45 Commits
		
	
	
		
			appbase-v1
			...
			f8944490f8
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					f8944490f8 | ||
| 
						 | 
					733af004f6 | ||
| 
						 | 
					c03568e1f5 | ||
| 
						 | 
					a0575a5e8b | ||
| 
						 | 
					0e57ce679e | ||
| 
						 | 
					f9211a8eb4 | ||
| 
						 | 
					4c31ff9b54 | ||
| 
						 | 
					8cf610962e | ||
| 
						 | 
					3071d186ec | ||
| 
						 | 
					df10306059 | ||
| 
						 | 
					ccdb9c5abd | ||
| 
						 | 
					f27209ab87 | ||
| 
						 | 
					e8682ce410 | ||
| 
						 | 
					2e4003dae0 | ||
| 
						 | 
					198b0975ce | ||
| 
						 | 
					24a578a9d2 | ||
| 
						 | 
					46de24447f | ||
| 
						 | 
					2a819e94e4 | ||
| 
						 | 
					6635358ec5 | ||
| 
						 | 
					1320984829 | ||
| 
						 | 
					abf1e5ba42 | ||
| 
						 | 
					1cd2f88038 | ||
| 
						 | 
					3f6e583d68 | ||
| 
						 | 
					271456bfcd | ||
| 
						 | 
					ee5458d82c | ||
| 
						 | 
					3a83367f71 | ||
| 
						 | 
					74b9350a6a | ||
| 
						 | 
					d2858f23f7 | ||
| 
						 | 
					40a5b9c339 | ||
| 
						 | 
					fd79113572 | ||
| 
						 | 
					9b911b583c | ||
| 
						 | 
					37817c3e8c | ||
| 
						 | 
					0b5402f5f3 | ||
| 
						 | 
					bea22e3853 | ||
| 
						 | 
					7e2ad0c01d | ||
| 
						 | 
					476ce02fc8 | ||
| 
						 | 
					bc697279ad | ||
| 
						 | 
					dee01f1179 | ||
| 
						 | 
					a500decc7a | ||
| 
						 | 
					5099d00050 | ||
| 
						 | 
					515d14e896 | ||
| 
						 | 
					f630e27ed8 | ||
| 
						 | 
					cd7ed01216 | ||
| 
						 | 
					bb24bbfbd1 | ||
| 
						 | 
					2ba2f88510 | 
@@ -113,10 +113,10 @@ if [[ $? -eq 0 ]]; then
 | 
			
		||||
    # 如果Git已经提交了所有代码就执行标签和应用发布操作
 | 
			
		||||
 | 
			
		||||
    # 预先询问是否添加工作流标签
 | 
			
		||||
    echo "Add Github Workflows Tag? (yes/No)"
 | 
			
		||||
	result=$(askAddWorkflowsTag)
 | 
			
		||||
	nAskAddWorkflowsTag=$?
 | 
			
		||||
	echo $result
 | 
			
		||||
    #echo "Add Github Workflows Tag? (yes/No)"
 | 
			
		||||
	#result=$(askAddWorkflowsTag)
 | 
			
		||||
	#nAskAddWorkflowsTag=$?
 | 
			
		||||
	#echo $result
 | 
			
		||||
 | 
			
		||||
    # 发布应用
 | 
			
		||||
	echo "Publishing WinBoLL APK ..."
 | 
			
		||||
@@ -138,17 +138,17 @@ if [[ $? -eq 0 ]]; then
 | 
			
		||||
	fi
 | 
			
		||||
    
 | 
			
		||||
    # 添加 GitHub 工作流标签
 | 
			
		||||
	if [[ $nAskAddWorkflowsTag -eq 1 ]]; then
 | 
			
		||||
	#if [[ $nAskAddWorkflowsTag -eq 1 ]]; then
 | 
			
		||||
	    # 如果用户选择添加工作流标签
 | 
			
		||||
    	result=$(addWorkflowsTag $1)
 | 
			
		||||
		if [[ $? -eq 0 ]]; then
 | 
			
		||||
		    echo $result
 | 
			
		||||
    	#result=$(addWorkflowsTag $1)
 | 
			
		||||
		#if [[ $? -eq 0 ]]; then
 | 
			
		||||
		#    echo $result
 | 
			
		||||
		    # 工作流标签添加成功
 | 
			
		||||
		else
 | 
			
		||||
			echo -e "${0}: addWorkflowsTag $1\n${result}\nAdd workflows tag cancel."
 | 
			
		||||
			exit 1 # addWorkflowsTag 异常
 | 
			
		||||
		fi
 | 
			
		||||
	fi
 | 
			
		||||
		#else
 | 
			
		||||
			#echo -e "${0}: addWorkflowsTag $1\n${result}\nAdd workflows tag cancel."
 | 
			
		||||
			#exit 1 # addWorkflowsTag 异常
 | 
			
		||||
		#fi
 | 
			
		||||
	#fi
 | 
			
		||||
	
 | 
			
		||||
	## 清理更新描述文件内容
 | 
			
		||||
	echo "" > $1/app_update_description.txt
 | 
			
		||||
 
 | 
			
		||||
@@ -114,9 +114,11 @@
 | 
			
		||||
 | 
			
		||||
# 本项目要实际运用需要注意以下几个步骤:
 | 
			
		||||
# 在项目根目录下:
 | 
			
		||||
## 1. 项目模块编译环境设置(必须),settings.gradle-demo 要复制为 settings.gradle,并取消相应项目模块的注释。
 | 
			
		||||
## 2. 项目 Android SDK 编译环境设置(可选),local.properties-demo 要复制为 local.properties,并按需要设置 Android SDK 目录。
 | 
			
		||||
## 3. 类库型模块编译环境设置(可选),winboll.properties-demo 要复制为 winboll.properties,并按需要设置 WinBoLL Maven 库登录用户信息。
 | 
			
		||||
## ★. 项目模块编译环境设置(必须),settings.gradle-demo 要复制为 settings.gradle,并取消相应项目模块的注释。
 | 
			
		||||
## ★. 项目 Android SDK 编译环境设置(可选),local.properties-demo 要复制为 local.properties,并按需要设置 Android SDK 目录。
 | 
			
		||||
## ★. 应用签名密钥 keystore 设置问题。一般调试编译只需用【Termux】cd 进 GenKeyStore 目录执行 $ bash gen_debug_keystore.sh 命令即可完成设置。
 | 
			
		||||
## ☆. 应用 WiBoLL 签名密钥配置问题<非必须考虑>。设置时需要 clone 【keystore】模块源码并拷贝模块目录的 appkey.jks 与 appkey.keystore 到项目根目录即可。
 | 
			
		||||
## ☆. 类库型模块编译环境设置(可选),winboll.properties-demo 要复制为 winboll.properties,并按需要设置 WinBoLL Maven 库登录用户信息。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# ☆类库型项目编译方法
 | 
			
		||||
 
 | 
			
		||||
@@ -29,7 +29,7 @@ android {
 | 
			
		||||
        // versionName 更新后需要手动设置 
 | 
			
		||||
        // 项目模块目录的 build.gradle 文件的 stageCount=0
 | 
			
		||||
        // Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
 | 
			
		||||
        versionName "15.8" 
 | 
			
		||||
        versionName "15.9" 
 | 
			
		||||
        if(true) {
 | 
			
		||||
            versionName = genVersionName("${versionName}")
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
#Created by .winboll/winboll_app_build.gradle
 | 
			
		||||
#Tue Jun 03 19:17:05 HKT 2025
 | 
			
		||||
#Thu Jun 19 20:42:40 HKT 2025
 | 
			
		||||
stageCount=2
 | 
			
		||||
libraryProject=libaes
 | 
			
		||||
baseVersion=15.8
 | 
			
		||||
publishVersion=15.8.1
 | 
			
		||||
baseVersion=15.9
 | 
			
		||||
publishVersion=15.9.1
 | 
			
		||||
buildCount=0
 | 
			
		||||
baseBetaVersion=15.8.2
 | 
			
		||||
baseBetaVersion=15.9.2
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
#Created by .winboll/winboll_app_build.gradle
 | 
			
		||||
#Sun Jun 01 08:03:56 GMT 2025
 | 
			
		||||
#Thu Jun 19 12:49:47 GMT 2025
 | 
			
		||||
stageCount=0
 | 
			
		||||
libraryProject=
 | 
			
		||||
baseVersion=15.0
 | 
			
		||||
publishVersion=15.0.0
 | 
			
		||||
buildCount=24
 | 
			
		||||
buildCount=26
 | 
			
		||||
baseBetaVersion=15.0.1
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
#Created by .winboll/winboll_app_build.gradle
 | 
			
		||||
#Wed Jun 04 12:14:48 HKT 2025
 | 
			
		||||
stageCount=6
 | 
			
		||||
#Mon Jun 09 09:38:19 HKT 2025
 | 
			
		||||
stageCount=9
 | 
			
		||||
libraryProject=libappbase
 | 
			
		||||
baseVersion=15.8
 | 
			
		||||
publishVersion=15.8.5
 | 
			
		||||
publishVersion=15.8.8
 | 
			
		||||
buildCount=0
 | 
			
		||||
baseBetaVersion=15.8.6
 | 
			
		||||
baseBetaVersion=15.8.9
 | 
			
		||||
 
 | 
			
		||||
@@ -40,7 +40,8 @@
 | 
			
		||||
            android:resizeableActivity="true"
 | 
			
		||||
            android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"/>
 | 
			
		||||
 | 
			
		||||
        <activity android:name=".activities.New2Activity"
 | 
			
		||||
        <activity
 | 
			
		||||
            android:name=".activities.New2Activity"
 | 
			
		||||
            android:label="New2Activity"
 | 
			
		||||
            android:exported="true"
 | 
			
		||||
            android:resizeableActivity="true"
 | 
			
		||||
@@ -75,7 +76,8 @@
 | 
			
		||||
 | 
			
		||||
        <service android:name=".services.AssistantService"/>
 | 
			
		||||
 | 
			
		||||
        <receiver android:name="cc.winboll.studio.appbase.receivers.MainReceiver"
 | 
			
		||||
        <receiver
 | 
			
		||||
            android:name="cc.winboll.studio.appbase.receivers.MainReceiver"
 | 
			
		||||
            android:exported="true">
 | 
			
		||||
 | 
			
		||||
            <intent-filter>
 | 
			
		||||
@@ -106,7 +108,8 @@
 | 
			
		||||
 | 
			
		||||
        </receiver>
 | 
			
		||||
 | 
			
		||||
        <receiver android:name=".receivers.APPNewsWidgetClickListener"
 | 
			
		||||
        <receiver
 | 
			
		||||
            android:name=".receivers.APPNewsWidgetClickListener"
 | 
			
		||||
            android:exported="true">
 | 
			
		||||
 | 
			
		||||
            <intent-filter>
 | 
			
		||||
@@ -123,7 +126,6 @@
 | 
			
		||||
            android:name="android.max_aspect"
 | 
			
		||||
            android:value="4.0"/>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    </application>
 | 
			
		||||
 | 
			
		||||
</manifest>
 | 
			
		||||
 
 | 
			
		||||
@@ -64,6 +64,8 @@ public class MainActivity extends WinBoLLActivity implements IWinBoLLActivity {
 | 
			
		||||
    public boolean onOptionsItemSelected(MenuItem item) {
 | 
			
		||||
        if(item.getItemId() == R.id.item_yun) {
 | 
			
		||||
            GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(this, cc.winboll.studio.libappbase.activities.YunActivity.class);
 | 
			
		||||
        } else if(item.getItemId() == R.id.item_logon) {
 | 
			
		||||
            GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(this, cc.winboll.studio.libappbase.activities.LogonActivity.class);
 | 
			
		||||
        }
 | 
			
		||||
        // 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。
 | 
			
		||||
        return super.onOptionsItemSelected(item);
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,10 @@
 | 
			
		||||
        android:id="@+id/item_yun"
 | 
			
		||||
        android:title="YUN"
 | 
			
		||||
        android:icon="@drawable/ic_winboll"/>
 | 
			
		||||
    <item
 | 
			
		||||
        android:id="@+id/item_logon"
 | 
			
		||||
        android:title="Logon"
 | 
			
		||||
        android:icon="@drawable/ic_winboll"/>
 | 
			
		||||
    <item
 | 
			
		||||
        android:id="@+id/item_log"
 | 
			
		||||
        android:title="LOG"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
#Created by .winboll/winboll_app_build.gradle
 | 
			
		||||
#Tue Jun 03 19:16:41 HKT 2025
 | 
			
		||||
#Thu Jun 19 20:42:26 HKT 2025
 | 
			
		||||
stageCount=2
 | 
			
		||||
libraryProject=libaes
 | 
			
		||||
baseVersion=15.8
 | 
			
		||||
publishVersion=15.8.1
 | 
			
		||||
baseVersion=15.9
 | 
			
		||||
publishVersion=15.9.1
 | 
			
		||||
buildCount=0
 | 
			
		||||
baseBetaVersion=15.8.2
 | 
			
		||||
baseBetaVersion=15.9.2
 | 
			
		||||
 
 | 
			
		||||
@@ -24,5 +24,7 @@ dependencies {
 | 
			
		||||
    api fileTree(dir: 'libs', include: ['*.jar'])
 | 
			
		||||
    // 网络连接类库
 | 
			
		||||
    api 'com.squareup.okhttp3:okhttp:4.4.1'
 | 
			
		||||
    // https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind
 | 
			
		||||
    
 | 
			
		||||
    api 'com.google.code.gson:gson:2.10.1'
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
#Created by .winboll/winboll_app_build.gradle
 | 
			
		||||
#Wed Jun 04 12:14:48 HKT 2025
 | 
			
		||||
stageCount=6
 | 
			
		||||
#Mon Jun 09 09:38:19 HKT 2025
 | 
			
		||||
stageCount=9
 | 
			
		||||
libraryProject=libappbase
 | 
			
		||||
baseVersion=15.8
 | 
			
		||||
publishVersion=15.8.5
 | 
			
		||||
publishVersion=15.8.8
 | 
			
		||||
buildCount=0
 | 
			
		||||
baseBetaVersion=15.8.6
 | 
			
		||||
baseBetaVersion=15.8.9
 | 
			
		||||
 
 | 
			
		||||
@@ -105,6 +105,8 @@
 | 
			
		||||
 | 
			
		||||
        <activity android:name="cc.winboll.studio.libappbase.activities.YunActivity"/>
 | 
			
		||||
 | 
			
		||||
        <activity android:name="cc.winboll.studio.libappbase.activities.LogonActivity"/>
 | 
			
		||||
 | 
			
		||||
    </application>
 | 
			
		||||
 | 
			
		||||
</manifest>
 | 
			
		||||
@@ -0,0 +1,150 @@
 | 
			
		||||
package cc.winboll.studio.libappbase.activities;
 | 
			
		||||
 | 
			
		||||
import android.app.Activity;
 | 
			
		||||
import android.os.Bundle;
 | 
			
		||||
import android.view.View;
 | 
			
		||||
import android.widget.RadioButton;
 | 
			
		||||
import cc.winboll.studio.libappbase.BuildConfig;
 | 
			
		||||
import cc.winboll.studio.libappbase.LogUtils;
 | 
			
		||||
import cc.winboll.studio.libappbase.LogView;
 | 
			
		||||
import cc.winboll.studio.libappbase.R;
 | 
			
		||||
import cc.winboll.studio.libappbase.models.UserInfoModel;
 | 
			
		||||
import cc.winboll.studio.libappbase.utils.RSAUtils;
 | 
			
		||||
import cc.winboll.studio.libappbase.utils.YunUtils;
 | 
			
		||||
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
 | 
			
		||||
import java.security.KeyPair;
 | 
			
		||||
import java.security.PrivateKey;
 | 
			
		||||
import java.security.PublicKey;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @Author ZhanGSKen<zhangsken@188.com>
 | 
			
		||||
 * @Date 2025/06/04 13:29
 | 
			
		||||
 * @Describe 用户登录框
 | 
			
		||||
 */
 | 
			
		||||
public class LogonActivity extends Activity implements IWinBoLLActivity {
 | 
			
		||||
 | 
			
		||||
    public static final String TAG = "LogonActivity";
 | 
			
		||||
 | 
			
		||||
    public static final String DEBUG_HOST = "http://10.8.0.250:456";
 | 
			
		||||
    public static final String YUN_HOST = "https://yun.winboll.cc";
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
    String mHost = "";
 | 
			
		||||
    RadioButton mrbYunHost;
 | 
			
		||||
    RadioButton mrbDebugHost;
 | 
			
		||||
    LogView mLogView;
 | 
			
		||||
    
 | 
			
		||||
    @Override
 | 
			
		||||
    public Activity getActivity() {
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String getTag() {
 | 
			
		||||
        return TAG;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onCreate(Bundle savedInstanceState) {
 | 
			
		||||
        super.onCreate(savedInstanceState);
 | 
			
		||||
        setContentView(R.layout.activity_logon);
 | 
			
		||||
        mLogView = findViewById(R.id.logview);
 | 
			
		||||
        mLogView.start();
 | 
			
		||||
 | 
			
		||||
        mHost = BuildConfig.DEBUG ? DEBUG_HOST: YUN_HOST;
 | 
			
		||||
        if (BuildConfig.DEBUG) {
 | 
			
		||||
            mrbYunHost = findViewById(R.id.rb_yunhost);
 | 
			
		||||
            mrbDebugHost = findViewById(R.id.rb_debughost);
 | 
			
		||||
            mrbYunHost.setChecked(!BuildConfig.DEBUG);
 | 
			
		||||
            mrbDebugHost.setChecked(BuildConfig.DEBUG);
 | 
			
		||||
        } else {
 | 
			
		||||
            findViewById(R.id.ll_hostbar).setVisibility(View.GONE);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public void onSwitchHost(View view) {
 | 
			
		||||
        if (view.getId() == R.id.rb_yunhost) {
 | 
			
		||||
            mrbDebugHost.setChecked(false);
 | 
			
		||||
            mHost = YUN_HOST;
 | 
			
		||||
        } else if (view.getId() == R.id.rb_debughost) {
 | 
			
		||||
            mrbYunHost.setChecked(false);
 | 
			
		||||
            mHost = DEBUG_HOST;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onResume() {
 | 
			
		||||
        super.onResume();
 | 
			
		||||
        mLogView.start();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void onTestLogin(View view) {
 | 
			
		||||
        LogUtils.d(TAG, "onTestLogin");
 | 
			
		||||
        final YunUtils yunUtils = YunUtils.getInstance(this);
 | 
			
		||||
        
 | 
			
		||||
        UserInfoModel userInfoModel = new UserInfoModel();
 | 
			
		||||
        userInfoModel.setUsername("jian");
 | 
			
		||||
        userInfoModel.setPassword("kkiio");
 | 
			
		||||
        userInfoModel.setToken("aaa111");
 | 
			
		||||
        yunUtils.login(mHost, userInfoModel);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void onTestRSA(View view) {
 | 
			
		||||
        LogUtils.d(TAG, "onTestRSA");
 | 
			
		||||
        RSAUtils utils = RSAUtils.getInstance(this);
 | 
			
		||||
        
 | 
			
		||||
        try {
 | 
			
		||||
            // 测试 1:首次生成密钥对
 | 
			
		||||
            LogUtils.d(TAG, "==== 首次生成密钥对 ====");
 | 
			
		||||
            if (utils.keysExist()) {
 | 
			
		||||
                LogUtils.d(TAG, "密钥对已生成");
 | 
			
		||||
            } else {
 | 
			
		||||
                utils.generateAndSaveKeys();
 | 
			
		||||
                LogUtils.d(TAG, "密钥对生成成功。");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 测试 2:获取密钥对(自动读取已生成的文件)
 | 
			
		||||
            KeyPair keyPair = utils.getOrGenerateKeys();
 | 
			
		||||
            PublicKey publicKey = keyPair.getPublic();
 | 
			
		||||
            PrivateKey privateKey = keyPair.getPrivate();
 | 
			
		||||
 | 
			
		||||
            // 打印密钥信息
 | 
			
		||||
            LogUtils.d(TAG, "\n==== 密钥信息 ====");
 | 
			
		||||
            LogUtils.d(TAG, "公钥算法:" + publicKey.getAlgorithm());
 | 
			
		||||
            LogUtils.d(TAG, "公钥编码长度:" + publicKey.getEncoded().length + "字节");
 | 
			
		||||
            LogUtils.d(TAG, "私钥算法:" + privateKey.getAlgorithm());
 | 
			
		||||
            LogUtils.d(TAG, "私钥编码长度:" + privateKey.getEncoded().length + "字节");
 | 
			
		||||
 | 
			
		||||
            // 测试 3:重复调用时检查是否复用文件
 | 
			
		||||
            LogUtils.d(TAG, "\n==== 二次调用 ====");
 | 
			
		||||
            KeyPair reusedPair = utils.getOrGenerateKeys();
 | 
			
		||||
            LogUtils.d(TAG, "是否为同一公钥:" + (publicKey.equals(reusedPair.getPublic()))); // true(单例引用)
 | 
			
		||||
            LogUtils.d(TAG, "操作完成");
 | 
			
		||||
 | 
			
		||||
            String testMessage = "Hello, RSA Encryption!";
 | 
			
		||||
 | 
			
		||||
            // 1. 获取或生成密钥对
 | 
			
		||||
            PublicKey publicKeyReused = reusedPair.getPublic();
 | 
			
		||||
            PrivateKey privateKeyReused = reusedPair.getPrivate();
 | 
			
		||||
 | 
			
		||||
            // 2. 公钥加密
 | 
			
		||||
            byte[] encryptedData = utils.encryptWithPublicKey(testMessage, publicKeyReused);
 | 
			
		||||
            LogUtils.d(TAG, "加密后数据(字节长度):" + encryptedData.length);
 | 
			
		||||
 | 
			
		||||
            // 3. 私钥解密
 | 
			
		||||
            String decryptedMessage = utils.decryptWithPrivateKey(encryptedData, privateKeyReused);
 | 
			
		||||
            LogUtils.d(TAG, "解密结果: " + decryptedMessage);
 | 
			
		||||
            
 | 
			
		||||
            // 4. 验证解密是否成功
 | 
			
		||||
            if (testMessage.equals(decryptedMessage)) {
 | 
			
		||||
                LogUtils.d(TAG, "加密解密测试通过!");
 | 
			
		||||
            } else {
 | 
			
		||||
                LogUtils.d(TAG, "测试失败:内容不一致");
 | 
			
		||||
            }
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -11,6 +11,8 @@ import java.io.IOException;
 | 
			
		||||
import okhttp3.OkHttpClient;
 | 
			
		||||
import okhttp3.Request;
 | 
			
		||||
import okhttp3.Response;
 | 
			
		||||
import android.widget.RadioButton;
 | 
			
		||||
import cc.winboll.studio.libappbase.LogView;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @Author ZhanGSKen<zhangsken@188.com>
 | 
			
		||||
@@ -21,7 +23,13 @@ public class YunActivity extends Activity implements IWinBoLLActivity {
 | 
			
		||||
 | 
			
		||||
    public static final String TAG = "YunActivity";
 | 
			
		||||
 | 
			
		||||
    String mYunHost = "";
 | 
			
		||||
    public static final String DEBUG_HOST = "http://10.8.0.250:456";
 | 
			
		||||
    public static final String YUN_HOST = "https://yun.winboll.cc";
 | 
			
		||||
 | 
			
		||||
    String mHost = "";
 | 
			
		||||
    RadioButton mrbYunHost;
 | 
			
		||||
    RadioButton mrbDebugHost;
 | 
			
		||||
    LogView mLogView;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Activity getActivity() {
 | 
			
		||||
@@ -37,7 +45,34 @@ public class YunActivity extends Activity implements IWinBoLLActivity {
 | 
			
		||||
    protected void onCreate(Bundle savedInstanceState) {
 | 
			
		||||
        super.onCreate(savedInstanceState);
 | 
			
		||||
        setContentView(R.layout.activity_yun);
 | 
			
		||||
        mYunHost = BuildConfig.DEBUG ?"http://10.8.0.250:456": "https://yun.winboll.cc";
 | 
			
		||||
        mLogView = findViewById(R.id.logview);
 | 
			
		||||
        mLogView.start();
 | 
			
		||||
 | 
			
		||||
        mHost = BuildConfig.DEBUG ? DEBUG_HOST: YUN_HOST;
 | 
			
		||||
        if (BuildConfig.DEBUG) {
 | 
			
		||||
            mrbYunHost = findViewById(R.id.rb_yunhost);
 | 
			
		||||
            mrbDebugHost = findViewById(R.id.rb_debughost);
 | 
			
		||||
            mrbYunHost.setChecked(!BuildConfig.DEBUG);
 | 
			
		||||
            mrbDebugHost.setChecked(BuildConfig.DEBUG);
 | 
			
		||||
        } else {
 | 
			
		||||
            findViewById(R.id.ll_hostbar).setVisibility(View.GONE);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void onSwitchHost(View view) {
 | 
			
		||||
        if (view.getId() == R.id.rb_yunhost) {
 | 
			
		||||
            mrbDebugHost.setChecked(false);
 | 
			
		||||
            mHost = YUN_HOST;
 | 
			
		||||
        } else if (view.getId() == R.id.rb_debughost) {
 | 
			
		||||
            mrbYunHost.setChecked(false);
 | 
			
		||||
            mHost = DEBUG_HOST;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onResume() {
 | 
			
		||||
        super.onResume();
 | 
			
		||||
        mLogView.start();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void onTestYun(View view) {
 | 
			
		||||
@@ -53,7 +88,7 @@ public class YunActivity extends Activity implements IWinBoLLActivity {
 | 
			
		||||
    void testYun() {
 | 
			
		||||
        OkHttpClient client = new OkHttpClient();
 | 
			
		||||
        Request request = new Request.Builder()
 | 
			
		||||
            .url(mYunHost + "/backups/")
 | 
			
		||||
            .url(mHost + "/backups/")
 | 
			
		||||
            .build();
 | 
			
		||||
 | 
			
		||||
        Response response = null;
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,53 @@
 | 
			
		||||
package cc.winboll.studio.libappbase.models;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @Author ZhanGSKen<zhangsken@188.com>
 | 
			
		||||
 * @Date 2025/06/05 11:26
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
public class ResponseData {
 | 
			
		||||
    
 | 
			
		||||
    public static final String STATUS_SUCCESS = "success";
 | 
			
		||||
    public static final String STATUS_ERROR = "error";
 | 
			
		||||
    
 | 
			
		||||
    private String status;
 | 
			
		||||
    private String message;
 | 
			
		||||
    private UserInfoModel data;
 | 
			
		||||
    
 | 
			
		||||
    public ResponseData() {
 | 
			
		||||
        this.status = "";
 | 
			
		||||
        this.message = "";
 | 
			
		||||
        this.data = new UserInfoModel();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public ResponseData(String status, String message, UserInfoModel data) {
 | 
			
		||||
        this.status = status;
 | 
			
		||||
        this.message = message;
 | 
			
		||||
        this.data = data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setStatus(String status) {
 | 
			
		||||
        this.status = status;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getStatus() {
 | 
			
		||||
        return status;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setMessage(String message) {
 | 
			
		||||
        this.message = message;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getMessage() {
 | 
			
		||||
        return message;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setData(UserInfoModel data) {
 | 
			
		||||
        this.data = data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public UserInfoModel getData() {
 | 
			
		||||
        return data;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -0,0 +1,92 @@
 | 
			
		||||
package cc.winboll.studio.libappbase.models;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @Author ZhanGSKen<zhangsken@188.com>
 | 
			
		||||
 * @Date 2025/06/04 19:14
 | 
			
		||||
 */
 | 
			
		||||
import android.util.JsonReader;
 | 
			
		||||
import android.util.JsonWriter;
 | 
			
		||||
import cc.winboll.studio.libappbase.BaseBean;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
 | 
			
		||||
public class UserInfoModel extends BaseBean {
 | 
			
		||||
 | 
			
		||||
    public static final String TAG = "UserInfoModel";
 | 
			
		||||
 | 
			
		||||
    String username;
 | 
			
		||||
    String password;
 | 
			
		||||
    String token;
 | 
			
		||||
 | 
			
		||||
    public UserInfoModel() {
 | 
			
		||||
        this.username = "";
 | 
			
		||||
        this.password = "";
 | 
			
		||||
        this.token = "";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setUsername(String username) {
 | 
			
		||||
        this.username = username;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getUsername() {
 | 
			
		||||
        return username;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setPassword(String password) {
 | 
			
		||||
        this.password = password;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getPassword() {
 | 
			
		||||
        return password;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setToken(String token) {
 | 
			
		||||
        this.token = token;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getToken() {
 | 
			
		||||
        return token;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String getName() {
 | 
			
		||||
        return UserInfoModel.class.getName();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
 | 
			
		||||
        super.writeThisToJsonWriter(jsonWriter);
 | 
			
		||||
        jsonWriter.name("username").value(getUsername());
 | 
			
		||||
        jsonWriter.name("password").value(getPassword());
 | 
			
		||||
        jsonWriter.name("token").value(getToken());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
 | 
			
		||||
        if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
 | 
			
		||||
            if (name.equals("username")) {
 | 
			
		||||
                setUsername(jsonReader.nextString());
 | 
			
		||||
            } else if (name.equals("password")) {
 | 
			
		||||
                setPassword(jsonReader.nextString());
 | 
			
		||||
            } else if (name.equals("token")) {
 | 
			
		||||
                setToken(jsonReader.nextString());
 | 
			
		||||
            } else {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
 | 
			
		||||
        jsonReader.beginObject();
 | 
			
		||||
        while (jsonReader.hasNext()) {
 | 
			
		||||
            String name = jsonReader.nextName();
 | 
			
		||||
            if (!initObjectsFromJsonReader(jsonReader, name)) {
 | 
			
		||||
                jsonReader.skipValue();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // 结束 JSON 对象
 | 
			
		||||
        jsonReader.endObject();
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,128 @@
 | 
			
		||||
package cc.winboll.studio.libappbase.utils;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @Author ZhanGSKen<zhangsken@188.com>
 | 
			
		||||
 * @Date 2025/06/04 20:15
 | 
			
		||||
 * @Describe 文件操作类
 | 
			
		||||
 */
 | 
			
		||||
import java.io.BufferedReader;
 | 
			
		||||
import java.io.BufferedWriter;
 | 
			
		||||
import java.io.ByteArrayOutputStream;
 | 
			
		||||
import java.io.FileInputStream;
 | 
			
		||||
import java.io.FileOutputStream;
 | 
			
		||||
import java.io.FileReader;
 | 
			
		||||
import java.io.FileWriter;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
 | 
			
		||||
public class FileUtils {
 | 
			
		||||
    public static final String TAG = "FileUtils";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 读取文件为字节数组(Java 7 语法)
 | 
			
		||||
     */
 | 
			
		||||
    public static byte[] readFileToByteArray(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();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 原字符串读写方法(适配 Java 7)
 | 
			
		||||
    public static String readFileToString(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();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,222 @@
 | 
			
		||||
package cc.winboll.studio.libappbase.utils;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @Author ZhanGSKen<zhangsken@188.com>
 | 
			
		||||
 * @Date 2025/06/04 13:36
 | 
			
		||||
 * @Describe RSA加密工具
 | 
			
		||||
 */
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import android.util.Base64;
 | 
			
		||||
import cc.winboll.studio.libappbase.LogUtils;
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.FileInputStream;
 | 
			
		||||
import java.io.FileOutputStream;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.security.KeyFactory;
 | 
			
		||||
import java.security.KeyPair;
 | 
			
		||||
import java.security.KeyPairGenerator;
 | 
			
		||||
import java.security.NoSuchAlgorithmException;
 | 
			
		||||
import java.security.PrivateKey;
 | 
			
		||||
import java.security.PublicKey;
 | 
			
		||||
import java.security.spec.InvalidKeySpecException;
 | 
			
		||||
import java.security.spec.PKCS8EncodedKeySpec;
 | 
			
		||||
import java.security.spec.X509EncodedKeySpec;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
import javax.crypto.Cipher;
 | 
			
		||||
 | 
			
		||||
public class RSAUtils {
 | 
			
		||||
    private static final String TAG = "RSAUtils";
 | 
			
		||||
    private static final int KEY_SIZE = 2048;
 | 
			
		||||
    private static final String KEY_ALGORITHM = "RSA";
 | 
			
		||||
    private static final String PUBLIC_KEY_FILE = "public.key";
 | 
			
		||||
    private static final String PRIVATE_KEY_FILE = "private.key";
 | 
			
		||||
    private static final String CIPHER_ALGORITHM = KEY_ALGORITHM + "/ECB/PKCS1Padding"; // 保留原加密方式
 | 
			
		||||
 | 
			
		||||
    private final String keyPath;
 | 
			
		||||
    private static volatile RSAUtils INSTANCE;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 构造方法:初始化密钥存储路径(内部存储)
 | 
			
		||||
     */
 | 
			
		||||
    private RSAUtils(Context context) {
 | 
			
		||||
        keyPath = context.getFilesDir() + File.separator + "keys" + File.separator; // 修正路径格式
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取单例实例
 | 
			
		||||
     */
 | 
			
		||||
    public static synchronized RSAUtils getInstance(Context context) {
 | 
			
		||||
        if (INSTANCE == null) {
 | 
			
		||||
            INSTANCE = new RSAUtils(context);
 | 
			
		||||
        }
 | 
			
		||||
        return INSTANCE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 检查密钥文件是否存在
 | 
			
		||||
     */
 | 
			
		||||
    public boolean keysExist() {
 | 
			
		||||
        File publicKeyFile = new File(keyPath + PUBLIC_KEY_FILE);
 | 
			
		||||
        File privateKeyFile = new File(keyPath + PRIVATE_KEY_FILE);
 | 
			
		||||
        return publicKeyFile.exists() && privateKeyFile.exists();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 生成密钥对并保存到文件
 | 
			
		||||
     */
 | 
			
		||||
    public void generateAndSaveKeys() throws Exception {
 | 
			
		||||
        LogUtils.d(TAG, "开始生成 RSA 密钥对(2048位)");
 | 
			
		||||
        KeyPairGenerator generator = KeyPairGenerator.getInstance(KEY_ALGORITHM);
 | 
			
		||||
        generator.initialize(KEY_SIZE);
 | 
			
		||||
        KeyPair keyPair = generator.generateKeyPair();
 | 
			
		||||
 | 
			
		||||
        saveKey(PUBLIC_KEY_FILE, keyPair.getPublic().getEncoded());
 | 
			
		||||
        saveKey(PRIVATE_KEY_FILE, keyPair.getPrivate().getEncoded());
 | 
			
		||||
        LogUtils.d(TAG, "密钥对生成并保存成功");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取或生成密钥对(线程安全)
 | 
			
		||||
     */
 | 
			
		||||
    public KeyPair getOrGenerateKeys() throws Exception {
 | 
			
		||||
        if (!keysExist()) {
 | 
			
		||||
            synchronized (RSAUtils.class) { // 双重检查锁,避免多线程重复生成
 | 
			
		||||
                if (!keysExist()) {
 | 
			
		||||
                    generateAndSaveKeys();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return readKeysFromFile();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 从文件读取密钥对
 | 
			
		||||
     */
 | 
			
		||||
    private KeyPair readKeysFromFile() throws Exception {
 | 
			
		||||
        LogUtils.d(TAG, "读取密钥对文件");
 | 
			
		||||
        try {
 | 
			
		||||
            byte[] publicKeyBytes = readFileToBytes(keyPath + PUBLIC_KEY_FILE);
 | 
			
		||||
            byte[] privateKeyBytes = readFileToBytes(keyPath + PRIVATE_KEY_FILE);
 | 
			
		||||
 | 
			
		||||
            X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(publicKeyBytes);
 | 
			
		||||
            PKCS8EncodedKeySpec privateSpec = new PKCS8EncodedKeySpec(privateKeyBytes);
 | 
			
		||||
 | 
			
		||||
            KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM);
 | 
			
		||||
            PublicKey publicKey = factory.generatePublic(publicSpec);
 | 
			
		||||
            PrivateKey privateKey = factory.generatePrivate(privateSpec);
 | 
			
		||||
 | 
			
		||||
            return new KeyPair(publicKey, privateKey);
 | 
			
		||||
        } catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) {
 | 
			
		||||
            LogUtils.e(TAG, "密钥文件读取失败:" + e.getMessage());
 | 
			
		||||
            throw new Exception("密钥文件损坏或格式错误", e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 保存密钥到文件(通用方法)
 | 
			
		||||
     */
 | 
			
		||||
    private void saveKey(String fileName, byte[] keyBytes) throws IOException {
 | 
			
		||||
        Objects.requireNonNull(keyBytes, "密钥字节数据不可为空");
 | 
			
		||||
        File dir = new File(keyPath);
 | 
			
		||||
        if (!dir.exists() && !dir.mkdirs()) {
 | 
			
		||||
            throw new IOException("创建密钥目录失败:" + keyPath);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        FileOutputStream fos = null;
 | 
			
		||||
        try {
 | 
			
		||||
            fos = new FileOutputStream(keyPath + fileName);
 | 
			
		||||
            fos.write(keyBytes);
 | 
			
		||||
        } finally {
 | 
			
		||||
            if (fos != null) {
 | 
			
		||||
                try {
 | 
			
		||||
                    fos.close();
 | 
			
		||||
                } catch (IOException e) {
 | 
			
		||||
                    LogUtils.e(TAG, "关闭文件流失败:" + e.getMessage());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 读取文件为字节数组(Java 7 兼容)
 | 
			
		||||
     */
 | 
			
		||||
    private byte[] readFileToBytes(String filePath) throws IOException {
 | 
			
		||||
        File file = new File(filePath);
 | 
			
		||||
        if (!file.exists() || file.isDirectory()) {
 | 
			
		||||
            throw new IOException("文件不存在或为目录:" + filePath);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        FileInputStream fis = null;
 | 
			
		||||
        try {
 | 
			
		||||
            fis = new FileInputStream(file);
 | 
			
		||||
            byte[] data = new byte[(int) file.length()];
 | 
			
		||||
            int bytesRead = fis.read(data);
 | 
			
		||||
            if (bytesRead != data.length) {
 | 
			
		||||
                throw new IOException("文件读取不完整");
 | 
			
		||||
            }
 | 
			
		||||
            return data;
 | 
			
		||||
        } finally {
 | 
			
		||||
            if (fis != null) {
 | 
			
		||||
                try {
 | 
			
		||||
                    fis.close();
 | 
			
		||||
                } catch (IOException e) {
 | 
			
		||||
                    LogUtils.e(TAG, "关闭文件流失败:" + e.getMessage());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 公钥加密(带参数校验)
 | 
			
		||||
     */
 | 
			
		||||
    public byte[] encryptWithPublicKey(String plainText, PublicKey publicKey) throws Exception {
 | 
			
		||||
        Objects.requireNonNull(plainText, "明文不可为空");
 | 
			
		||||
        Objects.requireNonNull(publicKey, "公钥不可为空");
 | 
			
		||||
 | 
			
		||||
        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
 | 
			
		||||
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
 | 
			
		||||
 | 
			
		||||
        // 检查数据长度是否超过 RSA 限制(2048位密钥最大明文为 214字节,PKCS1Padding)
 | 
			
		||||
        int maxPlainTextSize = cipher.getBlockSize() - 11; // PKCS1Padding 固定填充长度
 | 
			
		||||
        if (plainText.getBytes("UTF-8").length > maxPlainTextSize) {
 | 
			
		||||
            throw new IllegalArgumentException("明文过长,最大支持 " + maxPlainTextSize + " 字节");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return cipher.doFinal(plainText.getBytes("UTF-8"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 私钥解密(带参数校验)
 | 
			
		||||
     */
 | 
			
		||||
    public String decryptWithPrivateKey(byte[] encryptedData, PrivateKey privateKey) throws Exception {
 | 
			
		||||
        Objects.requireNonNull(encryptedData, "密文不可为空");
 | 
			
		||||
        Objects.requireNonNull(privateKey, "私钥不可为空");
 | 
			
		||||
 | 
			
		||||
        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
 | 
			
		||||
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
 | 
			
		||||
        byte[] decryptedBytes = cipher.doFinal(encryptedData);
 | 
			
		||||
        return new String(decryptedBytes, "UTF-8");
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * 将 HTTP 传输的 Base64 字符串还原为加密字节数组(Java 7 兼容)
 | 
			
		||||
     * @param httpString Base64 字符串(非 null)
 | 
			
		||||
     * @return 加密字节数组
 | 
			
		||||
     * @throws IllegalArgumentException 解码失败时抛出
 | 
			
		||||
     */
 | 
			
		||||
    public byte[] httpStringToEncryptBytes(String httpString) {
 | 
			
		||||
        Objects.requireNonNull(httpString, "HTTP 字符串不可为空");
 | 
			
		||||
 | 
			
		||||
        // 计算缺失的填充符数量(Java 7 不支持 repeat(),手动拼接)
 | 
			
		||||
        int pad = httpString.length() % 4;
 | 
			
		||||
        StringBuilder paddedString = new StringBuilder(httpString);
 | 
			
		||||
        if (pad != 0) {
 | 
			
		||||
            for (int i = 0; i < pad; i++) {
 | 
			
		||||
                paddedString.append('='); // 补全 '='
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 使用 Base64 解码(Android 原生 Base64 类兼容 Java 7)
 | 
			
		||||
        return Base64.decode(paddedString.toString(), Base64.URL_SAFE);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -0,0 +1,281 @@
 | 
			
		||||
package cc.winboll.studio.libappbase.utils;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @Author ZhanGSKen<zhangsken@188.com>
 | 
			
		||||
 * @Date 2025/06/04 17:21
 | 
			
		||||
 * @Describe 应用登录与接口工具
 | 
			
		||||
 */
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import android.os.Handler;
 | 
			
		||||
import android.os.Looper;
 | 
			
		||||
import cc.winboll.studio.libappbase.LogUtils;
 | 
			
		||||
import cc.winboll.studio.libappbase.models.ResponseData;
 | 
			
		||||
import cc.winboll.studio.libappbase.models.UserInfoModel;
 | 
			
		||||
import com.google.gson.Gson;
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.net.URLDecoder;
 | 
			
		||||
import java.nio.charset.StandardCharsets;
 | 
			
		||||
import java.security.KeyPair;
 | 
			
		||||
import java.security.PrivateKey;
 | 
			
		||||
import java.security.PublicKey;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
import okhttp3.Call;
 | 
			
		||||
import okhttp3.Callback;
 | 
			
		||||
import okhttp3.MediaType;
 | 
			
		||||
import okhttp3.OkHttpClient;
 | 
			
		||||
import okhttp3.Request;
 | 
			
		||||
import okhttp3.RequestBody;
 | 
			
		||||
import okhttp3.Response;
 | 
			
		||||
import java.io.UnsupportedEncodingException;
 | 
			
		||||
 | 
			
		||||
public class YunUtils {
 | 
			
		||||
    public static final String TAG = "YunUtils";
 | 
			
		||||
    // 私有静态实例,类加载时创建
 | 
			
		||||
    private static volatile YunUtils INSTANCE;
 | 
			
		||||
    Context mContext;
 | 
			
		||||
    UserInfoModel mUserInfoModel;
 | 
			
		||||
    String token = "";
 | 
			
		||||
    String mDataFolderPath = "";
 | 
			
		||||
    String mUserInfoModelPath = "";
 | 
			
		||||
 | 
			
		||||
    private static final int CONNECT_TIMEOUT = 15; // 连接超时时间(秒)
 | 
			
		||||
    private static final int READ_TIMEOUT = 20;    // 读取超时时间(秒)
 | 
			
		||||
    private static volatile YunUtils instance;
 | 
			
		||||
    private OkHttpClient okHttpClient;
 | 
			
		||||
    private Handler mainHandler; // 主线程 Handler
 | 
			
		||||
 | 
			
		||||
    // 私有构造方法,防止外部实例化
 | 
			
		||||
    private YunUtils(Context context) {
 | 
			
		||||
        LogUtils.d(TAG, "YunUtils");
 | 
			
		||||
        mContext = context;
 | 
			
		||||
        mDataFolderPath = mContext.getExternalFilesDir(TAG).toString();
 | 
			
		||||
        File fTest = new File(mDataFolderPath);
 | 
			
		||||
        if (!fTest.exists()) {
 | 
			
		||||
            fTest.mkdirs();
 | 
			
		||||
        }
 | 
			
		||||
        mUserInfoModelPath = mDataFolderPath + File.separator + "UserInfoModel.rsajson";
 | 
			
		||||
 | 
			
		||||
        okHttpClient = new OkHttpClient.Builder()
 | 
			
		||||
            .connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
 | 
			
		||||
            .readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
 | 
			
		||||
            .build();
 | 
			
		||||
        mainHandler = new Handler(Looper.getMainLooper()); // 获取主线程 Looper
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 公共静态方法,返回唯一实例
 | 
			
		||||
    public static synchronized YunUtils getInstance(Context context) {
 | 
			
		||||
        LogUtils.d(TAG, "getInstance");
 | 
			
		||||
        if (INSTANCE == null) {
 | 
			
		||||
            INSTANCE = new YunUtils(context);
 | 
			
		||||
        }
 | 
			
		||||
        return INSTANCE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void checkLoginStatus() {
 | 
			
		||||
        String token = getLocalToken();
 | 
			
		||||
        LogUtils.d(TAG, String.format("checkLoginStatus token is %s", token));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    String getLocalToken() {
 | 
			
		||||
        UserInfoModel userInfoModel = loadUserInfoModel();
 | 
			
		||||
        return (userInfoModel == null) ?"": userInfoModel.getToken();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void login(String host, UserInfoModel userInfoModel) {
 | 
			
		||||
        LogUtils.d(TAG, "login");
 | 
			
		||||
 | 
			
		||||
        // 发送 POST 请求
 | 
			
		||||
        String apiUrl = host + "/login/index.php";
 | 
			
		||||
        // 序列化对象为JSON
 | 
			
		||||
        Gson gson = new Gson();
 | 
			
		||||
        String jsonData = gson.toJson(userInfoModel); // 自动生成标准JSON
 | 
			
		||||
        //String jsonData = userInfoModel.toString();
 | 
			
		||||
        LogUtils.d(TAG, "要发送的数据 : " + jsonData);
 | 
			
		||||
 | 
			
		||||
        sendPostRequest(apiUrl, jsonData, new OnResponseListener() {
 | 
			
		||||
                // 成功回调(主线程)
 | 
			
		||||
                @Override
 | 
			
		||||
                public void onSuccess(String responseBody) {
 | 
			
		||||
                    LogUtils.d(TAG, "onSuccess");
 | 
			
		||||
                    LogUtils.d(TAG, String.format("responseBody %s", responseBody));
 | 
			
		||||
                    Gson gson = new Gson();
 | 
			
		||||
                    ResponseData result = gson.fromJson(responseBody, ResponseData.class); // 转为 Result 实例
 | 
			
		||||
                    if(result.getStatus().equals(ResponseData.STATUS_SUCCESS)) {
 | 
			
		||||
                        
 | 
			
		||||
                            UserInfoModel userInfoModel = result.getData();
 | 
			
		||||
                            if (userInfoModel != null) {
 | 
			
		||||
                                LogUtils.d(TAG, "收到网站 UserInfoModel");
 | 
			
		||||
                                String token = userInfoModel.getToken();
 | 
			
		||||
                                saveLocalToken(token);
 | 
			
		||||
                                checkLoginStatus();
 | 
			
		||||
                            }
 | 
			
		||||
                       
 | 
			
		||||
                    } else if(result.getStatus().equals(ResponseData.STATUS_ERROR)) {
 | 
			
		||||
                        try {
 | 
			
		||||
                            String decodedMessage = URLDecoder.decode(result.getMessage(), "UTF-8");
 | 
			
		||||
                            LogUtils.d(TAG, "服务器返回信息: " + decodedMessage);
 | 
			
		||||
                        } catch (UnsupportedEncodingException e) {
 | 
			
		||||
                            LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // 失败回调(主线程)
 | 
			
		||||
                @Override
 | 
			
		||||
                public void onFailure(String errorMsg) {
 | 
			
		||||
                    LogUtils.d(TAG, errorMsg);
 | 
			
		||||
                    // 处理错误
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void saveLocalToken(String token) {
 | 
			
		||||
        UserInfoModel userInfoModel = new UserInfoModel();
 | 
			
		||||
        userInfoModel.setToken(token);
 | 
			
		||||
        saveUserInfoModel(userInfoModel);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    UserInfoModel loadUserInfoModel() {
 | 
			
		||||
        LogUtils.d(TAG, "loadUserInfoModel");
 | 
			
		||||
        if (new File(mUserInfoModelPath).exists()) {
 | 
			
		||||
            try {
 | 
			
		||||
                // 加载加密后的模型数据
 | 
			
		||||
                byte[] encryptedData = FileUtils.readFileToByteArray(mUserInfoModelPath);
 | 
			
		||||
                // 加载 RSA 工具
 | 
			
		||||
                RSAUtils utils = RSAUtils.getInstance(mContext);
 | 
			
		||||
                KeyPair keyPair = utils.getOrGenerateKeys();
 | 
			
		||||
                //PublicKey publicKey = keyPair.getPublic();
 | 
			
		||||
                PrivateKey privateKey = keyPair.getPrivate();
 | 
			
		||||
                // 私钥解密模型数据
 | 
			
		||||
                String szInfo = utils.decryptWithPrivateKey(encryptedData, keyPair.getPrivate());
 | 
			
		||||
                LogUtils.d(TAG, String.format("szInfo %s", szInfo));
 | 
			
		||||
                mUserInfoModel = UserInfoModel.parseStringToBean(szInfo, UserInfoModel.class);
 | 
			
		||||
                if (mUserInfoModel == null) {
 | 
			
		||||
                    LogUtils.d(TAG, "模型数据解析为空数据。");
 | 
			
		||||
                }
 | 
			
		||||
                LogUtils.d(TAG, "UserInfoModel 解密加载结束。");
 | 
			
		||||
            } catch (Exception e) {
 | 
			
		||||
                LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            LogUtils.d(TAG, "云服务登录信息不存在。");
 | 
			
		||||
            mUserInfoModel = null;
 | 
			
		||||
        }
 | 
			
		||||
        return mUserInfoModel;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void saveUserInfoModel(UserInfoModel userInfoModel) {
 | 
			
		||||
        LogUtils.d(TAG, "saveUserInfoModel");
 | 
			
		||||
        try {
 | 
			
		||||
            String szInfo = userInfoModel.toString();
 | 
			
		||||
            LogUtils.d(TAG, "原始数据: " + szInfo);
 | 
			
		||||
 | 
			
		||||
            RSAUtils utils = RSAUtils.getInstance(mContext);
 | 
			
		||||
            KeyPair keyPair = utils.getOrGenerateKeys();
 | 
			
		||||
            PublicKey publicKey = keyPair.getPublic();
 | 
			
		||||
 | 
			
		||||
            // 公钥加密(传入字节数组,避免中间字符串转换)
 | 
			
		||||
            byte[] encryptedData = utils.encryptWithPublicKey(szInfo, publicKey);
 | 
			
		||||
 | 
			
		||||
            // 保存加密字节数组到文件(直接操作字节,无需转字符串)
 | 
			
		||||
            FileUtils.writeByteArrayToFile(encryptedData, mUserInfoModelPath);
 | 
			
		||||
            LogUtils.d(TAG, "加密数据已保存");
 | 
			
		||||
 | 
			
		||||
            // 测试解密(仅调试用)
 | 
			
		||||
            String szInfo2 = utils.decryptWithPrivateKey(encryptedData, keyPair.getPrivate());
 | 
			
		||||
            LogUtils.d(TAG, "解密结果: " + szInfo2);
 | 
			
		||||
 | 
			
		||||
            mUserInfoModel = UserInfoModel.parseStringToBean(szInfo2, UserInfoModel.class);
 | 
			
		||||
            if (mUserInfoModel == null) {
 | 
			
		||||
                LogUtils.d(TAG, "模型解析失败");
 | 
			
		||||
            }
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            LogUtils.d(TAG, "加密/解密失败: " + e.getMessage());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 发送 POST 请求(JSON 数据)
 | 
			
		||||
    public void sendPostRequest(String url, String data, OnResponseListener listener) {
 | 
			
		||||
        RequestBody requestBody = RequestBody.create(
 | 
			
		||||
            MediaType.parse("application/json; charset=utf-8"), // 关键头信息
 | 
			
		||||
            data.getBytes(StandardCharsets.UTF_8)
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        Request request = new Request.Builder()
 | 
			
		||||
            .url(url)
 | 
			
		||||
            .post(requestBody)
 | 
			
		||||
            .addHeader("Content-Type", "application/json") // 显式添加头
 | 
			
		||||
            .build();
 | 
			
		||||
 | 
			
		||||
        executeRequest(request, listener);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 发送 GET 请求
 | 
			
		||||
    public void sendGetRequest(String url, OnResponseListener listener) {
 | 
			
		||||
        Request request = new Request.Builder()
 | 
			
		||||
            .url(url)
 | 
			
		||||
            .get()
 | 
			
		||||
            .build();
 | 
			
		||||
        executeRequest(request, listener);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 执行请求(子线程处理)
 | 
			
		||||
    private void executeRequest(final Request request, final OnResponseListener listener) {
 | 
			
		||||
        okHttpClient.newCall(request).enqueue(new Callback() {
 | 
			
		||||
                // 响应成功(子线程)
 | 
			
		||||
                @Override
 | 
			
		||||
                public void onResponse(Call call, Response response) throws IOException {
 | 
			
		||||
                    try {
 | 
			
		||||
                        if (!response.isSuccessful()) {
 | 
			
		||||
                            postFailure(listener, "响应码错误:" + response.code());
 | 
			
		||||
                            return;
 | 
			
		||||
                        }
 | 
			
		||||
                        String responseBody = response.body().string();
 | 
			
		||||
                        postSuccess(listener, responseBody);
 | 
			
		||||
                    } catch (Exception e) {
 | 
			
		||||
                        postFailure(listener, "解析失败:" + e.getMessage());
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // 响应失败(子线程)
 | 
			
		||||
                @Override
 | 
			
		||||
                public void onFailure(Call call, IOException e) {
 | 
			
		||||
                    postFailure(listener, "网络失败:" + e.getMessage());
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // 主线程回调(使用 Handler)
 | 
			
		||||
                private void postSuccess(final OnResponseListener listener, final String msg) {
 | 
			
		||||
                    mainHandler.post(new Runnable() {
 | 
			
		||||
                            @Override
 | 
			
		||||
                            public void run() {
 | 
			
		||||
                                listener.onSuccess(msg);
 | 
			
		||||
                            }
 | 
			
		||||
                        });
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                private void postFailure(final OnResponseListener listener, final String msg) {
 | 
			
		||||
                    mainHandler.post(new Runnable() {
 | 
			
		||||
                            @Override
 | 
			
		||||
                            public void run() {
 | 
			
		||||
                                listener.onFailure(msg);
 | 
			
		||||
                            }
 | 
			
		||||
                        });
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public interface OnResponseListener {
 | 
			
		||||
        /**
 | 
			
		||||
         * 成功响应(主线程回调)
 | 
			
		||||
         * @param responseBody 响应体字符串
 | 
			
		||||
         */
 | 
			
		||||
        void onSuccess(String responseBody);
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * 失败回调(包含错误信息)
 | 
			
		||||
         * @param errorMsg 错误描述
 | 
			
		||||
         */
 | 
			
		||||
        void onFailure(String errorMsg);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										68
									
								
								libappbase/src/main/res/layout/activity_logon.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,68 @@
 | 
			
		||||
<?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">
 | 
			
		||||
 | 
			
		||||
    <LinearLayout
 | 
			
		||||
        android:orientation="horizontal"
 | 
			
		||||
        android:layout_width="match_parent"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:layout_gravity="right"
 | 
			
		||||
        android:gravity="right"
 | 
			
		||||
        android:padding="10dp"
 | 
			
		||||
        android:id="@+id/ll_hostbar">
 | 
			
		||||
 | 
			
		||||
        <RadioButton
 | 
			
		||||
            android:layout_width="wrap_content"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:text="10.8.0.250:456"
 | 
			
		||||
            android:id="@+id/rb_debughost"
 | 
			
		||||
            android:onClick="onSwitchHost"/>
 | 
			
		||||
 | 
			
		||||
        <RadioButton
 | 
			
		||||
            android:layout_width="wrap_content"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:text="yun.winboll.cc"
 | 
			
		||||
            android:id="@+id/rb_yunhost"
 | 
			
		||||
            android:onClick="onSwitchHost"/>
 | 
			
		||||
 | 
			
		||||
	</LinearLayout>
 | 
			
		||||
    
 | 
			
		||||
	<LinearLayout
 | 
			
		||||
		android:orientation="horizontal"
 | 
			
		||||
		android:layout_width="match_parent"
 | 
			
		||||
		android:layout_height="wrap_content"
 | 
			
		||||
		android:gravity="right">
 | 
			
		||||
 | 
			
		||||
		<Button
 | 
			
		||||
			android:layout_width="wrap_content"
 | 
			
		||||
			android:layout_height="wrap_content"
 | 
			
		||||
			android:text="Test RSA"
 | 
			
		||||
            android:onClick="onTestRSA"/>
 | 
			
		||||
 | 
			
		||||
        <Button
 | 
			
		||||
            android:layout_width="wrap_content"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:text="Test Login"
 | 
			
		||||
            android:onClick="onTestLogin"/>
 | 
			
		||||
        
 | 
			
		||||
	</LinearLayout>
 | 
			
		||||
 | 
			
		||||
	<LinearLayout
 | 
			
		||||
		android:orientation="vertical"
 | 
			
		||||
		android:layout_width="match_parent"
 | 
			
		||||
		android:layout_height="0dp"
 | 
			
		||||
		android:layout_weight="1.0">
 | 
			
		||||
 | 
			
		||||
		<cc.winboll.studio.libappbase.LogView
 | 
			
		||||
			android:layout_width="match_parent"
 | 
			
		||||
			android:layout_height="match_parent"
 | 
			
		||||
			android:id="@+id/logview"/>
 | 
			
		||||
 | 
			
		||||
	</LinearLayout>
 | 
			
		||||
 | 
			
		||||
</LinearLayout>
 | 
			
		||||
 | 
			
		||||
@@ -9,7 +9,34 @@
 | 
			
		||||
	<LinearLayout
 | 
			
		||||
		android:orientation="horizontal"
 | 
			
		||||
		android:layout_width="match_parent"
 | 
			
		||||
		android:layout_height="wrap_content">
 | 
			
		||||
		android:layout_height="wrap_content"
 | 
			
		||||
		android:layout_gravity="right"
 | 
			
		||||
		android:gravity="right"
 | 
			
		||||
		android:padding="10dp"
 | 
			
		||||
		android:id="@+id/ll_hostbar">
 | 
			
		||||
 | 
			
		||||
		<RadioButton
 | 
			
		||||
			android:layout_width="wrap_content"
 | 
			
		||||
			android:layout_height="wrap_content"
 | 
			
		||||
			android:text="10.8.0.250:456"
 | 
			
		||||
			android:id="@+id/rb_debughost"
 | 
			
		||||
			android:onClick="onSwitchHost"/>
 | 
			
		||||
 | 
			
		||||
		<RadioButton
 | 
			
		||||
			android:layout_width="wrap_content"
 | 
			
		||||
			android:layout_height="wrap_content"
 | 
			
		||||
			android:text="yun.winboll.cc"
 | 
			
		||||
			android:id="@+id/rb_yunhost"
 | 
			
		||||
			android:onClick="onSwitchHost"/>
 | 
			
		||||
 | 
			
		||||
	</LinearLayout>
 | 
			
		||||
 | 
			
		||||
	<LinearLayout
 | 
			
		||||
		android:orientation="horizontal"
 | 
			
		||||
		android:layout_width="match_parent"
 | 
			
		||||
		android:layout_height="wrap_content"
 | 
			
		||||
		android:layout_gravity="right"
 | 
			
		||||
		android:gravity="right">
 | 
			
		||||
 | 
			
		||||
		<Button
 | 
			
		||||
			android:layout_width="wrap_content"
 | 
			
		||||
@@ -19,5 +46,18 @@
 | 
			
		||||
 | 
			
		||||
	</LinearLayout>
 | 
			
		||||
 | 
			
		||||
	<LinearLayout
 | 
			
		||||
		android:orientation="vertical"
 | 
			
		||||
		android:layout_width="match_parent"
 | 
			
		||||
		android:layout_height="0dp"
 | 
			
		||||
		android:layout_weight="1.0">
 | 
			
		||||
 | 
			
		||||
		<cc.winboll.studio.libappbase.LogView
 | 
			
		||||
			android:layout_width="match_parent"
 | 
			
		||||
			android:layout_height="match_parent"
 | 
			
		||||
			android:id="@+id/logview"/>
 | 
			
		||||
 | 
			
		||||
	</LinearLayout>
 | 
			
		||||
 | 
			
		||||
</LinearLayout>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								numtable/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1 @@
 | 
			
		||||
/build
 | 
			
		||||
							
								
								
									
										34
									
								
								numtable/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,34 @@
 | 
			
		||||
# NumTable
 | 
			
		||||
 | 
			
		||||
#### 介绍
 | 
			
		||||
桌面图标多元应用。提供一个数字风格化的桌面标识图标,快捷的桌面标识创建途径。主要应用于桌面繁多时的页面环境辅助识别。
 | 
			
		||||
 | 
			
		||||
#### 软件架构
 | 
			
		||||
适配安卓应用 [AIDE Pro] 的 Gradle 编译结构。
 | 
			
		||||
也适配安卓应用 [AndroidIDE] 的 Gradle 编译结构。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### Gradle 编译说明
 | 
			
		||||
调试版编译命令 :gradle assembleBetaDebug
 | 
			
		||||
阶段版编译命令 :bash .winboll/bashPublishAPKAddTag.sh numtable
 | 
			
		||||
 | 
			
		||||
#### 使用说明
 | 
			
		||||
 | 
			
		||||
#### 参与贡献
 | 
			
		||||
 | 
			
		||||
1.  Fork 本仓库
 | 
			
		||||
2.  新建 Feat_xxx 分支
 | 
			
		||||
3.  提交代码 : ZhanGSKen(ZhanGSKen<zhangsken@188.com>)
 | 
			
		||||
4.  新建 Pull Request
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 特技
 | 
			
		||||
 | 
			
		||||
1.  使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
 | 
			
		||||
2.  Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
 | 
			
		||||
3.  你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
 | 
			
		||||
4.  [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
 | 
			
		||||
5.  Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
 | 
			
		||||
6.  Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
 | 
			
		||||
 | 
			
		||||
#### 参考文档
 | 
			
		||||
							
								
								
									
										1
									
								
								numtable/app_update_description.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1 @@
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										73
									
								
								numtable/build.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,73 @@
 | 
			
		||||
apply plugin: 'com.android.application'
 | 
			
		||||
apply from: '../.winboll/winboll_app_build.gradle'
 | 
			
		||||
apply from: '../.winboll/winboll_lint_build.gradle'
 | 
			
		||||
 | 
			
		||||
def genVersionName(def versionName){
 | 
			
		||||
    // 检查编译标志位配置
 | 
			
		||||
    assert (winbollBuildProps['stageCount'] != null)
 | 
			
		||||
    assert (winbollBuildProps['baseVersion'] != null)
 | 
			
		||||
    // 保存基础版本号
 | 
			
		||||
    winbollBuildProps.setProperty("baseVersion", "${versionName}");
 | 
			
		||||
    //保存编译标志配置
 | 
			
		||||
    FileOutputStream fos = new FileOutputStream(winbollBuildPropsFile)
 | 
			
		||||
    winbollBuildProps.store(fos, "${winbollBuildPropsDesc}");
 | 
			
		||||
    fos.close();
 | 
			
		||||
    
 | 
			
		||||
    // 返回编译版本号
 | 
			
		||||
    return "${versionName}." + winbollBuildProps['stageCount']
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
android {
 | 
			
		||||
    compileSdkVersion 32
 | 
			
		||||
    buildToolsVersion "32.0.0"
 | 
			
		||||
 | 
			
		||||
    defaultConfig {
 | 
			
		||||
        applicationId "cc.winboll.studio.numtable"
 | 
			
		||||
        minSdkVersion 24
 | 
			
		||||
        targetSdkVersion 30
 | 
			
		||||
        versionCode 1
 | 
			
		||||
        // versionName 更新后需要手动设置 
 | 
			
		||||
        // .winboll/winbollBuildProps.properties 文件的 stageCount=0
 | 
			
		||||
        // Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
 | 
			
		||||
        versionName "15.1"
 | 
			
		||||
        if(true) {
 | 
			
		||||
            versionName = genVersionName("${versionName}")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    buildTypes {
 | 
			
		||||
        release {
 | 
			
		||||
            minifyEnabled false
 | 
			
		||||
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    api fileTree(dir: 'libs', include: ['*.jar'])
 | 
			
		||||
    
 | 
			
		||||
    // SSH
 | 
			
		||||
    api 'com.jcraft:jsch:0.1.55'
 | 
			
		||||
    // Html 解析
 | 
			
		||||
    api 'org.jsoup:jsoup:1.13.1'
 | 
			
		||||
    // 二维码类库
 | 
			
		||||
    api 'com.google.zxing:core:3.4.1'
 | 
			
		||||
    api 'com.journeyapps:zxing-android-embedded:3.6.0'
 | 
			
		||||
    // 应用介绍页类库
 | 
			
		||||
    api 'io.github.medyo:android-about-page:2.0.0'
 | 
			
		||||
    // 吐司类库
 | 
			
		||||
    api 'com.github.getActivity:ToastUtils:10.5'
 | 
			
		||||
    // 网络连接类库
 | 
			
		||||
    api 'com.squareup.okhttp3:okhttp:4.4.1'
 | 
			
		||||
    // AndroidX 类库
 | 
			
		||||
    api 'androidx.appcompat:appcompat:1.1.0'
 | 
			
		||||
    api 'com.google.android.material:material:1.4.0'
 | 
			
		||||
    //api 'androidx.viewpager:viewpager:1.0.0'
 | 
			
		||||
    //api 'androidx.vectordrawable:vectordrawable:1.1.0'
 | 
			
		||||
    //api 'androidx.vectordrawable:vectordrawable-animated:1.1.0'
 | 
			
		||||
    //api 'androidx.fragment:fragment:1.1.0'
 | 
			
		||||
    
 | 
			
		||||
    api 'cc.winboll.studio:libaes:15.8.0'
 | 
			
		||||
    api 'cc.winboll.studio:libapputils:15.8.2'
 | 
			
		||||
    api 'cc.winboll.studio:libappbase:15.8.2'
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								numtable/build.properties
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,8 @@
 | 
			
		||||
#Created by .winboll/winboll_app_build.gradle
 | 
			
		||||
#Sun Jun 08 21:21:11 HKT 2025
 | 
			
		||||
stageCount=1
 | 
			
		||||
libraryProject=
 | 
			
		||||
baseVersion=15.1
 | 
			
		||||
publishVersion=15.1.0
 | 
			
		||||
buildCount=0
 | 
			
		||||
baseBetaVersion=15.1.1
 | 
			
		||||
							
								
								
									
										21
									
								
								numtable/proguard-rules.pro
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,21 @@
 | 
			
		||||
# Add project specific ProGuard rules here.
 | 
			
		||||
# You can control the set of applied configuration files using the
 | 
			
		||||
# proguardFiles setting in build.gradle.
 | 
			
		||||
#
 | 
			
		||||
# For more details, see
 | 
			
		||||
#   http://developer.android.com/guide/developing/tools/proguard.html
 | 
			
		||||
 | 
			
		||||
# If your project uses WebView with JS, uncomment the following
 | 
			
		||||
# and specify the fully qualified class name to the JavaScript interface
 | 
			
		||||
# class:
 | 
			
		||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
 | 
			
		||||
#   public *;
 | 
			
		||||
#}
 | 
			
		||||
 | 
			
		||||
# Uncomment this to preserve the line number information for
 | 
			
		||||
# debugging stack traces.
 | 
			
		||||
#-keepattributes SourceFile,LineNumberTable
 | 
			
		||||
 | 
			
		||||
# If you keep the line number information, uncomment this to
 | 
			
		||||
# hide the original source file name.
 | 
			
		||||
#-renamesourcefileattribute SourceFile
 | 
			
		||||
							
								
								
									
										14
									
								
								numtable/src/beta/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,14 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:tools="http://schemas.android.com/tools" >
 | 
			
		||||
 | 
			
		||||
    <application
 | 
			
		||||
        tools:replace="android:icon"
 | 
			
		||||
        android:icon="@drawable/ic_launcher_beta">
 | 
			
		||||
 | 
			
		||||
        <!-- Put flavor specific code here -->
 | 
			
		||||
 | 
			
		||||
    </application>
 | 
			
		||||
 | 
			
		||||
</manifest>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										6
									
								
								numtable/src/beta/res/values/strings.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,6 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
 | 
			
		||||
    <string name="app_name">NumTable +</string>
 | 
			
		||||
 | 
			
		||||
</resources>
 | 
			
		||||
							
								
								
									
										37
									
								
								numtable/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,37 @@
 | 
			
		||||
<?xml version='1.0' encoding='utf-8'?>
 | 
			
		||||
<manifest
 | 
			
		||||
    xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    package="cc.winboll.studio.numtable">
 | 
			
		||||
 | 
			
		||||
    <application
 | 
			
		||||
        android:allowBackup="true"
 | 
			
		||||
        android:icon="@drawable/ic_launcher"
 | 
			
		||||
        android:roundIcon="@drawable/ic_launcher"
 | 
			
		||||
        android:label="@string/app_name"
 | 
			
		||||
        android:theme="@style/MyAppTheme"
 | 
			
		||||
        android:resizeableActivity="true"
 | 
			
		||||
        android:name=".App">
 | 
			
		||||
 | 
			
		||||
        <activity
 | 
			
		||||
            android:name=".MainActivity"
 | 
			
		||||
            android:label="@string/app_name">
 | 
			
		||||
 | 
			
		||||
            <intent-filter>
 | 
			
		||||
 | 
			
		||||
                <action android:name="android.intent.action.MAIN"/>
 | 
			
		||||
 | 
			
		||||
                <category android:name="android.intent.category.LAUNCHER"/>
 | 
			
		||||
 | 
			
		||||
            </intent-filter>
 | 
			
		||||
 | 
			
		||||
        </activity>
 | 
			
		||||
 | 
			
		||||
        <meta-data
 | 
			
		||||
            android:name="android.max_aspect"
 | 
			
		||||
            android:value="4.0"/>
 | 
			
		||||
 | 
			
		||||
        <activity android:name=".GlobalApplication$CrashActivity"/>
 | 
			
		||||
 | 
			
		||||
    </application>
 | 
			
		||||
 | 
			
		||||
</manifest>
 | 
			
		||||
							
								
								
									
										345
									
								
								numtable/src/main/java/cc/winboll/studio/numtable/App.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,345 @@
 | 
			
		||||
package cc.winboll.studio.numtable;
 | 
			
		||||
 | 
			
		||||
import android.app.Activity;
 | 
			
		||||
import android.content.ClipData;
 | 
			
		||||
import android.content.ClipboardManager;
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import android.content.Intent;
 | 
			
		||||
import android.content.pm.PackageInfo;
 | 
			
		||||
import android.content.res.Resources;
 | 
			
		||||
import android.graphics.Typeface;
 | 
			
		||||
import android.os.Build;
 | 
			
		||||
import android.os.Bundle;
 | 
			
		||||
import android.os.Handler;
 | 
			
		||||
import android.os.Looper;
 | 
			
		||||
import android.text.TextUtils;
 | 
			
		||||
import android.util.Log;
 | 
			
		||||
import android.view.Gravity;
 | 
			
		||||
import android.view.Menu;
 | 
			
		||||
import android.view.MenuItem;
 | 
			
		||||
import android.view.ViewGroup;
 | 
			
		||||
import android.widget.HorizontalScrollView;
 | 
			
		||||
import android.widget.ScrollView;
 | 
			
		||||
import android.widget.TextView;
 | 
			
		||||
import android.widget.Toast;
 | 
			
		||||
import cc.winboll.studio.libappbase.GlobalApplication;
 | 
			
		||||
import com.hjq.toast.ToastUtils;
 | 
			
		||||
import com.hjq.toast.style.WhiteToastStyle;
 | 
			
		||||
import java.io.ByteArrayInputStream;
 | 
			
		||||
import java.io.ByteArrayOutputStream;
 | 
			
		||||
import java.io.Closeable;
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.FileInputStream;
 | 
			
		||||
import java.io.FileOutputStream;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.io.InputStream;
 | 
			
		||||
import java.io.OutputStream;
 | 
			
		||||
import java.lang.Thread.UncaughtExceptionHandler;
 | 
			
		||||
import java.text.DateFormat;
 | 
			
		||||
import java.text.SimpleDateFormat;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.Date;
 | 
			
		||||
import java.util.LinkedHashMap;
 | 
			
		||||
import java.util.concurrent.atomic.AtomicBoolean;
 | 
			
		||||
 | 
			
		||||
public class App extends GlobalApplication {
 | 
			
		||||
 | 
			
		||||
    private static Handler MAIN_HANDLER = new Handler(Looper.getMainLooper());
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onCreate() {
 | 
			
		||||
        super.onCreate();
 | 
			
		||||
        
 | 
			
		||||
        // 初始化 Toast 框架
 | 
			
		||||
        ToastUtils.init(this);
 | 
			
		||||
        // 设置 Toast 布局样式
 | 
			
		||||
        //ToastUtils.setView(R.layout.view_toast);
 | 
			
		||||
        ToastUtils.setStyle(new WhiteToastStyle());
 | 
			
		||||
        ToastUtils.setGravity(Gravity.BOTTOM, 0, 200);
 | 
			
		||||
        
 | 
			
		||||
        //CrashHandler.getInstance().registerGlobal(this);
 | 
			
		||||
        //CrashHandler.getInstance().registerPart(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void write(InputStream input, OutputStream output) throws IOException {
 | 
			
		||||
        byte[] buf = new byte[1024 * 8];
 | 
			
		||||
        int len;
 | 
			
		||||
        while ((len = input.read(buf)) != -1) {
 | 
			
		||||
            output.write(buf, 0, len);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void write(File file, byte[] data) throws IOException {
 | 
			
		||||
        File parent = file.getParentFile();
 | 
			
		||||
        if (parent != null && !parent.exists()) parent.mkdirs();
 | 
			
		||||
 | 
			
		||||
        ByteArrayInputStream input = new ByteArrayInputStream(data);
 | 
			
		||||
        FileOutputStream output = new FileOutputStream(file);
 | 
			
		||||
        try {
 | 
			
		||||
            write(input, output);
 | 
			
		||||
        } finally {
 | 
			
		||||
            closeIO(input, output);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static String toString(InputStream input) throws IOException {
 | 
			
		||||
        ByteArrayOutputStream output = new ByteArrayOutputStream();
 | 
			
		||||
        write(input, output);
 | 
			
		||||
        try {
 | 
			
		||||
            return output.toString("UTF-8");
 | 
			
		||||
        } finally {
 | 
			
		||||
            closeIO(input, output);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void closeIO(Closeable... closeables) {
 | 
			
		||||
        for (Closeable closeable : closeables) {
 | 
			
		||||
            try {
 | 
			
		||||
                if (closeable != null) closeable.close();
 | 
			
		||||
            } catch (IOException ignored) {}
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static class CrashHandler {
 | 
			
		||||
 | 
			
		||||
        public static final UncaughtExceptionHandler DEFAULT_UNCAUGHT_EXCEPTION_HANDLER = Thread.getDefaultUncaughtExceptionHandler();
 | 
			
		||||
 | 
			
		||||
        private static CrashHandler sInstance;
 | 
			
		||||
 | 
			
		||||
        private PartCrashHandler mPartCrashHandler;
 | 
			
		||||
 | 
			
		||||
        public static CrashHandler getInstance() {
 | 
			
		||||
            if (sInstance == null) {
 | 
			
		||||
                sInstance = new CrashHandler();
 | 
			
		||||
            }
 | 
			
		||||
            return sInstance;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void registerGlobal(Context context) {
 | 
			
		||||
            registerGlobal(context, null);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void registerGlobal(Context context, String crashDir) {
 | 
			
		||||
            Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandlerImpl(context.getApplicationContext(), crashDir));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void unregister() {
 | 
			
		||||
            Thread.setDefaultUncaughtExceptionHandler(DEFAULT_UNCAUGHT_EXCEPTION_HANDLER);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void registerPart(Context context) {
 | 
			
		||||
            unregisterPart(context);
 | 
			
		||||
            mPartCrashHandler = new PartCrashHandler(context.getApplicationContext());
 | 
			
		||||
            MAIN_HANDLER.postAtFrontOfQueue(mPartCrashHandler);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void unregisterPart(Context context) {
 | 
			
		||||
            if (mPartCrashHandler != null) {
 | 
			
		||||
                mPartCrashHandler.isRunning.set(false);
 | 
			
		||||
                mPartCrashHandler = null;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static class PartCrashHandler implements Runnable {
 | 
			
		||||
 | 
			
		||||
            private final Context mContext;
 | 
			
		||||
 | 
			
		||||
            public AtomicBoolean isRunning = new AtomicBoolean(true);
 | 
			
		||||
 | 
			
		||||
            public PartCrashHandler(Context context) {
 | 
			
		||||
                this.mContext = context;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public void run() {
 | 
			
		||||
                while (isRunning.get()) {
 | 
			
		||||
                    try {
 | 
			
		||||
                        Looper.loop();
 | 
			
		||||
                    } catch (final Throwable e) {
 | 
			
		||||
                        e.printStackTrace();
 | 
			
		||||
                        if (isRunning.get()) {
 | 
			
		||||
                            MAIN_HANDLER.post(new Runnable(){
 | 
			
		||||
 | 
			
		||||
                                    @Override
 | 
			
		||||
                                    public void run() {
 | 
			
		||||
                                        Toast.makeText(mContext, e.toString(), Toast.LENGTH_LONG).show();
 | 
			
		||||
                                    }
 | 
			
		||||
                                });
 | 
			
		||||
                        } else {
 | 
			
		||||
                            if (e instanceof RuntimeException) {
 | 
			
		||||
                                throw (RuntimeException)e;
 | 
			
		||||
                            } else {
 | 
			
		||||
                                throw new RuntimeException(e);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static class UncaughtExceptionHandlerImpl implements UncaughtExceptionHandler {
 | 
			
		||||
 | 
			
		||||
            private static DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy_MM_dd-HH_mm_ss");
 | 
			
		||||
 | 
			
		||||
            private final Context mContext;
 | 
			
		||||
 | 
			
		||||
            private final File mCrashDir;
 | 
			
		||||
 | 
			
		||||
            public UncaughtExceptionHandlerImpl(Context context, String crashDir) {
 | 
			
		||||
                this.mContext = context;
 | 
			
		||||
                this.mCrashDir = TextUtils.isEmpty(crashDir) ? new File(mContext.getExternalCacheDir(), "crash") : new File(crashDir);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public void uncaughtException(Thread thread, Throwable throwable) {
 | 
			
		||||
                try {
 | 
			
		||||
 | 
			
		||||
                    String log = buildLog(throwable);
 | 
			
		||||
                    writeLog(log);
 | 
			
		||||
 | 
			
		||||
                    try {
 | 
			
		||||
                        Intent intent = new Intent(mContext, CrashActivity.class);
 | 
			
		||||
                        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 | 
			
		||||
                        intent.putExtra(Intent.EXTRA_TEXT, log);
 | 
			
		||||
                        mContext.startActivity(intent);
 | 
			
		||||
                    } catch (Throwable e) {
 | 
			
		||||
                        e.printStackTrace();
 | 
			
		||||
                        writeLog(e.toString());
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    throwable.printStackTrace();
 | 
			
		||||
                    android.os.Process.killProcess(android.os.Process.myPid());
 | 
			
		||||
                    System.exit(0);
 | 
			
		||||
 | 
			
		||||
                } catch (Throwable e) {
 | 
			
		||||
                    if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null) DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(thread, throwable);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            private String buildLog(Throwable throwable) {
 | 
			
		||||
                String time = DATE_FORMAT.format(new Date());
 | 
			
		||||
 | 
			
		||||
                String versionName = "unknown";
 | 
			
		||||
                long versionCode = 0;
 | 
			
		||||
                try {
 | 
			
		||||
                    PackageInfo packageInfo = mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0);
 | 
			
		||||
                    versionName = packageInfo.versionName;
 | 
			
		||||
                    versionCode = Build.VERSION.SDK_INT >= 28 ? packageInfo.getLongVersionCode() : packageInfo.versionCode;
 | 
			
		||||
                } catch (Throwable ignored) {}
 | 
			
		||||
 | 
			
		||||
                LinkedHashMap<String, String> head = new LinkedHashMap<String, String>();
 | 
			
		||||
                head.put("Time Of Crash", time);
 | 
			
		||||
                head.put("Device", String.format("%s, %s", Build.MANUFACTURER, Build.MODEL));
 | 
			
		||||
                head.put("Android Version", String.format("%s (%d)", Build.VERSION.RELEASE, Build.VERSION.SDK_INT));
 | 
			
		||||
                head.put("App Version", String.format("%s (%d)", versionName, versionCode));
 | 
			
		||||
                head.put("Kernel", getKernel());
 | 
			
		||||
                head.put("Support Abis", Build.VERSION.SDK_INT >= 21 && Build.SUPPORTED_ABIS != null ? Arrays.toString(Build.SUPPORTED_ABIS): "unknown");
 | 
			
		||||
                head.put("Fingerprint", Build.FINGERPRINT);
 | 
			
		||||
 | 
			
		||||
                StringBuilder builder = new StringBuilder();
 | 
			
		||||
 | 
			
		||||
                for (String key : head.keySet()) {
 | 
			
		||||
                    if (builder.length() != 0) builder.append("\n");
 | 
			
		||||
                    builder.append(key);
 | 
			
		||||
                    builder.append(" :    ");
 | 
			
		||||
                    builder.append(head.get(key));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                builder.append("\n\n");
 | 
			
		||||
                builder.append(Log.getStackTraceString(throwable));
 | 
			
		||||
 | 
			
		||||
                return builder.toString(); 
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            private void writeLog(String log) {
 | 
			
		||||
                String time = DATE_FORMAT.format(new Date());
 | 
			
		||||
                File file = new File(mCrashDir, "crash_" + time + ".txt");
 | 
			
		||||
                try {
 | 
			
		||||
                    write(file, log.getBytes("UTF-8"));
 | 
			
		||||
                } catch (Throwable e) {
 | 
			
		||||
                    e.printStackTrace();
 | 
			
		||||
                } 
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            private static String getKernel() {
 | 
			
		||||
                try {
 | 
			
		||||
                    return App.toString(new FileInputStream("/proc/version")).trim();
 | 
			
		||||
                } catch (Throwable e) {
 | 
			
		||||
                    return e.getMessage();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static final class CrashActivity extends Activity {
 | 
			
		||||
 | 
			
		||||
        private String mLog;
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        protected void onCreate(Bundle savedInstanceState) {
 | 
			
		||||
            super.onCreate(savedInstanceState);
 | 
			
		||||
 | 
			
		||||
            setTheme(android.R.style.Theme_DeviceDefault);
 | 
			
		||||
            setTitle("App Crash");
 | 
			
		||||
 | 
			
		||||
            mLog = getIntent().getStringExtra(Intent.EXTRA_TEXT);
 | 
			
		||||
 | 
			
		||||
            ScrollView contentView = new ScrollView(this);
 | 
			
		||||
            contentView.setFillViewport(true);
 | 
			
		||||
 | 
			
		||||
            HorizontalScrollView horizontalScrollView = new HorizontalScrollView(this);
 | 
			
		||||
 | 
			
		||||
            TextView textView = new TextView(this);
 | 
			
		||||
            int padding = dp2px(16);
 | 
			
		||||
            textView.setPadding(padding, padding, padding, padding);
 | 
			
		||||
            textView.setText(mLog);
 | 
			
		||||
            textView.setTextIsSelectable(true);
 | 
			
		||||
            textView.setTypeface(Typeface.DEFAULT);
 | 
			
		||||
            textView.setLinksClickable(true);
 | 
			
		||||
 | 
			
		||||
            horizontalScrollView.addView(textView);
 | 
			
		||||
            contentView.addView(horizontalScrollView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
 | 
			
		||||
 | 
			
		||||
            setContentView(contentView);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void restart() {
 | 
			
		||||
            Intent intent = getPackageManager().getLaunchIntentForPackage(getPackageName());
 | 
			
		||||
            if (intent != null) {
 | 
			
		||||
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 | 
			
		||||
                startActivity(intent);
 | 
			
		||||
            }
 | 
			
		||||
            finish();
 | 
			
		||||
            android.os.Process.killProcess(android.os.Process.myPid());
 | 
			
		||||
            System.exit(0);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static int dp2px(float dpValue) {
 | 
			
		||||
            final float scale = Resources.getSystem().getDisplayMetrics().density;
 | 
			
		||||
            return (int) (dpValue * scale + 0.5f);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public boolean onCreateOptionsMenu(Menu menu) {
 | 
			
		||||
            menu.add(0, android.R.id.copy, 0, android.R.string.copy)
 | 
			
		||||
                .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
 | 
			
		||||
            return super.onCreateOptionsMenu(menu);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public boolean onOptionsItemSelected(MenuItem item) {
 | 
			
		||||
            switch (item.getItemId()) {
 | 
			
		||||
                case android.R.id.copy:
 | 
			
		||||
                    ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
 | 
			
		||||
                    cm.setPrimaryClip(ClipData.newPlainText(getPackageName(), mLog));
 | 
			
		||||
                    return true;
 | 
			
		||||
            }
 | 
			
		||||
            return super.onOptionsItemSelected(item);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onBackPressed() {
 | 
			
		||||
            restart();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,31 @@
 | 
			
		||||
package cc.winboll.studio.numtable;
 | 
			
		||||
 | 
			
		||||
import android.os.Bundle;
 | 
			
		||||
import androidx.appcompat.app.AppCompatActivity;
 | 
			
		||||
import androidx.appcompat.widget.Toolbar;
 | 
			
		||||
import cc.winboll.studio.libappbase.LogView;
 | 
			
		||||
import com.hjq.toast.ToastUtils;
 | 
			
		||||
 | 
			
		||||
public class MainActivity extends AppCompatActivity {
 | 
			
		||||
 | 
			
		||||
    LogView mLogView;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onCreate(Bundle savedInstanceState) {
 | 
			
		||||
        super.onCreate(savedInstanceState);
 | 
			
		||||
        setContentView(R.layout.activity_main);
 | 
			
		||||
 | 
			
		||||
		Toolbar toolbar=(Toolbar)findViewById(R.id.toolbar);
 | 
			
		||||
		setSupportActionBar(toolbar);
 | 
			
		||||
 | 
			
		||||
        mLogView = findViewById(R.id.logview);
 | 
			
		||||
        
 | 
			
		||||
        ToastUtils.show("onCreate");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onResume() {
 | 
			
		||||
        super.onResume();
 | 
			
		||||
        mLogView.start();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,34 @@
 | 
			
		||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:aapt="http://schemas.android.com/aapt"
 | 
			
		||||
    android:width="108dp"
 | 
			
		||||
    android:height="108dp"
 | 
			
		||||
    android:viewportHeight="108"
 | 
			
		||||
    android:viewportWidth="108">
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillType="evenOdd"
 | 
			
		||||
        android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
 | 
			
		||||
        android:strokeColor="#00000000"
 | 
			
		||||
        android:strokeWidth="1">
 | 
			
		||||
        <aapt:attr name="android:fillColor">
 | 
			
		||||
            <gradient
 | 
			
		||||
                android:endX="78.5885"
 | 
			
		||||
                android:endY="90.9159"
 | 
			
		||||
                android:startX="48.7653"
 | 
			
		||||
                android:startY="61.0927"
 | 
			
		||||
                android:type="linear">
 | 
			
		||||
                <item
 | 
			
		||||
                    android:color="#44000000"
 | 
			
		||||
                    android:offset="0.0" />
 | 
			
		||||
                <item
 | 
			
		||||
                    android:color="#00000000"
 | 
			
		||||
                    android:offset="1.0" />
 | 
			
		||||
            </gradient>
 | 
			
		||||
        </aapt:attr>
 | 
			
		||||
    </path>
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#FFFFFF"
 | 
			
		||||
        android:fillType="nonZero"
 | 
			
		||||
        android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
 | 
			
		||||
        android:strokeColor="#00000000"
 | 
			
		||||
        android:strokeWidth="1" />
 | 
			
		||||
</vector>
 | 
			
		||||
							
								
								
									
										13
									
								
								numtable/src/main/res/drawable/ic_launcher.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,13 @@
 | 
			
		||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    android:width="512dp"
 | 
			
		||||
    android:height="512dp"
 | 
			
		||||
    android:viewportWidth="512"
 | 
			
		||||
    android:viewportHeight="512">
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#FF3D8A1C"
 | 
			
		||||
        android:strokeColor="#FFF5DD00"
 | 
			
		||||
        android:strokeWidth="20.0"
 | 
			
		||||
        android:strokeLineCap="round"
 | 
			
		||||
        android:strokeMiterLimit="10"
 | 
			
		||||
        android:pathData="M47.53 417.75C38.23 258.17 97.09 94.95 156.27 94.16 211.66 98.38 260.9 237.99 329.08 242.47 351.54 241.69 397.94 228.17 449.46 88.59 447.52 262.11 394.97 417.45 338.71 418.74 279.06 412.93 220.17 297.22 157.81 295.26 132.98 294.7 116.37 308.69 47.53 417.75Z"/>
 | 
			
		||||
</vector>
 | 
			
		||||
							
								
								
									
										170
									
								
								numtable/src/main/res/drawable/ic_launcher_background.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,170 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    android:width="108dp"
 | 
			
		||||
    android:height="108dp"
 | 
			
		||||
    android:viewportHeight="108"
 | 
			
		||||
    android:viewportWidth="108">
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#FFFBC41E"
 | 
			
		||||
        android:pathData="M0,0h108v108h-108z" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M9,0L9,108"
 | 
			
		||||
        android:strokeColor="#33FFFFFF"
 | 
			
		||||
        android:strokeWidth="0.8" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M19,0L19,108"
 | 
			
		||||
        android:strokeColor="#33FFFFFF"
 | 
			
		||||
        android:strokeWidth="0.8" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M29,0L29,108"
 | 
			
		||||
        android:strokeColor="#33FFFFFF"
 | 
			
		||||
        android:strokeWidth="0.8" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M39,0L39,108"
 | 
			
		||||
        android:strokeColor="#33FFFFFF"
 | 
			
		||||
        android:strokeWidth="0.8" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M49,0L49,108"
 | 
			
		||||
        android:strokeColor="#33FFFFFF"
 | 
			
		||||
        android:strokeWidth="0.8" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M59,0L59,108"
 | 
			
		||||
        android:strokeColor="#33FFFFFF"
 | 
			
		||||
        android:strokeWidth="0.8" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M69,0L69,108"
 | 
			
		||||
        android:strokeColor="#33FFFFFF"
 | 
			
		||||
        android:strokeWidth="0.8" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M79,0L79,108"
 | 
			
		||||
        android:strokeColor="#33FFFFFF"
 | 
			
		||||
        android:strokeWidth="0.8" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M89,0L89,108"
 | 
			
		||||
        android:strokeColor="#33FFFFFF"
 | 
			
		||||
        android:strokeWidth="0.8" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M99,0L99,108"
 | 
			
		||||
        android:strokeColor="#33FFFFFF"
 | 
			
		||||
        android:strokeWidth="0.8" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M0,9L108,9"
 | 
			
		||||
        android:strokeColor="#33FFFFFF"
 | 
			
		||||
        android:strokeWidth="0.8" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M0,19L108,19"
 | 
			
		||||
        android:strokeColor="#33FFFFFF"
 | 
			
		||||
        android:strokeWidth="0.8" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M0,29L108,29"
 | 
			
		||||
        android:strokeColor="#33FFFFFF"
 | 
			
		||||
        android:strokeWidth="0.8" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M0,39L108,39"
 | 
			
		||||
        android:strokeColor="#33FFFFFF"
 | 
			
		||||
        android:strokeWidth="0.8" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M0,49L108,49"
 | 
			
		||||
        android:strokeColor="#33FFFFFF"
 | 
			
		||||
        android:strokeWidth="0.8" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M0,59L108,59"
 | 
			
		||||
        android:strokeColor="#33FFFFFF"
 | 
			
		||||
        android:strokeWidth="0.8" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M0,69L108,69"
 | 
			
		||||
        android:strokeColor="#33FFFFFF"
 | 
			
		||||
        android:strokeWidth="0.8" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M0,79L108,79"
 | 
			
		||||
        android:strokeColor="#33FFFFFF"
 | 
			
		||||
        android:strokeWidth="0.8" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M0,89L108,89"
 | 
			
		||||
        android:strokeColor="#33FFFFFF"
 | 
			
		||||
        android:strokeWidth="0.8" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M0,99L108,99"
 | 
			
		||||
        android:strokeColor="#33FFFFFF"
 | 
			
		||||
        android:strokeWidth="0.8" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M19,29L89,29"
 | 
			
		||||
        android:strokeColor="#33FFFFFF"
 | 
			
		||||
        android:strokeWidth="0.8" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M19,39L89,39"
 | 
			
		||||
        android:strokeColor="#33FFFFFF"
 | 
			
		||||
        android:strokeWidth="0.8" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M19,49L89,49"
 | 
			
		||||
        android:strokeColor="#33FFFFFF"
 | 
			
		||||
        android:strokeWidth="0.8" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M19,59L89,59"
 | 
			
		||||
        android:strokeColor="#33FFFFFF"
 | 
			
		||||
        android:strokeWidth="0.8" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M19,69L89,69"
 | 
			
		||||
        android:strokeColor="#33FFFFFF"
 | 
			
		||||
        android:strokeWidth="0.8" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M19,79L89,79"
 | 
			
		||||
        android:strokeColor="#33FFFFFF"
 | 
			
		||||
        android:strokeWidth="0.8" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M29,19L29,89"
 | 
			
		||||
        android:strokeColor="#33FFFFFF"
 | 
			
		||||
        android:strokeWidth="0.8" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M39,19L39,89"
 | 
			
		||||
        android:strokeColor="#33FFFFFF"
 | 
			
		||||
        android:strokeWidth="0.8" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M49,19L49,89"
 | 
			
		||||
        android:strokeColor="#33FFFFFF"
 | 
			
		||||
        android:strokeWidth="0.8" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M59,19L59,89"
 | 
			
		||||
        android:strokeColor="#33FFFFFF"
 | 
			
		||||
        android:strokeWidth="0.8" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M69,19L69,89"
 | 
			
		||||
        android:strokeColor="#33FFFFFF"
 | 
			
		||||
        android:strokeWidth="0.8" />
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#00000000"
 | 
			
		||||
        android:pathData="M79,19L79,89"
 | 
			
		||||
        android:strokeColor="#33FFFFFF"
 | 
			
		||||
        android:strokeWidth="0.8" />
 | 
			
		||||
</vector>
 | 
			
		||||
							
								
								
									
										11
									
								
								numtable/src/main/res/drawable/ic_launcher_beta.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,11 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    android:clickable="true">
 | 
			
		||||
    <item android:drawable="@drawable/ic_launcher_background"/> 
 | 
			
		||||
    <item 
 | 
			
		||||
        android:left="15dp" 
 | 
			
		||||
        android:top="15dp" 
 | 
			
		||||
        android:right="15dp" 
 | 
			
		||||
        android:bottom="15dp"
 | 
			
		||||
        android:drawable="@drawable/ic_launcher"/>
 | 
			
		||||
</layer-list>
 | 
			
		||||
							
								
								
									
										51
									
								
								numtable/src/main/res/layout/activity_main.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,51 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<LinearLayout
 | 
			
		||||
	xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
	xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
			
		||||
	android:layout_width="match_parent"
 | 
			
		||||
	android:layout_height="match_parent"
 | 
			
		||||
	android:orientation="vertical">
 | 
			
		||||
 | 
			
		||||
	<com.google.android.material.appbar.AppBarLayout
 | 
			
		||||
		android:layout_width="match_parent"
 | 
			
		||||
		android:layout_height="wrap_content"
 | 
			
		||||
		android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
 | 
			
		||||
 | 
			
		||||
		<androidx.appcompat.widget.Toolbar
 | 
			
		||||
			android:id="@+id/toolbar"
 | 
			
		||||
			android:layout_width="match_parent"
 | 
			
		||||
			android:layout_height="?attr/actionBarSize"
 | 
			
		||||
			app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
 | 
			
		||||
 | 
			
		||||
	</com.google.android.material.appbar.AppBarLayout>
 | 
			
		||||
 | 
			
		||||
	<LinearLayout
 | 
			
		||||
		android:orientation="vertical"
 | 
			
		||||
		android:layout_width="match_parent"
 | 
			
		||||
		android:layout_height="0dp"
 | 
			
		||||
		android:layout_weight="1.0"
 | 
			
		||||
		android:gravity="center_vertical|center_horizontal">
 | 
			
		||||
 | 
			
		||||
		<TextView
 | 
			
		||||
			android:layout_width="wrap_content"
 | 
			
		||||
			android:layout_height="wrap_content"
 | 
			
		||||
			android:text="NumTable"
 | 
			
		||||
			android:textAppearance="?android:attr/textAppearanceLarge"/>
 | 
			
		||||
 | 
			
		||||
	</LinearLayout>
 | 
			
		||||
 | 
			
		||||
	<LinearLayout
 | 
			
		||||
		android:orientation="vertical"
 | 
			
		||||
		android:layout_width="match_parent"
 | 
			
		||||
		android:layout_height="0dp"
 | 
			
		||||
		android:layout_weight="1.0">
 | 
			
		||||
 | 
			
		||||
		<cc.winboll.studio.libappbase.LogView
 | 
			
		||||
			android:layout_width="match_parent"
 | 
			
		||||
			android:layout_height="match_parent"
 | 
			
		||||
			android:id="@+id/logview"/>
 | 
			
		||||
 | 
			
		||||
	</LinearLayout>
 | 
			
		||||
 | 
			
		||||
</LinearLayout>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										5
									
								
								numtable/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,5 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
 | 
			
		||||
    <background android:drawable="@drawable/ic_launcher_background" />
 | 
			
		||||
    <foreground android:drawable="@drawable/ic_launcher_foreground" />
 | 
			
		||||
</adaptive-icon>
 | 
			
		||||
@@ -0,0 +1,5 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
 | 
			
		||||
    <background android:drawable="@drawable/ic_launcher_background" />
 | 
			
		||||
    <foreground android:drawable="@drawable/ic_launcher_foreground" />
 | 
			
		||||
</adaptive-icon>
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								numtable/src/main/res/mipmap-hdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.0 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								numtable/src/main/res/mipmap-hdpi/ic_launcher_round.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.9 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								numtable/src/main/res/mipmap-mdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 2.0 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								numtable/src/main/res/mipmap-mdpi/ic_launcher_round.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 2.8 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								numtable/src/main/res/mipmap-xhdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.5 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								numtable/src/main/res/mipmap-xhdpi/ic_launcher_round.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 6.9 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								numtable/src/main/res/mipmap-xxhdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 6.3 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								numtable/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 10 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								numtable/src/main/res/mipmap-xxxhdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 9.0 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								numtable/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 15 KiB  | 
							
								
								
									
										6
									
								
								numtable/src/main/res/values/colors.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,6 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
    <color name="colorPrimary">#009688</color>
 | 
			
		||||
    <color name="colorPrimaryDark">#00796B</color>
 | 
			
		||||
    <color name="colorAccent">#FF9800</color>
 | 
			
		||||
</resources>
 | 
			
		||||
							
								
								
									
										4
									
								
								numtable/src/main/res/values/strings.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,4 @@
 | 
			
		||||
<resources>
 | 
			
		||||
    <string name="app_name">NumTable</string>
 | 
			
		||||
    
 | 
			
		||||
</resources>
 | 
			
		||||
							
								
								
									
										11
									
								
								numtable/src/main/res/values/styles.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,11 @@
 | 
			
		||||
<resources>
 | 
			
		||||
 | 
			
		||||
    <!-- Base application theme. -->
 | 
			
		||||
    <style name="MyAppTheme" parent="Theme.AppCompat.Light.NoActionBar">
 | 
			
		||||
        <!-- Customize your theme here. -->
 | 
			
		||||
        <item name="colorPrimary">@color/colorPrimary</item>
 | 
			
		||||
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
 | 
			
		||||
        <item name="colorAccent">@color/colorAccent</item>
 | 
			
		||||
    </style>
 | 
			
		||||
 | 
			
		||||
</resources>
 | 
			
		||||
							
								
								
									
										12
									
								
								numtable/src/stage/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,12 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:tools="http://schemas.android.com/tools" >
 | 
			
		||||
 | 
			
		||||
    <application>
 | 
			
		||||
 | 
			
		||||
        <!-- Put flavor specific code here -->
 | 
			
		||||
 | 
			
		||||
    </application>
 | 
			
		||||
 | 
			
		||||
</manifest>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										6
									
								
								numtable/src/stage/res/values/strings.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,6 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
 | 
			
		||||
    <!-- Put flavor specific strings here -->
 | 
			
		||||
 | 
			
		||||
</resources>
 | 
			
		||||
@@ -45,9 +45,9 @@ android {
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    api fileTree(dir: 'libs', include: ['*.jar'])
 | 
			
		||||
    api 'cc.winboll.studio:libaes:15.6.0'
 | 
			
		||||
    api 'cc.winboll.studio:libapputils:15.3.4'
 | 
			
		||||
    api 'cc.winboll.studio:libappbase:15.7.6'
 | 
			
		||||
    api 'cc.winboll.studio:libaes:15.9.1'
 | 
			
		||||
    api 'cc.winboll.studio:libapputils:15.8.4'
 | 
			
		||||
    api 'cc.winboll.studio:libappbase:15.8.4'
 | 
			
		||||
    
 | 
			
		||||
    // 吐司提示库
 | 
			
		||||
    api 'com.github.getActivity:ToastUtils:10.5'
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
#Created by .winboll/winboll_app_build.gradle
 | 
			
		||||
#Thu May 29 09:43:37 HKT 2025
 | 
			
		||||
stageCount=2
 | 
			
		||||
#Mon Jun 23 20:07:50 HKT 2025
 | 
			
		||||
stageCount=8
 | 
			
		||||
libraryProject=
 | 
			
		||||
baseVersion=15.4
 | 
			
		||||
publishVersion=15.4.1
 | 
			
		||||
publishVersion=15.4.7
 | 
			
		||||
buildCount=0
 | 
			
		||||
baseBetaVersion=15.4.2
 | 
			
		||||
baseBetaVersion=15.4.8
 | 
			
		||||
 
 | 
			
		||||
@@ -121,6 +121,8 @@
 | 
			
		||||
 | 
			
		||||
        <activity android:name="cc.winboll.studio.powerbell.activities.AboutActivity"/>
 | 
			
		||||
 | 
			
		||||
        <activity android:name="cc.winboll.studio.powerbell.activities.PixelPickerActivity"/>
 | 
			
		||||
 | 
			
		||||
    </application>
 | 
			
		||||
 | 
			
		||||
</manifest>
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
package cc.winboll.studio.powerbell;
 | 
			
		||||
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import android.os.Environment;
 | 
			
		||||
import android.view.Gravity;
 | 
			
		||||
import cc.winboll.studio.libappbase.GlobalApplication;
 | 
			
		||||
import cc.winboll.studio.powerbell.receivers.GlobalApplicationReceiver;
 | 
			
		||||
@@ -27,12 +28,21 @@ public class App extends GlobalApplication {
 | 
			
		||||
    public void onCreate() {
 | 
			
		||||
        super.onCreate();
 | 
			
		||||
 | 
			
		||||
        // 初始化临时文件夹目录
 | 
			
		||||
        File fTempDir = new File(getExternalCacheDir(), "TempDir");
 | 
			
		||||
        if(!fTempDir.exists()) {
 | 
			
		||||
            fTempDir.mkdirs();
 | 
			
		||||
        // 临时文件夹方案1
 | 
			
		||||
        // 获取Pictures文件夹路径(Android 10及以上推荐使用MediaStore,此处为传统方式)
 | 
			
		||||
        File picturesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
 | 
			
		||||
        // 定义目标文件路径(在Pictures目录下创建"PowerBell"子文件夹及文件)
 | 
			
		||||
        File powerBellDir = new File(picturesDir, "PowerBell");
 | 
			
		||||
        
 | 
			
		||||
        // 临时文件夹方案2 <图片保存失败>
 | 
			
		||||
        // 获取Pictures文件夹路径(Android 10及以上推荐使用MediaStore,此处为传统方式)
 | 
			
		||||
        //File powerBellDir = getExternalFilesDir("TempDir");
 | 
			
		||||
 | 
			
		||||
        // 先创建文件夹(如果不存在)
 | 
			
		||||
        if (!powerBellDir.exists()) {
 | 
			
		||||
            powerBellDir.mkdirs();
 | 
			
		||||
        }
 | 
			
		||||
        szTempDir = fTempDir.getAbsolutePath();
 | 
			
		||||
        szTempDir = powerBellDir.getAbsolutePath();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        // 初始化 Toast 框架
 | 
			
		||||
 
 | 
			
		||||
@@ -10,23 +10,27 @@ import android.os.Bundle;
 | 
			
		||||
import android.provider.MediaStore;
 | 
			
		||||
import android.view.Menu;
 | 
			
		||||
import android.view.MenuItem;
 | 
			
		||||
import android.widget.RelativeLayout;
 | 
			
		||||
import android.widget.Toast;
 | 
			
		||||
import cc.winboll.studio.libaes.views.AToolbar;
 | 
			
		||||
import cc.winboll.studio.libappbase.LogUtils;
 | 
			
		||||
import cc.winboll.studio.libappbase.LogView;
 | 
			
		||||
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.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;
 | 
			
		||||
 | 
			
		||||
public class MainActivity extends WinBoLLActivity {
 | 
			
		||||
 | 
			
		||||
public class MainActivity extends Activity {
 | 
			
		||||
    public static final String TAG = "MainActivity";
 | 
			
		||||
    public static final int BACKGROUND_PICTURE_REQUEST_CODE = 0;
 | 
			
		||||
 | 
			
		||||
    public static MainActivity _mMainActivity;
 | 
			
		||||
    LogView mLogView;
 | 
			
		||||
    //LogView mLogView;
 | 
			
		||||
    //ArrayList<Fragment> mlistFragment;
 | 
			
		||||
    App mApplication;
 | 
			
		||||
    //AppConfigUtils mAppConfigUtils;
 | 
			
		||||
@@ -35,6 +39,16 @@ public class MainActivity extends Activity {
 | 
			
		||||
    MainViewFragment mMainViewFragment;
 | 
			
		||||
    AToolbar mAToolbar;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Activity getActivity() {
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String getTag() {
 | 
			
		||||
        return TAG;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onCreate(Bundle savedInstanceState) {
 | 
			
		||||
        //LogUtils.d(TAG, "onCreate(...)");
 | 
			
		||||
@@ -42,10 +56,10 @@ public class MainActivity extends Activity {
 | 
			
		||||
        setContentView(R.layout.activity_main);
 | 
			
		||||
 | 
			
		||||
        // 设置调试日志
 | 
			
		||||
        mLogView = findViewById(R.id.logview);
 | 
			
		||||
        mLogView.start();
 | 
			
		||||
        //LogUtils.d(TAG, "LogView Start.");
 | 
			
		||||
        mLogView.updateLogView();
 | 
			
		||||
//        mLogView = findViewById(R.id.logview);
 | 
			
		||||
//        mLogView.start();
 | 
			
		||||
//        //LogUtils.d(TAG, "LogView Start.");
 | 
			
		||||
//        mLogView.updateLogView();
 | 
			
		||||
 | 
			
		||||
        _mMainActivity = MainActivity.this;
 | 
			
		||||
        mApplication = (App) getApplication();
 | 
			
		||||
@@ -117,8 +131,8 @@ public class MainActivity extends Activity {
 | 
			
		||||
        super.onResume();
 | 
			
		||||
        // 回到窗口自动取消提醒消息
 | 
			
		||||
        //NotificationHelper.cancelRemindNotification(this);
 | 
			
		||||
 | 
			
		||||
        reloadBackground();
 | 
			
		||||
		setBackgroundColor();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Menu icons are inflated just as they were with actionbar
 | 
			
		||||
@@ -157,6 +171,8 @@ public class MainActivity extends Activity {
 | 
			
		||||
            Intent intent = new Intent();
 | 
			
		||||
            intent.setClass(this, BackgroundPictureActivity.class);
 | 
			
		||||
            startActivity(intent);
 | 
			
		||||
        } else if (menuItemId == R.id.action_log) {
 | 
			
		||||
            App.getWinBoLLActivityManager().startLogActivity(this);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
@@ -193,4 +209,12 @@ public class MainActivity extends Activity {
 | 
			
		||||
            moveTaskToBack(true);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	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);
 | 
			
		||||
	}
 | 
			
		||||
} 
 | 
			
		||||
 
 | 
			
		||||
@@ -1,22 +1,27 @@
 | 
			
		||||
package cc.winboll.studio.powerbell.activities;
 | 
			
		||||
 | 
			
		||||
import android.Manifest;
 | 
			
		||||
import android.app.Activity;
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import android.content.Intent;
 | 
			
		||||
import android.content.pm.PackageManager;
 | 
			
		||||
import android.graphics.Bitmap;
 | 
			
		||||
import android.graphics.BitmapFactory;
 | 
			
		||||
import android.graphics.drawable.Drawable;
 | 
			
		||||
import android.net.Uri;
 | 
			
		||||
import android.os.Build;
 | 
			
		||||
import android.os.Bundle;
 | 
			
		||||
import android.provider.MediaStore;
 | 
			
		||||
import android.view.View;
 | 
			
		||||
import android.widget.ImageView;
 | 
			
		||||
import android.widget.Toast;
 | 
			
		||||
import android.widget.RelativeLayout;
 | 
			
		||||
import androidx.core.app.ActivityCompat;
 | 
			
		||||
import androidx.core.content.ContextCompat;
 | 
			
		||||
import cc.winboll.studio.libaes.views.AToolbar;
 | 
			
		||||
import cc.winboll.studio.libappbase.LogUtils;
 | 
			
		||||
import cc.winboll.studio.libappbase.utils.ToastUtils;
 | 
			
		||||
import cc.winboll.studio.powerbell.App;
 | 
			
		||||
import cc.winboll.studio.powerbell.R;
 | 
			
		||||
import cc.winboll.studio.powerbell.activities.BackgroundPictureActivity;
 | 
			
		||||
import cc.winboll.studio.powerbell.beans.BackgroundPictureBean;
 | 
			
		||||
import cc.winboll.studio.powerbell.dialogs.BackgroundPicturePreviewDialog;
 | 
			
		||||
import cc.winboll.studio.powerbell.utils.BackgroundPictureUtils;
 | 
			
		||||
@@ -24,42 +29,45 @@ import cc.winboll.studio.powerbell.utils.FileUtils;
 | 
			
		||||
import cc.winboll.studio.powerbell.utils.UriUtil;
 | 
			
		||||
import java.io.BufferedOutputStream;
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.FileNotFoundException;
 | 
			
		||||
import java.io.FileOutputStream;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.io.OutputStream;
 | 
			
		||||
 | 
			
		||||
public class BackgroundPictureActivity extends Activity
 | 
			
		||||
implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
 | 
			
		||||
public class BackgroundPictureActivity extends WinBoLLActivity implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
 | 
			
		||||
 | 
			
		||||
    public static final String TAG = "BackgroundPictureActivity";
 | 
			
		||||
 | 
			
		||||
    public BackgroundPictureUtils mBackgroundPictureUtils;
 | 
			
		||||
 | 
			
		||||
    // 图片选择请求
 | 
			
		||||
    // 图片选择请求码
 | 
			
		||||
    public static final int REQUEST_SELECT_PICTURE = 0;
 | 
			
		||||
    // 照相选择请求
 | 
			
		||||
    public static final int REQUEST_TAKE_PHOTO = 1;
 | 
			
		||||
    // 图片裁剪选择请求
 | 
			
		||||
    public static final int REQUEST_CROP_IMAGE = 2;
 | 
			
		||||
    private static final int STORAGE_PERMISSION_REQUEST = 100;
 | 
			
		||||
 | 
			
		||||
    AToolbar mAToolbar;
 | 
			
		||||
    // 所有图片存储的文件夹
 | 
			
		||||
    File mfBackgroundDir;
 | 
			
		||||
    // 拍照与剪裁的文件夹
 | 
			
		||||
    File mfPictureDir;
 | 
			
		||||
    // 拍照文件类
 | 
			
		||||
    File mfTakePhoto;
 | 
			
		||||
    // 接收到的图片文件类
 | 
			
		||||
    public File mfRecivedPicture;
 | 
			
		||||
    // 剪裁文件类
 | 
			
		||||
    File mfTempCropPicture;
 | 
			
		||||
    // 剪裁接收后的文件的文件名
 | 
			
		||||
    private AToolbar mAToolbar;
 | 
			
		||||
    private File mfBackgroundDir;       // 背景图片存储文件夹
 | 
			
		||||
    private File mfPictureDir;          // 拍照与剪裁临时文件夹
 | 
			
		||||
    private File mfTakePhoto;           // 拍照文件
 | 
			
		||||
    private File mfRecivedPicture;      // 接收的图片文件
 | 
			
		||||
    private File mfTempCropPicture;     // 剪裁临时文件
 | 
			
		||||
    private File mfRecivedCropPicture;  // 剪裁后的目标文件
 | 
			
		||||
 | 
			
		||||
    // 静态变量
 | 
			
		||||
    public static String _mszRecivedCropPicture = "RecivedCrop.jpg";
 | 
			
		||||
    File mfRecivedCropPicture;
 | 
			
		||||
    static String _mszCommonFileType = "jpeg";
 | 
			
		||||
    // 背景图片的压缩比
 | 
			
		||||
    int mnPictureCompress = 100;
 | 
			
		||||
    static String _RecivedPictureFileName;
 | 
			
		||||
    private static String _mszCommonFileType = "jpeg";
 | 
			
		||||
    private int mnPictureCompress = 100;
 | 
			
		||||
    private static String _RecivedPictureFileName;
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public Activity getActivity() {
 | 
			
		||||
		return this;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public String getTag() {
 | 
			
		||||
		return TAG;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onCreate(Bundle savedInstanceState) {
 | 
			
		||||
@@ -67,30 +75,29 @@ implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
 | 
			
		||||
        setContentView(R.layout.activity_backgroundpicture);
 | 
			
		||||
        initEnv();
 | 
			
		||||
 | 
			
		||||
        // 初始化工具类和文件夹
 | 
			
		||||
        mBackgroundPictureUtils = BackgroundPictureUtils.getInstance(this);
 | 
			
		||||
        mfBackgroundDir = new File(mBackgroundPictureUtils.getBackgroundDir());
 | 
			
		||||
        if (!mfBackgroundDir.exists()) {
 | 
			
		||||
            mfBackgroundDir.mkdirs();
 | 
			
		||||
        }
 | 
			
		||||
        //mfPictureDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), getString(R.string.app_projectname));
 | 
			
		||||
 | 
			
		||||
        mfPictureDir = new File(App.getTempDirPath());
 | 
			
		||||
        if (!mfPictureDir.exists()) {
 | 
			
		||||
            mfPictureDir.mkdirs();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 初始化文件对象
 | 
			
		||||
        mfTakePhoto = new File(mfPictureDir, "TakePhoto.jpg");
 | 
			
		||||
        mfTempCropPicture = new File(mfPictureDir, "TempCrop.jpg");
 | 
			
		||||
 | 
			
		||||
        mfRecivedPicture = getRecivedPictureFile(this);
 | 
			
		||||
        mfRecivedCropPicture = new File(mfBackgroundDir, _mszRecivedCropPicture);
 | 
			
		||||
 | 
			
		||||
        // 初始化工具栏
 | 
			
		||||
        mAToolbar = (AToolbar) findViewById(R.id.toolbar);
 | 
			
		||||
        setActionBar(mAToolbar);
 | 
			
		||||
        //mAToolbar.setTitle(getTitle() + "-" + getString(R.string.subtitle_activity_backgroundpicture));
 | 
			
		||||
        mAToolbar.setSubtitle(R.string.subtitle_activity_backgroundpicture);
 | 
			
		||||
        //mAToolbar.setTitleTextAppearance(this, R.style.Toolbar_TitleText);
 | 
			
		||||
        //mAToolbar.setSubtitleTextAppearance(this, R.style.Toolbar_SubTitleText);
 | 
			
		||||
        //mAToolbar.setBackgroundColor(getColor(R.color.colorPrimary));
 | 
			
		||||
        setActionBar(mAToolbar);
 | 
			
		||||
        getActionBar().setDisplayHomeAsUpEnabled(true);
 | 
			
		||||
        mAToolbar.setNavigationOnClickListener(new View.OnClickListener() {
 | 
			
		||||
                @Override
 | 
			
		||||
@@ -99,41 +106,30 @@ implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        //给按钮设置点击事件
 | 
			
		||||
        // 设置按钮点击事件
 | 
			
		||||
        findViewById(R.id.activitybackgroundpictureAButton5).setOnClickListener(onOriginNullClickListener);
 | 
			
		||||
        findViewById(R.id.activitybackgroundpictureAButton4).setOnClickListener(onReceivedPictureClickListener);
 | 
			
		||||
        findViewById(R.id.activitybackgroundpictureAButton1).setOnClickListener(onTakePhotoClickListener);
 | 
			
		||||
        findViewById(R.id.activitybackgroundpictureAButton2).setOnClickListener(onSelectPictureClickListener);
 | 
			
		||||
        findViewById(R.id.activitybackgroundpictureAButton3).setOnClickListener(onCropPictureClickListener);
 | 
			
		||||
        findViewById(R.id.activitybackgroundpictureAButton6).setOnClickListener(onCropFreePictureClickListener);
 | 
			
		||||
		findViewById(R.id.activitybackgroundpictureAButton7).setOnClickListener(onPixelPickerClickListener);
 | 
			
		||||
		findViewById(R.id.activitybackgroundpictureAButton8).setOnClickListener(onCleanPixelClickListener);
 | 
			
		||||
 | 
			
		||||
        updatePreviewBackground();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        // 判断并且处理应用分享到的文件
 | 
			
		||||
        //
 | 
			
		||||
        //ToastUtils.show("Activity Opened.");
 | 
			
		||||
 | 
			
		||||
        // 预备接收参数
 | 
			
		||||
        // 处理分享的图片
 | 
			
		||||
        Intent intent = getIntent();
 | 
			
		||||
        String action = intent.getAction();
 | 
			
		||||
        String type = intent.getType();
 | 
			
		||||
 | 
			
		||||
        //LogUtils.d(TAG, "action : " + action);
 | 
			
		||||
        //LogUtils.d(TAG, "type : " + type);
 | 
			
		||||
 | 
			
		||||
        // 判断是否进入图片分享状态
 | 
			
		||||
        if (Intent.ACTION_SEND.equals(action)
 | 
			
		||||
            && type != null
 | 
			
		||||
            && ("image/*".equals(type) || "image/jpeg".equals(type) || "image/jpg".equals(type) || "image/png".equals(type) || "image/webp".equals(type))) {
 | 
			
		||||
            // 预览图片
 | 
			
		||||
            BackgroundPicturePreviewDialog dlg= new BackgroundPicturePreviewDialog(this);
 | 
			
		||||
        if (Intent.ACTION_SEND.equals(action) && type != null && isImageType(type)) {
 | 
			
		||||
            BackgroundPicturePreviewDialog dlg = new BackgroundPicturePreviewDialog(this);
 | 
			
		||||
            dlg.show();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void initEnv() {
 | 
			
		||||
    private void initEnv() {
 | 
			
		||||
        LogUtils.d(TAG, "initEnv()");
 | 
			
		||||
        _RecivedPictureFileName = "Recived.data";
 | 
			
		||||
    }
 | 
			
		||||
@@ -144,47 +140,55 @@ implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onAcceptRecivedPicture(String szPreRecivedPictureName) {
 | 
			
		||||
        //ToastUtils.show("onAcceptRecivedPicture");
 | 
			
		||||
        BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(this);
 | 
			
		||||
        utils.getBackgroundPictureBean().setIsUseBackgroundFile(true);
 | 
			
		||||
        utils.saveData();
 | 
			
		||||
        File fPreRecivedPictureName = new File(utils.getBackgroundDir(), szPreRecivedPictureName);
 | 
			
		||||
        FileUtils.copyFile(fPreRecivedPictureName, mfRecivedPicture);
 | 
			
		||||
        // 加载背景
 | 
			
		||||
        startCropImageActivity(false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //
 | 
			
		||||
    // 更新预览背景
 | 
			
		||||
    //
 | 
			
		||||
    public void updatePreviewBackground() {
 | 
			
		||||
        LogUtils.d(TAG, "updatePreviewBackground");
 | 
			
		||||
        ImageView ivPreviewBackground = findViewById(R.id.activitybackgroundpictureImageView1);
 | 
			
		||||
        BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(this);
 | 
			
		||||
        utils.loadBackgroundPictureBean();
 | 
			
		||||
        boolean isUseBackgroundFile = utils.getBackgroundPictureBean().isUseBackgroundFile();
 | 
			
		||||
        if (isUseBackgroundFile && mfRecivedCropPicture.exists()) {
 | 
			
		||||
            try {
 | 
			
		||||
                String szBackgroundFilePath = utils.getBackgroundDir() + getBackgroundFileName();
 | 
			
		||||
                Drawable drawableBackground = FileUtils.getImageDrawable(szBackgroundFilePath);
 | 
			
		||||
                drawableBackground.setAlpha(120);
 | 
			
		||||
                ivPreviewBackground.setImageDrawable(drawableBackground);
 | 
			
		||||
                ToastUtils.show("Use acceptRecived background.");
 | 
			
		||||
            } catch (IOException e) {
 | 
			
		||||
                LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
 | 
			
		||||
            }
 | 
			
		||||
        File sourceFile = new File(utils.getBackgroundDir(), szPreRecivedPictureName);
 | 
			
		||||
        if (FileUtils.copyFile(sourceFile, mfRecivedPicture)) {
 | 
			
		||||
            startCropImageActivity(false);
 | 
			
		||||
        } else {
 | 
			
		||||
            ToastUtils.show(" No background.");
 | 
			
		||||
            Drawable drawableBackground = getDrawable(R.drawable.blank10x10);
 | 
			
		||||
            drawableBackground.setAlpha(120);
 | 
			
		||||
            ivPreviewBackground.setImageDrawable(drawableBackground);
 | 
			
		||||
            ToastUtils.show("图片复制失败,请重试");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 更新背景图片预览
 | 
			
		||||
     */
 | 
			
		||||
    public void updatePreviewBackground() {
 | 
			
		||||
        LogUtils.d(TAG, "updatePreviewBackground");
 | 
			
		||||
        ImageView ivPreviewBackground = (ImageView) findViewById(R.id.activitybackgroundpictureImageView1);
 | 
			
		||||
        BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(this);
 | 
			
		||||
        utils.loadBackgroundPictureBean();
 | 
			
		||||
 | 
			
		||||
        boolean isUseBackgroundFile = utils.getBackgroundPictureBean().isUseBackgroundFile();
 | 
			
		||||
        if (isUseBackgroundFile && mfRecivedCropPicture.exists()) {
 | 
			
		||||
            try {
 | 
			
		||||
                String filePath = utils.getBackgroundDir() + getBackgroundFileName();
 | 
			
		||||
                Drawable drawable = FileUtils.getImageDrawable(filePath);
 | 
			
		||||
                if (drawable != null) {
 | 
			
		||||
                    //drawable.setAlpha(120);
 | 
			
		||||
                    ivPreviewBackground.setImageDrawable(drawable);
 | 
			
		||||
                }
 | 
			
		||||
                //ToastUtils.show("背景图片已更新");
 | 
			
		||||
            } catch (IOException e) {
 | 
			
		||||
                LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
 | 
			
		||||
                ToastUtils.show("背景图片加载失败");
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            ToastUtils.show("未使用背景图片");
 | 
			
		||||
            Drawable drawable = getResources().getDrawable(R.drawable.blank10x10);
 | 
			
		||||
            if (drawable != null) {
 | 
			
		||||
                drawable.setAlpha(120);
 | 
			
		||||
                ivPreviewBackground.setImageDrawable(drawable);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 点击事件监听器
 | 
			
		||||
    private View.OnClickListener onOriginNullClickListener = new View.OnClickListener() {
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onClick(View v) {
 | 
			
		||||
            // 选择原始空白背景
 | 
			
		||||
            BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(BackgroundPictureActivity.this);
 | 
			
		||||
            BackgroundPictureBean bean = utils.getBackgroundPictureBean();
 | 
			
		||||
            bean.setIsUseBackgroundFile(false);
 | 
			
		||||
@@ -196,11 +200,10 @@ implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
 | 
			
		||||
    private View.OnClickListener onSelectPictureClickListener = new View.OnClickListener() {
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onClick(View v) {
 | 
			
		||||
            // 导入外部图片
 | 
			
		||||
            Intent intent = new Intent(
 | 
			
		||||
                Intent.ACTION_PICK,
 | 
			
		||||
                android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
 | 
			
		||||
            startActivityForResult(intent, REQUEST_SELECT_PICTURE);
 | 
			
		||||
            if (checkAndRequestStoragePermission()) {
 | 
			
		||||
                Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
 | 
			
		||||
                startActivityForResult(intent, REQUEST_SELECT_PICTURE);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
@@ -211,7 +214,7 @@ implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
 | 
			
		||||
            if (fCheck.exists()) {
 | 
			
		||||
                startCropImageActivity(false);
 | 
			
		||||
            } else {
 | 
			
		||||
                ToastUtils.show("There is not any picture to crop.");
 | 
			
		||||
                ToastUtils.show("没有可剪裁的图片");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
@@ -223,7 +226,7 @@ implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
 | 
			
		||||
            if (fCheck.exists()) {
 | 
			
		||||
                startCropImageActivity(true);
 | 
			
		||||
            } else {
 | 
			
		||||
                ToastUtils.show("There is not any picture to crop.");
 | 
			
		||||
                ToastUtils.show("没有可剪裁的图片");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
@@ -233,6 +236,7 @@ implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
 | 
			
		||||
        public void onClick(View v) {
 | 
			
		||||
            LogUtils.d(TAG, "onTakePhotoClickListener");
 | 
			
		||||
            LogUtils.d(TAG, "mfTakePhoto : " + mfTakePhoto.getPath());
 | 
			
		||||
 | 
			
		||||
            if (mfTakePhoto.exists()) {
 | 
			
		||||
                mfTakePhoto.delete();
 | 
			
		||||
            }
 | 
			
		||||
@@ -240,56 +244,94 @@ implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
 | 
			
		||||
                mfTakePhoto.createNewFile();
 | 
			
		||||
            } catch (IOException e) {
 | 
			
		||||
                LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
 | 
			
		||||
                ToastUtils.show("拍照文件创建失败");
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (checkAndRequestStoragePermission()) {
 | 
			
		||||
                Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
 | 
			
		||||
                startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
 | 
			
		||||
            }
 | 
			
		||||
            Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
 | 
			
		||||
            startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    private View.OnClickListener onReceivedPictureClickListener = new View.OnClickListener() {
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onClick(View v) {
 | 
			
		||||
            // 选择接收到的背景图片
 | 
			
		||||
            BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(BackgroundPictureActivity.this);
 | 
			
		||||
			utils.getBackgroundPictureBean().setIsUseBackgroundFile(true);
 | 
			
		||||
            utils.getBackgroundPictureBean().setIsUseBackgroundFile(true);
 | 
			
		||||
            utils.saveData();
 | 
			
		||||
            updatePreviewBackground();
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
	private View.OnClickListener onPixelPickerClickListener = new View.OnClickListener() {
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onClick(View v) {
 | 
			
		||||
			// 从文件路径启动像素拾取活动
 | 
			
		||||
			//String imagePath = "/storage/emulated/0/DCIM/Camera/sample.jpg";
 | 
			
		||||
			String imagePath = mfRecivedCropPicture.toString();
 | 
			
		||||
			Intent intent = new Intent(getApplicationContext(), PixelPickerActivity.class);
 | 
			
		||||
			intent.putExtra("imagePath", imagePath);
 | 
			
		||||
			startActivity(intent);
 | 
			
		||||
            //App.getWinBoLLActivityManager().startWinBoLLActivity(getActivity(), intent, PixelPickerActivity.class);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
	private View.OnClickListener onCleanPixelClickListener = new View.OnClickListener() {
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onClick(View v) {
 | 
			
		||||
			BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(BackgroundPictureActivity.this);
 | 
			
		||||
			BackgroundPictureBean bean = utils.getBackgroundPictureBean();
 | 
			
		||||
			bean.setPixelColor(0);
 | 
			
		||||
			utils.saveData();
 | 
			
		||||
			setBackgroundColor();
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 压缩图片并保存到接收文件
 | 
			
		||||
     */
 | 
			
		||||
    void compressQualityToRecivedPicture(Bitmap bitmap) {
 | 
			
		||||
        // 设置输出流
 | 
			
		||||
        OutputStream outStream = null;
 | 
			
		||||
        try {
 | 
			
		||||
            // 创建输出流对象,准备写入压缩后的图片文件
 | 
			
		||||
            mfRecivedPicture = getRecivedPictureFile(this);
 | 
			
		||||
            // 创建新的接收文件
 | 
			
		||||
            if (!mfRecivedPicture.exists()) {
 | 
			
		||||
                mfRecivedPicture.createNewFile();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            FileOutputStream fos = new FileOutputStream(mfRecivedPicture);
 | 
			
		||||
 | 
			
		||||
            // 获取输出流对象
 | 
			
		||||
            outStream = new BufferedOutputStream(fos);
 | 
			
		||||
 | 
			
		||||
            // 使用默认的质量参数压缩图片
 | 
			
		||||
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outStream); // 70% 的质量
 | 
			
		||||
 | 
			
		||||
            // 关闭输出流以完成文件操作
 | 
			
		||||
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outStream);
 | 
			
		||||
            outStream.flush();
 | 
			
		||||
            outStream.close();
 | 
			
		||||
        } catch (IOException e) {
 | 
			
		||||
            LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
 | 
			
		||||
            ToastUtils.show("图片压缩失败");
 | 
			
		||||
        } finally {
 | 
			
		||||
            if (outStream != null) {
 | 
			
		||||
                try {
 | 
			
		||||
                    outStream.close();
 | 
			
		||||
                } catch (IOException e) {
 | 
			
		||||
                    LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (bitmap != null && !bitmap.isRecycled()) {
 | 
			
		||||
                bitmap.recycle();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 启动图片裁剪活动
 | 
			
		||||
     * @param isCropFree 是否自由裁剪
 | 
			
		||||
     */
 | 
			
		||||
    public void startCropImageActivity(boolean isCropFree) {
 | 
			
		||||
        LogUtils.d(TAG, "startCropImageActivity");
 | 
			
		||||
        BackgroundPictureBean bean = mBackgroundPictureUtils.loadBackgroundPictureBean();
 | 
			
		||||
        mfRecivedPicture = getRecivedPictureFile(this);
 | 
			
		||||
        Uri uri = UriUtil.getUriForFile(this, mfRecivedPicture);
 | 
			
		||||
        LogUtils.d(TAG, "uri : " + uri.toString());
 | 
			
		||||
 | 
			
		||||
        if (mfTempCropPicture.exists()) {
 | 
			
		||||
            mfTempCropPicture.delete();
 | 
			
		||||
        }
 | 
			
		||||
@@ -297,27 +339,24 @@ implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
 | 
			
		||||
            mfTempCropPicture.createNewFile();
 | 
			
		||||
        } catch (IOException e) {
 | 
			
		||||
            LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
 | 
			
		||||
            ToastUtils.show("剪裁临时文件创建失败");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        // 使用正确的文件路径构建 Uri
 | 
			
		||||
 | 
			
		||||
        Uri cropOutPutUri = Uri.fromFile(mfTempCropPicture);
 | 
			
		||||
        LogUtils.d(TAG, "mfTempCropPicture : " + mfTempCropPicture.getPath());
 | 
			
		||||
 | 
			
		||||
        Intent intent = new Intent("com.android.camera.action.CROP");
 | 
			
		||||
        intent.setDataAndType(uri, "image/" + _mszCommonFileType);
 | 
			
		||||
        // 下面这个crop=true是设置在开启的Intent中设置显示的VIEW可裁剪
 | 
			
		||||
        intent.putExtra("crop", "true");
 | 
			
		||||
        intent.putExtra("noFaceDetection", true);
 | 
			
		||||
 | 
			
		||||
        if (!isCropFree) {
 | 
			
		||||
            // aspectX aspectY 是宽高的比例
 | 
			
		||||
            intent.putExtra("aspectX", bean.getBackgroundWidth());
 | 
			
		||||
            intent.putExtra("aspectY", bean.getBackgroundHeight());
 | 
			
		||||
        }
 | 
			
		||||
        // outputX outputY 是裁剪图片宽高
 | 
			
		||||
        //intent.putExtra("outputX", 100);
 | 
			
		||||
        //intent.putExtra("outputY", 100);
 | 
			
		||||
        //return-data =false 意味着裁剪成功后不能在onActivityResult 的intent 中获得图片
 | 
			
		||||
        //intent.putExtra("return-data", false);
 | 
			
		||||
 | 
			
		||||
        intent.putExtra("return-data", true);
 | 
			
		||||
        //裁剪后的图片输出至  cropOutPutUri
 | 
			
		||||
        intent.putExtra(MediaStore.EXTRA_OUTPUT, cropOutPutUri);
 | 
			
		||||
        intent.putExtra("scale", true);
 | 
			
		||||
        intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
 | 
			
		||||
@@ -325,12 +364,101 @@ implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
 | 
			
		||||
        startActivityForResult(intent, REQUEST_CROP_IMAGE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 启动裁剪窗口,裁剪操作文件为 uirImage
 | 
			
		||||
    //
 | 
			
		||||
    /**
 | 
			
		||||
     * 保存剪裁后的Bitmap(优化版)
 | 
			
		||||
     */
 | 
			
		||||
    private void saveCropBitmap(Bitmap bitmap) {
 | 
			
		||||
        if (bitmap == null) {
 | 
			
		||||
            ToastUtils.show("剪裁图片为空");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 内存优化:大图片自动缩放
 | 
			
		||||
        Bitmap scaledBitmap = bitmap;
 | 
			
		||||
        if (bitmap.getByteCount() > 10 * 1024 * 1024) { // 超过10MB
 | 
			
		||||
            float scale = 1.0f;
 | 
			
		||||
            while (scaledBitmap.getByteCount() > 5 * 1024 * 1024) {
 | 
			
		||||
                scale -= 0.2f; // 每次缩小20%
 | 
			
		||||
                if (scale < 0.2f) break; // 最小缩放到20%
 | 
			
		||||
                scaledBitmap = scaleBitmap(scaledBitmap, scale);
 | 
			
		||||
            }
 | 
			
		||||
            if (scaledBitmap != bitmap) {
 | 
			
		||||
                bitmap.recycle(); // 回收原Bitmap
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 优化:创建保存目录
 | 
			
		||||
        File backgroundDir = new File(mBackgroundPictureUtils.getBackgroundDir());
 | 
			
		||||
        if (!backgroundDir.exists()) {
 | 
			
		||||
            if (!backgroundDir.mkdirs()) {
 | 
			
		||||
                ToastUtils.show("无法创建保存目录");
 | 
			
		||||
                if (scaledBitmap != bitmap) scaledBitmap.recycle();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        File saveFile = new File(backgroundDir, getBackgroundFileName());
 | 
			
		||||
 | 
			
		||||
        // 优化:检查文件是否可写
 | 
			
		||||
        if (saveFile.exists() && !saveFile.canWrite()) {
 | 
			
		||||
            if (!saveFile.delete()) {
 | 
			
		||||
                ToastUtils.show("无法删除旧文件");
 | 
			
		||||
                if (scaledBitmap != bitmap) scaledBitmap.recycle();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        FileOutputStream fos = null;
 | 
			
		||||
        try {
 | 
			
		||||
            fos = new FileOutputStream(saveFile);
 | 
			
		||||
            boolean success = scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 80, fos);
 | 
			
		||||
            fos.flush();
 | 
			
		||||
            if (success) {
 | 
			
		||||
                ToastUtils.show("保存成功");
 | 
			
		||||
                // 更新数据
 | 
			
		||||
                mBackgroundPictureUtils.getBackgroundPictureBean().setIsUseBackgroundFile(true);
 | 
			
		||||
                updatePreviewBackground();
 | 
			
		||||
            } else {
 | 
			
		||||
                ToastUtils.show("图片压缩保存失败");
 | 
			
		||||
            }
 | 
			
		||||
        } catch (FileNotFoundException e) {
 | 
			
		||||
            LogUtils.e(TAG, "文件未找到" + e);
 | 
			
		||||
            ToastUtils.show("保存失败:文件路径错误");
 | 
			
		||||
        } catch (IOException e) {
 | 
			
		||||
            LogUtils.e(TAG, "写入异常" + e);
 | 
			
		||||
            ToastUtils.show("保存失败:磁盘可能已满或路径错误");
 | 
			
		||||
        } finally {
 | 
			
		||||
            if (fos != null) {
 | 
			
		||||
                try {
 | 
			
		||||
                    fos.close();
 | 
			
		||||
                } catch (IOException e) {
 | 
			
		||||
                    LogUtils.e(TAG, "流关闭异常" + e);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (scaledBitmap != null && !scaledBitmap.isRecycled()) {
 | 
			
		||||
                scaledBitmap.recycle();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 缩放Bitmap
 | 
			
		||||
     */
 | 
			
		||||
    private Bitmap scaleBitmap(Bitmap original, float scale) {
 | 
			
		||||
        if (original == null) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        int width = (int) (original.getWidth() * scale);
 | 
			
		||||
        int height = (int) (original.getHeight() * scale);
 | 
			
		||||
        return Bitmap.createScaledBitmap(original, width, height, true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 分享图片
 | 
			
		||||
     */
 | 
			
		||||
    void sharePicture() {
 | 
			
		||||
        Uri uri = UriUtil.getUriForFile(this, mfRecivedPicture);
 | 
			
		||||
        Intent shareIntent = new Intent();    
 | 
			
		||||
        shareIntent.setAction(Intent.ACTION_SEND);    
 | 
			
		||||
        Intent shareIntent = new Intent(Intent.ACTION_SEND);
 | 
			
		||||
        shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
 | 
			
		||||
        shareIntent.setType("image/" + _mszCommonFileType);
 | 
			
		||||
        shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
 | 
			
		||||
@@ -345,45 +473,121 @@ implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
 | 
			
		||||
 | 
			
		||||
        super.onActivityResult(requestCode, resultCode, data);
 | 
			
		||||
        if (requestCode == REQUEST_SELECT_PICTURE) {
 | 
			
		||||
            // 处理选择后图片
 | 
			
		||||
            if (resultCode == RESULT_OK) {
 | 
			
		||||
                try {
 | 
			
		||||
                    Uri selectedImage = data.getData(); 
 | 
			
		||||
                    LogUtils.d(TAG, "Uri is : " + selectedImage.toString());
 | 
			
		||||
                    File fSrcImage = new File(UriUtil.getFilePathFromUri(this, selectedImage));
 | 
			
		||||
                    mfRecivedPicture = getRecivedPictureFile(this);
 | 
			
		||||
 | 
			
		||||
                    FileUtils.copyFile(fSrcImage, mfRecivedPicture);
 | 
			
		||||
                    // 启动剪裁文件窗口
 | 
			
		||||
        if (requestCode == REQUEST_SELECT_PICTURE && resultCode == RESULT_OK) {
 | 
			
		||||
            try {
 | 
			
		||||
                Uri selectedImage = data.getData();
 | 
			
		||||
                LogUtils.d(TAG, "Uri is : " + selectedImage.toString());
 | 
			
		||||
                File fSrcImage = new File(UriUtil.getFilePathFromUri(this, selectedImage));
 | 
			
		||||
                mfRecivedPicture = getRecivedPictureFile(this);
 | 
			
		||||
                if (FileUtils.copyFile(fSrcImage, mfRecivedPicture)) {
 | 
			
		||||
                    startCropImageActivity(false);
 | 
			
		||||
                } catch (Exception e) {
 | 
			
		||||
                    LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
 | 
			
		||||
                } else {
 | 
			
		||||
                    ToastUtils.show("图片复制失败,请重试");
 | 
			
		||||
                }
 | 
			
		||||
            } catch (Exception e) {
 | 
			
		||||
                LogUtils.e(TAG, "选择图片异常" + e);
 | 
			
		||||
                ToastUtils.show("选择图片失败:" + e.getMessage());
 | 
			
		||||
            }
 | 
			
		||||
        } else if (requestCode == REQUEST_TAKE_PHOTO) {
 | 
			
		||||
            if (resultCode == RESULT_OK) {
 | 
			
		||||
                LogUtils.d(TAG, "REQUEST_TAKE_PHOTO");
 | 
			
		||||
                Bundle extras = data.getExtras();
 | 
			
		||||
        } else if (requestCode == REQUEST_TAKE_PHOTO && resultCode == RESULT_OK) {
 | 
			
		||||
            LogUtils.d(TAG, "REQUEST_TAKE_PHOTO");
 | 
			
		||||
            Bundle extras = data.getExtras();
 | 
			
		||||
            if (extras != null) {
 | 
			
		||||
                Bitmap imageBitmap = (Bitmap) extras.get("data");
 | 
			
		||||
                compressQualityToRecivedPicture(imageBitmap);
 | 
			
		||||
                startCropImageActivity(false);
 | 
			
		||||
            }
 | 
			
		||||
        } else if (requestCode == REQUEST_CROP_IMAGE) {
 | 
			
		||||
            if (resultCode == RESULT_OK) {
 | 
			
		||||
                LogUtils.d(TAG, "CROP_IMAGE_REQUEST_CODE");
 | 
			
		||||
                FileUtils.copyFile(mfTempCropPicture, mfRecivedCropPicture);
 | 
			
		||||
                mfTempCropPicture.delete();
 | 
			
		||||
                mBackgroundPictureUtils.getBackgroundPictureBean().setIsUseBackgroundFile(true);
 | 
			
		||||
                updatePreviewBackground();
 | 
			
		||||
                if (imageBitmap != null) {
 | 
			
		||||
                    compressQualityToRecivedPicture(imageBitmap);
 | 
			
		||||
                    startCropImageActivity(false);
 | 
			
		||||
                } else {
 | 
			
		||||
                    ToastUtils.show("拍照图片为空");
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                ToastUtils.show("拍照数据获取失败");
 | 
			
		||||
            }
 | 
			
		||||
        } else if (requestCode == REQUEST_CROP_IMAGE && resultCode == RESULT_OK) {
 | 
			
		||||
            LogUtils.d(TAG, "CROP_IMAGE_REQUEST_CODE");
 | 
			
		||||
            try {
 | 
			
		||||
                Bitmap cropBitmap = null;
 | 
			
		||||
                // 方案1:通过Intent获取剪裁后的Bitmap
 | 
			
		||||
                if (data != null && data.hasExtra("data")) {
 | 
			
		||||
                    cropBitmap = data.getParcelableExtra("data");
 | 
			
		||||
                } else if (mfTempCropPicture.exists()) {
 | 
			
		||||
                    cropBitmap = BitmapFactory.decodeFile(mfTempCropPicture.getPath());
 | 
			
		||||
                } else {
 | 
			
		||||
                    ToastUtils.show("剪裁文件不存在");
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
        } else {
 | 
			
		||||
            String sz = "Unsolved requestCode = " + Integer.toString(requestCode);
 | 
			
		||||
            Toast.makeText(getApplication(), sz, Toast.LENGTH_SHORT).show();
 | 
			
		||||
            LogUtils.d(TAG, sz);
 | 
			
		||||
                if (cropBitmap != null) {
 | 
			
		||||
                    saveCropBitmap(cropBitmap);
 | 
			
		||||
                } else {
 | 
			
		||||
                    ToastUtils.show("获取剪裁图片失败");
 | 
			
		||||
                }
 | 
			
		||||
            } catch (OutOfMemoryError e) {
 | 
			
		||||
                LogUtils.e(TAG, "内存溢出" + e);
 | 
			
		||||
                ToastUtils.show("保存失败:内存不足,请尝试裁剪更小的图片");
 | 
			
		||||
            } catch (Exception e) {
 | 
			
		||||
                LogUtils.e(TAG, "剪裁保存异常" + e);
 | 
			
		||||
                ToastUtils.show("保存失败:" + e.getMessage());
 | 
			
		||||
            }/* finally {
 | 
			
		||||
			 // 安全删除临时文件
 | 
			
		||||
			 if (mfTempCropPicture.exists()) {
 | 
			
		||||
			 mfTempCropPicture.delete();
 | 
			
		||||
			 }
 | 
			
		||||
			 }*/
 | 
			
		||||
        } else if (resultCode != RESULT_OK) {
 | 
			
		||||
            LogUtils.d(TAG, "操作取消或失败,requestCode: " + requestCode);
 | 
			
		||||
            ToastUtils.show("操作已取消");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 检查类型是否为图片
 | 
			
		||||
     */
 | 
			
		||||
    private boolean isImageType(String type) {
 | 
			
		||||
        return type.startsWith("image/") || "image/jpeg".equals(type) || 
 | 
			
		||||
            "image/jpg".equals(type) || "image/png".equals(type) || 
 | 
			
		||||
            "image/webp".equals(type);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 检查并申请存储权限
 | 
			
		||||
     */
 | 
			
		||||
    private boolean checkAndRequestStoragePermission() {
 | 
			
		||||
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
 | 
			
		||||
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
 | 
			
		||||
                ActivityCompat.requestPermissions(this, 
 | 
			
		||||
                                                  new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 
 | 
			
		||||
                                                  STORAGE_PERMISSION_REQUEST);
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
 | 
			
		||||
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
 | 
			
		||||
        if (requestCode == STORAGE_PERMISSION_REQUEST) {
 | 
			
		||||
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
 | 
			
		||||
                ToastUtils.show("存储权限已获取");
 | 
			
		||||
            } else {
 | 
			
		||||
                ToastUtils.show("需要存储权限才能保存图片");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	void setBackgroundColor() {
 | 
			
		||||
		BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(BackgroundPictureActivity.this);
 | 
			
		||||
		BackgroundPictureBean bean = utils.getBackgroundPictureBean();
 | 
			
		||||
		int nPixelColor = bean.getPixelColor();
 | 
			
		||||
		RelativeLayout mainLayout = findViewById(R.id.activitybackgroundpictureRelativeLayout1);
 | 
			
		||||
		mainLayout.setBackgroundColor(nPixelColor);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected void onResume() {
 | 
			
		||||
		super.onResume();
 | 
			
		||||
		setBackgroundColor();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,251 @@
 | 
			
		||||
package cc.winboll.studio.powerbell.activities;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @Author ZhanGSKen<zhangsken@188.com>
 | 
			
		||||
 * @Date 2025/06/22 14:15
 | 
			
		||||
 */
 | 
			
		||||
import android.app.Activity;
 | 
			
		||||
import android.app.Dialog;
 | 
			
		||||
import android.graphics.Bitmap;
 | 
			
		||||
import android.graphics.BitmapFactory;
 | 
			
		||||
import android.graphics.Color;
 | 
			
		||||
import android.os.Bundle;
 | 
			
		||||
import android.view.MenuItem;
 | 
			
		||||
import android.view.MotionEvent;
 | 
			
		||||
import android.view.View;
 | 
			
		||||
import android.view.ViewGroup;
 | 
			
		||||
import android.widget.Button;
 | 
			
		||||
import android.widget.ImageView;
 | 
			
		||||
import android.widget.RelativeLayout;
 | 
			
		||||
import android.widget.TextView;
 | 
			
		||||
import android.widget.Toast;
 | 
			
		||||
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.PixelPickerActivity;
 | 
			
		||||
import cc.winboll.studio.powerbell.beans.BackgroundPictureBean;
 | 
			
		||||
import cc.winboll.studio.powerbell.utils.BackgroundPictureUtils;
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.FileInputStream;
 | 
			
		||||
import java.io.FileNotFoundException;
 | 
			
		||||
 | 
			
		||||
public class PixelPickerActivity extends WinBoLLActivity implements IWinBoLLActivity {
 | 
			
		||||
 | 
			
		||||
	public static final String TAG = "PixelPickerActivity";
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public Activity getActivity() {
 | 
			
		||||
		return this;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public String getTag() {
 | 
			
		||||
		return TAG;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private AToolbar mAToolbar;
 | 
			
		||||
	private ImageView imageView;
 | 
			
		||||
	private Bitmap originalBitmap;
 | 
			
		||||
	private TextView infoText;
 | 
			
		||||
	private ViewGroup imageContainer;
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected void onCreate(Bundle savedInstanceState) {
 | 
			
		||||
		super.onCreate(savedInstanceState);
 | 
			
		||||
		setContentView(R.layout.activity_pixelpicker);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        // 初始化工具栏
 | 
			
		||||
        mAToolbar = (AToolbar) findViewById(R.id.toolbar);
 | 
			
		||||
        setActionBar(mAToolbar);
 | 
			
		||||
        mAToolbar.setSubtitle(R.string.subtitle_activity_pixelpicker);
 | 
			
		||||
        getActionBar().setDisplayHomeAsUpEnabled(true);
 | 
			
		||||
        mAToolbar.setNavigationOnClickListener(new View.OnClickListener() {
 | 
			
		||||
                @Override
 | 
			
		||||
                public void onClick(View v) {
 | 
			
		||||
                    finish();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
		imageView = findViewById(R.id.imageView);
 | 
			
		||||
		infoText = findViewById(R.id.infoText);
 | 
			
		||||
		imageContainer = findViewById(R.id.imageContainer);
 | 
			
		||||
 | 
			
		||||
		// 从Intent获取图片路径并加载
 | 
			
		||||
		String imagePath = getIntent().getStringExtra("imagePath");
 | 
			
		||||
		if (imagePath != null) {
 | 
			
		||||
			loadImage(imagePath);
 | 
			
		||||
		} else {
 | 
			
		||||
			infoText.setText("未找到图片路径");
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 设置图片点击事件
 | 
			
		||||
		imageContainer.setOnTouchListener(new View.OnTouchListener() {
 | 
			
		||||
				@Override
 | 
			
		||||
				public boolean onTouch(View v, MotionEvent event) {
 | 
			
		||||
					if (event.getAction() == MotionEvent.ACTION_DOWN && originalBitmap != null) {
 | 
			
		||||
						// 计算点击位置在图片上的实际坐标
 | 
			
		||||
						float touchX = event.getX();
 | 
			
		||||
						float touchY = event.getY();
 | 
			
		||||
 | 
			
		||||
						int pixelX = -1, pixelY = -1;
 | 
			
		||||
						try {
 | 
			
		||||
							// 获取图片在容器中的实际位置和尺寸
 | 
			
		||||
							int[] imageLocation = new int[2];
 | 
			
		||||
							imageView.getLocationInWindow(imageLocation);
 | 
			
		||||
							int imageWidth = imageView.getWidth();
 | 
			
		||||
							int imageHeight = imageView.getHeight();
 | 
			
		||||
 | 
			
		||||
							// 计算缩放比例
 | 
			
		||||
							float scaleX = (float) originalBitmap.getWidth() / imageWidth;
 | 
			
		||||
							float scaleY = (float) originalBitmap.getHeight() / imageHeight;
 | 
			
		||||
 | 
			
		||||
							// 调整触摸坐标到图片坐标系
 | 
			
		||||
							float adjustedX = touchX - imageLocation[0];
 | 
			
		||||
							float adjustedY = touchY - imageLocation[1];
 | 
			
		||||
 | 
			
		||||
							// 检查是否在图片范围内
 | 
			
		||||
							if (adjustedX >= 0 && adjustedX <= imageWidth && adjustedY >= 0 && adjustedY <= imageHeight) {
 | 
			
		||||
								// 计算实际像素坐标
 | 
			
		||||
								pixelX = (int) (adjustedX * scaleX);
 | 
			
		||||
								pixelY = (int) (adjustedY * scaleY);
 | 
			
		||||
 | 
			
		||||
								// 再次检查像素坐标是否在有效范围内
 | 
			
		||||
								if (pixelX >= 0 && pixelX < originalBitmap.getWidth() && 
 | 
			
		||||
									pixelY >= 0 && pixelY < originalBitmap.getHeight()) {
 | 
			
		||||
									int pixelColor = originalBitmap.getPixel(pixelX, pixelY);
 | 
			
		||||
									showPixelDialog(pixelColor, pixelX, pixelY);
 | 
			
		||||
								} else {
 | 
			
		||||
									Toast.makeText(PixelPickerActivity.this, "像素坐标超出范围", Toast.LENGTH_SHORT).show();
 | 
			
		||||
								}
 | 
			
		||||
							} else {
 | 
			
		||||
								Toast.makeText(PixelPickerActivity.this, "点击位置超出图片显示范围", Toast.LENGTH_SHORT).show();
 | 
			
		||||
							}
 | 
			
		||||
						} catch (Exception e) {
 | 
			
		||||
							e.printStackTrace();
 | 
			
		||||
							Toast.makeText(PixelPickerActivity.this, "计算像素位置失败", Toast.LENGTH_SHORT).show();
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					return true;
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * 加载图片
 | 
			
		||||
	 */
 | 
			
		||||
	private void loadImage(String imagePath) {
 | 
			
		||||
		try {
 | 
			
		||||
			File file = new File(imagePath);
 | 
			
		||||
			if (file.exists()) {
 | 
			
		||||
				// 解码图片
 | 
			
		||||
				BitmapFactory.Options options = new BitmapFactory.Options();
 | 
			
		||||
				options.inSampleSize = 1; // 加载原图
 | 
			
		||||
				originalBitmap = BitmapFactory.decodeStream(new FileInputStream(file), null, options);
 | 
			
		||||
 | 
			
		||||
				if (originalBitmap != null) {
 | 
			
		||||
					imageView.setImageBitmap(originalBitmap);
 | 
			
		||||
					infoText.setText("图片已加载,点击获取像素值");
 | 
			
		||||
				} else {
 | 
			
		||||
					infoText.setText("图片加载失败");
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				infoText.setText("图片文件不存在");
 | 
			
		||||
			}
 | 
			
		||||
		} catch (FileNotFoundException e) {
 | 
			
		||||
			e.printStackTrace();
 | 
			
		||||
			infoText.setText("图片文件未找到");
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * 显示像素对话框
 | 
			
		||||
	 */
 | 
			
		||||
	private void showPixelDialog(final int pixelColor, int x, int y) {
 | 
			
		||||
		Dialog dialog = new Dialog(this);
 | 
			
		||||
		dialog.setContentView(R.layout.dialog_pixel);
 | 
			
		||||
		dialog.setCancelable(true);
 | 
			
		||||
 | 
			
		||||
		// 设置像素颜色视图背景
 | 
			
		||||
		TextView colorView = dialog.findViewById(R.id.pixelColorView);
 | 
			
		||||
		colorView.setBackgroundColor(pixelColor);
 | 
			
		||||
 | 
			
		||||
		// 显示颜色信息
 | 
			
		||||
		TextView infoText = dialog.findViewById(R.id.colorInfoText);
 | 
			
		||||
		String colorInfo = String.format(
 | 
			
		||||
			"RGB: (%d, %d, %d)\n" +
 | 
			
		||||
			"ARGB: #%08X\n" +
 | 
			
		||||
			"实际像素位置: (%d, %d)",
 | 
			
		||||
			Color.red(pixelColor),
 | 
			
		||||
			Color.green(pixelColor),
 | 
			
		||||
			Color.blue(pixelColor),
 | 
			
		||||
			pixelColor,
 | 
			
		||||
			x, y);
 | 
			
		||||
		infoText.setText(colorInfo);
 | 
			
		||||
 | 
			
		||||
		// 设置确定按钮点击事件
 | 
			
		||||
		Button confirmButton = dialog.findViewById(R.id.confirmButton);
 | 
			
		||||
		confirmButton.setOnClickListener(new View.OnClickListener() {
 | 
			
		||||
				@Override
 | 
			
		||||
				public void onClick(View v) {
 | 
			
		||||
					dialog.dismiss();
 | 
			
		||||
					// 可以在这里添加确定后的回调逻辑
 | 
			
		||||
					BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(PixelPickerActivity.this);
 | 
			
		||||
					BackgroundPictureBean bean = utils.getBackgroundPictureBean();
 | 
			
		||||
					bean.setPixelColor(pixelColor);
 | 
			
		||||
					utils.saveData();
 | 
			
		||||
					Toast.makeText(PixelPickerActivity.this, "已记录像素值", Toast.LENGTH_SHORT).show();
 | 
			
		||||
					setBackgroundColor();
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
		dialog.show();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected void onDestroy() {
 | 
			
		||||
		super.onDestroy();
 | 
			
		||||
		// 回收Bitmap资源
 | 
			
		||||
		if (originalBitmap != null && !originalBitmap.isRecycled()) {
 | 
			
		||||
			originalBitmap.recycle();
 | 
			
		||||
			originalBitmap = null;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	void setBackgroundColor() {
 | 
			
		||||
		BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(PixelPickerActivity.this);
 | 
			
		||||
		BackgroundPictureBean bean = utils.getBackgroundPictureBean();
 | 
			
		||||
		int nPixelColor = bean.getPixelColor();
 | 
			
		||||
		RelativeLayout mainLayout = findViewById(R.id.activitypixelpickerRelativeLayout1);
 | 
			
		||||
		mainLayout.setBackgroundColor(nPixelColor);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected void onResume() {
 | 
			
		||||
		super.onResume();
 | 
			
		||||
		setBackgroundColor();
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean onOptionsItemSelected(MenuItem item) {
 | 
			
		||||
        if (item.getItemId() == android.R.id.home) {
 | 
			
		||||
            GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(getApplicationContext(), BackgroundPictureActivity.class);
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        // 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。
 | 
			
		||||
        return super.onOptionsItemSelected(item);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void onBackPressed() {
 | 
			
		||||
		super.onBackPressed();
 | 
			
		||||
		GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(getApplicationContext(), BackgroundPictureActivity.class);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
@@ -0,0 +1,53 @@
 | 
			
		||||
package cc.winboll.studio.powerbell.activities;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @Author ZhanGSKen<zhangsken@188.com>
 | 
			
		||||
 * @Date 2025/06/19 20:35
 | 
			
		||||
 * @Describe 应用窗口基类
 | 
			
		||||
 */
 | 
			
		||||
import android.app.Activity;
 | 
			
		||||
import android.os.Bundle;
 | 
			
		||||
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 cc.winboll.studio.powerbell.R;
 | 
			
		||||
 | 
			
		||||
public abstract class WinBoLLActivity extends Activity implements IWinBoLLActivity {
 | 
			
		||||
 | 
			
		||||
    public static final String TAG = "WinBoLLActivity";
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onCreate(Bundle savedInstanceState) {
 | 
			
		||||
        super.onCreate(savedInstanceState);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onPostCreate(Bundle savedInstanceState) {
 | 
			
		||||
        super.onPostCreate(savedInstanceState);
 | 
			
		||||
        GlobalApplication.getWinBoLLActivityManager().add(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onDestroy() {
 | 
			
		||||
        super.onDestroy();
 | 
			
		||||
        GlobalApplication.getWinBoLLActivityManager().registeRemove(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean onOptionsItemSelected(MenuItem item) {
 | 
			
		||||
        if (item.getItemId() == android.R.id.home) {
 | 
			
		||||
            GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(getApplicationContext(), MainActivity.class);
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        // 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。
 | 
			
		||||
        return super.onOptionsItemSelected(item);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void onBackPressed() {
 | 
			
		||||
		super.onBackPressed();
 | 
			
		||||
		GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(getApplicationContext(), MainActivity.class);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -17,6 +17,8 @@ public class BackgroundPictureBean extends BaseBean {
 | 
			
		||||
    int backgroundWidth = 100;
 | 
			
		||||
    int backgroundHeight = 100;
 | 
			
		||||
    boolean isUseBackgroundFile = false;
 | 
			
		||||
	// 图片拾取像素颜色
 | 
			
		||||
	int pixelColor = 0;
 | 
			
		||||
 | 
			
		||||
    public BackgroundPictureBean() {
 | 
			
		||||
    }
 | 
			
		||||
@@ -25,6 +27,14 @@ public class BackgroundPictureBean extends BaseBean {
 | 
			
		||||
        this.isUseBackgroundFile = isUseBackgroundFile;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	public void setPixelColor(int pixelColor) {
 | 
			
		||||
		this.pixelColor = pixelColor;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public int getPixelColor() {
 | 
			
		||||
		return pixelColor;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    public void setBackgroundWidth(int backgroundWidth) {
 | 
			
		||||
        this.backgroundWidth = backgroundWidth;
 | 
			
		||||
    }
 | 
			
		||||
@@ -61,6 +71,7 @@ public class BackgroundPictureBean extends BaseBean {
 | 
			
		||||
        jsonWriter.name("backgroundWidth").value(bean.getBackgroundWidth());
 | 
			
		||||
        jsonWriter.name("backgroundHeight").value(bean.getBackgroundHeight());
 | 
			
		||||
        jsonWriter.name("isUseBackgroundFile").value(bean.isUseBackgroundFile());
 | 
			
		||||
		jsonWriter.name("pixelColor").value(bean.getPixelColor());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
@@ -75,6 +86,8 @@ public class BackgroundPictureBean extends BaseBean {
 | 
			
		||||
                bean.setBackgroundHeight(jsonReader.nextInt());
 | 
			
		||||
            } else if (name.equals("isUseBackgroundFile")) {
 | 
			
		||||
                bean.setIsUseBackgroundFile(jsonReader.nextBoolean());
 | 
			
		||||
            } else if (name.equals("pixelColor")) {
 | 
			
		||||
                bean.setPixelColor(jsonReader.nextInt());
 | 
			
		||||
            } else {
 | 
			
		||||
                jsonReader.skipValue();
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -311,11 +311,11 @@ public class MainViewFragment extends Fragment {
 | 
			
		||||
        LogUtils.d(TAG, String.format("fBackgroundFilePath.exists() %s", fBackgroundFilePath.exists()));
 | 
			
		||||
        if (bean.isUseBackgroundFile() && fBackgroundFilePath.exists()) {
 | 
			
		||||
            Drawable drawableBackground = Drawable.createFromPath(szBackgroundFilePath);
 | 
			
		||||
            drawableBackground.setAlpha(120);
 | 
			
		||||
            //drawableBackground.setAlpha(120);
 | 
			
		||||
            imageView.setImageDrawable(drawableBackground);
 | 
			
		||||
        } else {
 | 
			
		||||
            Drawable drawableBackground = getActivity().getDrawable(R.drawable.blank10x10);
 | 
			
		||||
            drawableBackground.setAlpha(120);
 | 
			
		||||
            //drawableBackground.setAlpha(120);
 | 
			
		||||
            imageView.setImageDrawable(drawableBackground);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<RelativeLayout
 | 
			
		||||
<LinearLayout
 | 
			
		||||
	xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
	xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
			
		||||
	android:orientation="vertical"
 | 
			
		||||
@@ -12,83 +12,110 @@
 | 
			
		||||
		android:id="@+id/toolbar"
 | 
			
		||||
		style="@style/DefaultAToolbar"/>
 | 
			
		||||
 | 
			
		||||
	<ImageView
 | 
			
		||||
		android:layout_width="match_parent"
 | 
			
		||||
		android:layout_height="match_parent"
 | 
			
		||||
		android:id="@+id/activitybackgroundpictureImageView1"
 | 
			
		||||
		android:layout_below="@id/toolbar">
 | 
			
		||||
 | 
			
		||||
	</ImageView>
 | 
			
		||||
 | 
			
		||||
	<LinearLayout
 | 
			
		||||
		android:orientation="vertical"
 | 
			
		||||
		android:layout_width="match_parent"
 | 
			
		||||
		android:layout_height="wrap_content"
 | 
			
		||||
		android:layout_below="@id/toolbar">
 | 
			
		||||
	<RelativeLayout
 | 
			
		||||
		android:layout_width="wrap_content"
 | 
			
		||||
		android:layout_height="wrap_content">
 | 
			
		||||
 | 
			
		||||
		<RelativeLayout
 | 
			
		||||
			android:layout_width="wrap_content"
 | 
			
		||||
			android:layout_height="wrap_content">
 | 
			
		||||
			android:layout_width="match_parent"
 | 
			
		||||
			android:layout_height="match_parent"
 | 
			
		||||
			android:id="@+id/activitybackgroundpictureRelativeLayout1"/>
 | 
			
		||||
 | 
			
		||||
			<cc.winboll.studio.libaes.views.AButton
 | 
			
		||||
				android:layout_width="160dp"
 | 
			
		||||
				android:layout_height="36dp"
 | 
			
		||||
				android:text="Origin BG"
 | 
			
		||||
				android:id="@+id/activitybackgroundpictureAButton5"
 | 
			
		||||
				android:layout_alignParentLeft="true"
 | 
			
		||||
				android:layout_margin="5dp"/>
 | 
			
		||||
 | 
			
		||||
			<cc.winboll.studio.libaes.views.AButton
 | 
			
		||||
				android:layout_width="160dp"
 | 
			
		||||
				android:layout_height="36dp"
 | 
			
		||||
				android:text="Received BG"
 | 
			
		||||
				android:id="@+id/activitybackgroundpictureAButton4"
 | 
			
		||||
				android:layout_alignParentRight="true"
 | 
			
		||||
				android:layout_margin="5dp"/>
 | 
			
		||||
		<ImageView
 | 
			
		||||
			android:layout_width="match_parent"
 | 
			
		||||
			android:layout_height="match_parent"
 | 
			
		||||
			android:id="@+id/activitybackgroundpictureImageView1"
 | 
			
		||||
			android:layout_below="@id/toolbar">
 | 
			
		||||
 | 
			
		||||
		</RelativeLayout>
 | 
			
		||||
		</ImageView>
 | 
			
		||||
 | 
			
		||||
		<LinearLayout
 | 
			
		||||
			android:orientation="horizontal"
 | 
			
		||||
			android:orientation="vertical"
 | 
			
		||||
			android:layout_width="match_parent"
 | 
			
		||||
			android:layout_height="wrap_content"
 | 
			
		||||
			android:gravity="right">
 | 
			
		||||
			android:layout_below="@id/toolbar">
 | 
			
		||||
 | 
			
		||||
			<cc.winboll.studio.libaes.views.AButton
 | 
			
		||||
				android:layout_width="50dp"
 | 
			
		||||
				android:layout_height="36dp"
 | 
			
		||||
				android:text="◎"
 | 
			
		||||
				android:layout_gravity="center_vertical"
 | 
			
		||||
				android:layout_margin="10dp"
 | 
			
		||||
				android:id="@+id/activitybackgroundpictureAButton1"/>
 | 
			
		||||
			<RelativeLayout
 | 
			
		||||
				android:layout_width="wrap_content"
 | 
			
		||||
				android:layout_height="wrap_content">
 | 
			
		||||
 | 
			
		||||
			<cc.winboll.studio.libaes.views.AButton
 | 
			
		||||
				android:layout_width="50dp"
 | 
			
		||||
				android:layout_height="36dp"
 | 
			
		||||
				android:text="☑"
 | 
			
		||||
				android:layout_gravity="center_vertical"
 | 
			
		||||
				android:layout_margin="10dp"
 | 
			
		||||
				android:id="@+id/activitybackgroundpictureAButton2"/>
 | 
			
		||||
				<cc.winboll.studio.libaes.views.AButton
 | 
			
		||||
					android:layout_width="160dp"
 | 
			
		||||
					android:layout_height="36dp"
 | 
			
		||||
					android:text="Origin BG"
 | 
			
		||||
					android:id="@+id/activitybackgroundpictureAButton5"
 | 
			
		||||
					android:layout_alignParentLeft="true"
 | 
			
		||||
					android:layout_margin="5dp"/>
 | 
			
		||||
 | 
			
		||||
			<cc.winboll.studio.libaes.views.AButton
 | 
			
		||||
				android:layout_width="50dp"
 | 
			
		||||
				android:layout_height="36dp"
 | 
			
		||||
				android:text="[+]"
 | 
			
		||||
				android:layout_gravity="center_vertical"
 | 
			
		||||
				android:layout_margin="10dp"
 | 
			
		||||
				android:id="@+id/activitybackgroundpictureAButton3"/>
 | 
			
		||||
				<cc.winboll.studio.libaes.views.AButton
 | 
			
		||||
					android:layout_width="160dp"
 | 
			
		||||
					android:layout_height="36dp"
 | 
			
		||||
					android:text="Received BG"
 | 
			
		||||
					android:id="@+id/activitybackgroundpictureAButton4"
 | 
			
		||||
					android:layout_alignParentRight="true"
 | 
			
		||||
					android:layout_margin="5dp"/>
 | 
			
		||||
 | 
			
		||||
			<cc.winboll.studio.libaes.views.AButton
 | 
			
		||||
				android:layout_width="50dp"
 | 
			
		||||
				android:layout_height="36dp"
 | 
			
		||||
				android:text="[+~]"
 | 
			
		||||
				android:layout_gravity="center_vertical"
 | 
			
		||||
				android:layout_margin="10dp"
 | 
			
		||||
				android:id="@+id/activitybackgroundpictureAButton6"/>
 | 
			
		||||
			</RelativeLayout>
 | 
			
		||||
 | 
			
		||||
			<LinearLayout
 | 
			
		||||
				android:orientation="horizontal"
 | 
			
		||||
				android:layout_width="match_parent"
 | 
			
		||||
				android:layout_height="wrap_content"
 | 
			
		||||
				android:gravity="right">
 | 
			
		||||
 | 
			
		||||
				<cc.winboll.studio.libaes.views.AButton
 | 
			
		||||
					android:layout_width="50dp"
 | 
			
		||||
					android:layout_height="36dp"
 | 
			
		||||
					android:text="◎"
 | 
			
		||||
					android:layout_gravity="center_vertical"
 | 
			
		||||
					android:layout_margin="5dp"
 | 
			
		||||
					android:id="@+id/activitybackgroundpictureAButton1"/>
 | 
			
		||||
 | 
			
		||||
				<cc.winboll.studio.libaes.views.AButton
 | 
			
		||||
					android:layout_width="50dp"
 | 
			
		||||
					android:layout_height="36dp"
 | 
			
		||||
					android:text="☑"
 | 
			
		||||
					android:layout_gravity="center_vertical"
 | 
			
		||||
					android:layout_margin="5dp"
 | 
			
		||||
					android:id="@+id/activitybackgroundpictureAButton2"/>
 | 
			
		||||
 | 
			
		||||
				<cc.winboll.studio.libaes.views.AButton
 | 
			
		||||
					android:layout_width="50dp"
 | 
			
		||||
					android:layout_height="36dp"
 | 
			
		||||
					android:text="[+]"
 | 
			
		||||
					android:layout_gravity="center_vertical"
 | 
			
		||||
					android:layout_margin="5dp"
 | 
			
		||||
					android:id="@+id/activitybackgroundpictureAButton3"/>
 | 
			
		||||
 | 
			
		||||
				<cc.winboll.studio.libaes.views.AButton
 | 
			
		||||
					android:layout_width="50dp"
 | 
			
		||||
					android:layout_height="36dp"
 | 
			
		||||
					android:text="[+~]"
 | 
			
		||||
					android:layout_gravity="center_vertical"
 | 
			
		||||
					android:layout_margin="5dp"
 | 
			
		||||
					android:id="@+id/activitybackgroundpictureAButton6"/>
 | 
			
		||||
 | 
			
		||||
				<cc.winboll.studio.libaes.views.AButton
 | 
			
		||||
					android:layout_width="50dp"
 | 
			
		||||
					android:layout_height="36dp"
 | 
			
		||||
					android:text="[◐]"
 | 
			
		||||
					android:layout_gravity="center_vertical"
 | 
			
		||||
					android:layout_margin="5dp"
 | 
			
		||||
					android:id="@+id/activitybackgroundpictureAButton7"/>
 | 
			
		||||
				
 | 
			
		||||
				<cc.winboll.studio.libaes.views.AButton
 | 
			
		||||
					android:layout_width="50dp"
 | 
			
		||||
					android:layout_height="36dp"
 | 
			
		||||
					android:text="[○]"
 | 
			
		||||
					android:layout_gravity="center_vertical"
 | 
			
		||||
					android:layout_margin="5dp"
 | 
			
		||||
					android:id="@+id/activitybackgroundpictureAButton8"/>
 | 
			
		||||
				
 | 
			
		||||
			</LinearLayout>
 | 
			
		||||
 | 
			
		||||
		</LinearLayout>
 | 
			
		||||
 | 
			
		||||
	</LinearLayout>
 | 
			
		||||
 | 
			
		||||
</RelativeLayout>
 | 
			
		||||
	</RelativeLayout>
 | 
			
		||||
</LinearLayout>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,42 +2,34 @@
 | 
			
		||||
<LinearLayout
 | 
			
		||||
	xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
	xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
			
		||||
    android:layout_width="match_parent"
 | 
			
		||||
	android:layout_width="match_parent"
 | 
			
		||||
	android:layout_height="match_parent"
 | 
			
		||||
	android:orientation="vertical">
 | 
			
		||||
 | 
			
		||||
	<LinearLayout
 | 
			
		||||
		android:orientation="horizontal"
 | 
			
		||||
	<cc.winboll.studio.libaes.views.AToolbar
 | 
			
		||||
		android:layout_width="match_parent"
 | 
			
		||||
		android:layout_height="wrap_content">
 | 
			
		||||
		android:layout_height="@dimen/toolbar_height"
 | 
			
		||||
		android:id="@+id/toolbar"
 | 
			
		||||
		android:gravity="center_vertical"
 | 
			
		||||
		style="@style/DefaultAToolbar"/>
 | 
			
		||||
 | 
			
		||||
		<cc.winboll.studio.libaes.views.AToolbar
 | 
			
		||||
			android:layout_width="match_parent"
 | 
			
		||||
			android:layout_height="@dimen/toolbar_height"
 | 
			
		||||
			android:id="@+id/toolbar"
 | 
			
		||||
			android:gravity="center_vertical"
 | 
			
		||||
			style="@style/DefaultAToolbar"/>
 | 
			
		||||
 | 
			
		||||
	</LinearLayout>
 | 
			
		||||
 | 
			
		||||
	<LinearLayout
 | 
			
		||||
		android:orientation="vertical"
 | 
			
		||||
	<RelativeLayout
 | 
			
		||||
		android:layout_width="match_parent"
 | 
			
		||||
		android:layout_height="wrap_content"
 | 
			
		||||
		android:layout_height="0dp"
 | 
			
		||||
		android:layout_weight="1.0">
 | 
			
		||||
 | 
			
		||||
		<RelativeLayout
 | 
			
		||||
			android:layout_width="match_parent"
 | 
			
		||||
			android:layout_height="match_parent"
 | 
			
		||||
			android:id="@+id/activitymainRelativeLayout1"
 | 
			
		||||
			android:background="#FFEE2121"/>
 | 
			
		||||
 | 
			
		||||
		<FrameLayout
 | 
			
		||||
			android:layout_width="match_parent"
 | 
			
		||||
			android:layout_height="match_parent"
 | 
			
		||||
			android:id="@+id/activitymainFrameLayout1"/>
 | 
			
		||||
 | 
			
		||||
	</LinearLayout>
 | 
			
		||||
 | 
			
		||||
	<cc.winboll.studio.libappbase.LogView
 | 
			
		||||
		android:orientation="horizontal"
 | 
			
		||||
		android:layout_width="match_parent"
 | 
			
		||||
		android:layout_height="200dp"
 | 
			
		||||
		android:id="@+id/logview"/>
 | 
			
		||||
	</RelativeLayout>
 | 
			
		||||
 | 
			
		||||
</LinearLayout>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										54
									
								
								powerbell/src/main/res/layout/activity_pixelpicker.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,54 @@
 | 
			
		||||
<?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:layout_width="match_parent"
 | 
			
		||||
	android:layout_height="match_parent"
 | 
			
		||||
	android:orientation="vertical">
 | 
			
		||||
 | 
			
		||||
	<cc.winboll.studio.libaes.views.AToolbar
 | 
			
		||||
		android:layout_width="match_parent"
 | 
			
		||||
		android:layout_height="@dimen/toolbar_height"
 | 
			
		||||
		android:id="@+id/toolbar"
 | 
			
		||||
		android:gravity="center_vertical"
 | 
			
		||||
		style="@style/DefaultAToolbar"/>
 | 
			
		||||
 | 
			
		||||
	<RelativeLayout
 | 
			
		||||
		android:layout_width="match_parent"
 | 
			
		||||
		android:layout_height="0dp"
 | 
			
		||||
		android:layout_weight="1.0">
 | 
			
		||||
 | 
			
		||||
		<RelativeLayout
 | 
			
		||||
			android:layout_width="match_parent"
 | 
			
		||||
			android:layout_height="match_parent"
 | 
			
		||||
			android:background="#FFEE2121"
 | 
			
		||||
			android:id="@+id/activitypixelpickerRelativeLayout1"/>
 | 
			
		||||
 | 
			
		||||
		<FrameLayout
 | 
			
		||||
			android:id="@+id/imageContainer"
 | 
			
		||||
			android:layout_width="match_parent"
 | 
			
		||||
			android:layout_height="match_parent">
 | 
			
		||||
 | 
			
		||||
			<ImageView
 | 
			
		||||
				android:id="@+id/imageView"
 | 
			
		||||
				android:layout_width="match_parent"
 | 
			
		||||
				android:layout_height="match_parent"
 | 
			
		||||
				android:src="@drawable/ic_launcher"/>
 | 
			
		||||
 | 
			
		||||
		</FrameLayout>
 | 
			
		||||
 | 
			
		||||
		<TextView
 | 
			
		||||
			android:id="@+id/infoText"
 | 
			
		||||
			android:layout_width="wrap_content"
 | 
			
		||||
			android:layout_height="wrap_content"
 | 
			
		||||
			android:layout_alignParentBottom="true"
 | 
			
		||||
			android:layout_centerHorizontal="true"
 | 
			
		||||
			android:padding="10dp"
 | 
			
		||||
			android:background="#80000000"
 | 
			
		||||
			android:textColor="#FFFFFF"
 | 
			
		||||
			android:text="点击图片获取像素值"/>
 | 
			
		||||
 | 
			
		||||
	</RelativeLayout>
 | 
			
		||||
 | 
			
		||||
</LinearLayout>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										32
									
								
								powerbell/src/main/res/layout/dialog_pixel.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,32 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    android:layout_width="240dp"
 | 
			
		||||
    android:layout_height="wrap_content"
 | 
			
		||||
    android:orientation="vertical"
 | 
			
		||||
    android:padding="16dp">
 | 
			
		||||
 | 
			
		||||
    <TextView
 | 
			
		||||
        android:id="@+id/pixelColorView"
 | 
			
		||||
        android:layout_width="200dp"
 | 
			
		||||
        android:layout_height="200dp"
 | 
			
		||||
        android:layout_gravity="center_horizontal" />
 | 
			
		||||
 | 
			
		||||
    <TextView
 | 
			
		||||
        android:id="@+id/colorInfoText"
 | 
			
		||||
        android:layout_width="wrap_content"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:layout_gravity="center_horizontal"
 | 
			
		||||
        android:layout_marginTop="8dp"
 | 
			
		||||
        android:textColor="#333333"
 | 
			
		||||
        android:textSize="14sp" />
 | 
			
		||||
 | 
			
		||||
    <Button
 | 
			
		||||
        android:id="@+id/confirmButton"
 | 
			
		||||
        android:layout_width="match_parent"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:layout_marginTop="16dp"
 | 
			
		||||
        android:background="@android:color/holo_blue_light"
 | 
			
		||||
        android:text="确定"
 | 
			
		||||
        android:textColor="@android:color/white" />
 | 
			
		||||
</LinearLayout>
 | 
			
		||||
 | 
			
		||||
@@ -43,15 +43,6 @@
 | 
			
		||||
 | 
			
		||||
			</LinearLayout>
 | 
			
		||||
 | 
			
		||||
			<TextView
 | 
			
		||||
				android:layout_width="match_parent"
 | 
			
		||||
				android:layout_height="wrap_content"
 | 
			
		||||
				android:text="Tips"
 | 
			
		||||
				android:textSize="@dimen/text_content_size"
 | 
			
		||||
				android:id="@+id/fragmentandroidviewTextView1"
 | 
			
		||||
				android:background="@drawable/bg_frame"
 | 
			
		||||
				android:padding="10dp"/>
 | 
			
		||||
 | 
			
		||||
		</LinearLayout>
 | 
			
		||||
 | 
			
		||||
		<LinearLayout
 | 
			
		||||
@@ -205,6 +196,21 @@
 | 
			
		||||
 | 
			
		||||
		</LinearLayout>
 | 
			
		||||
 | 
			
		||||
		<LinearLayout
 | 
			
		||||
			android:layout_height="wrap_content"
 | 
			
		||||
			android:layout_width="match_parent">
 | 
			
		||||
 | 
			
		||||
			<TextView
 | 
			
		||||
				android:layout_width="match_parent"
 | 
			
		||||
				android:layout_height="wrap_content"
 | 
			
		||||
				android:text="Tips"
 | 
			
		||||
				android:textSize="@dimen/text_content_size"
 | 
			
		||||
				android:id="@+id/fragmentandroidviewTextView1"
 | 
			
		||||
				android:background="@drawable/bg_frame"
 | 
			
		||||
				android:padding="10dp"/>
 | 
			
		||||
 | 
			
		||||
		</LinearLayout>
 | 
			
		||||
 | 
			
		||||
	</LinearLayout>
 | 
			
		||||
 | 
			
		||||
</RelativeLayout>
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,9 @@
 | 
			
		||||
    <item
 | 
			
		||||
        android:id="@+id/action_changepicture"
 | 
			
		||||
        android:title="@string/item_changepicture"/>
 | 
			
		||||
    <item
 | 
			
		||||
        android:id="@+id/action_log"
 | 
			
		||||
        android:title="@string/item_logview"/>
 | 
			
		||||
    <item
 | 
			
		||||
        android:id="@+id/action_about"
 | 
			
		||||
        android:title="@string/item_aboutview"/>
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,7 @@
 | 
			
		||||
    <string name="texthint_CustomSlideToCleanRecord">Slide Right To Clean Up APP Record.</string>
 | 
			
		||||
    <string name="subtitle_activity_clearrecord">清理记录</string>
 | 
			
		||||
    <string name="subtitle_activity_backgroundpicture">更换背景图片</string>
 | 
			
		||||
    <string name="subtitle_activity_pixelpicker">背景像素拾取</string>
 | 
			
		||||
    <string name="subtitle_activity_about">关于应用</string>
 | 
			
		||||
    <string name="msg_AOHPCTCSeekBar_ClearRecord">>>>Seek 100% To Clear Battery Record.>>></string>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,7 @@
 | 
			
		||||
    <string name="texthint_CustomSlideToCleanRecord">Slide Right To Clean Up APP Record.</string>
 | 
			
		||||
    <string name="subtitle_activity_clearrecord">Clean Record</string>
 | 
			
		||||
    <string name="subtitle_activity_backgroundpicture">Background Picture</string>
 | 
			
		||||
    <string name="subtitle_activity_pixelpicker">Pixel Picker</string>
 | 
			
		||||
    <string name="subtitle_activity_about">About The APP</string>
 | 
			
		||||
    <string name="msg_AOHPCTCSeekBar_ClearRecord">>>>Seek 100% To Clear Battery Record.>>></string>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -52,3 +52,7 @@
 | 
			
		||||
// Ollama 项目编译设置
 | 
			
		||||
//include ':ollama'
 | 
			
		||||
//rootProject.name = "ollama"
 | 
			
		||||
 | 
			
		||||
// NumTable 项目编译设置
 | 
			
		||||
//include ':numtable'
 | 
			
		||||
//rootProject.name = "numtable"
 | 
			
		||||
 
 | 
			
		||||