Compare commits
	
		
			308 Commits
		
	
	
		
			aes-v15.8.
			...
			contacts-v
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| eb8d37c340 | |||
|   | d65a839878 | ||
| 6656161903 | |||
|   | edc63c750b | ||
| b170085482 | |||
| 6e8ba3394d | |||
| b91fe3c16b | |||
| e30b7acfdb | |||
| 6221555dfe | |||
|   | c60eb29f4f | ||
|   | 3efcf40025 | ||
|   | 27e948b86d | ||
|   | ae4742f3e2 | ||
|   | d3312cbb29 | ||
|   | 58f3778dff | ||
|   | f5e2961445 | ||
|   | e4e9c31f02 | ||
|   | a02a7efe43 | ||
|   | 2220efd009 | ||
|   | 0bc0fdf8c2 | ||
|   | 6b7b07a6fd | ||
|   | 8f3417818d | ||
|   | ca280c7334 | ||
|   | 8172cf7b6f | ||
|   | 8014f4149c | ||
|   | 3c36ed589a | ||
|   | 9768103741 | ||
|   | 787b8f0d77 | ||
|   | 1d58126fd8 | ||
|   | 7b5a3d2d71 | ||
|   | a988a9d4f6 | ||
|   | 04a67e666b | ||
|   | 348edc8aaf | ||
|   | 5970ae33c8 | ||
|   | 91a44f48ef | ||
|   | c66e9a090b | ||
|   | 7a14b55247 | ||
|   | fab68f16c8 | ||
|   | 3a97c6135f | ||
|   | cdad017d8c | ||
|   | bf84382963 | ||
|   | 34601fc5b1 | ||
|   | a40dbcfb61 | ||
|   | 4d344b299b | ||
|   | 37b0867d34 | ||
|   | cdfbb082d2 | ||
|   | 7e476894a7 | ||
|   | 0e8ae2e020 | ||
|   | 48623a2805 | ||
|   | b505156211 | ||
|   | 91b30fb576 | ||
|   | ab3ac72d54 | ||
|   | 73285c8779 | ||
|   | fa338ec8c7 | ||
|   | 7a3a1f4bcd | ||
|   | ea65810e7d | ||
|   | ad991e3da2 | ||
|   | 7e263447c8 | ||
|   | 80201e8370 | ||
|   | ea0473606a | ||
|   | 870e9a94fb | ||
|   | 2421ecb943 | ||
|   | 687fff7216 | ||
|   | 50d4cd830b | ||
|   | 2079822c00 | ||
|   | 297c76f328 | ||
|   | 43b18ee662 | ||
|   | 3ec3a4cfc2 | ||
|   | 2c10a9f38c | ||
|   | e62888636e | ||
|   | 364980dd02 | ||
|   | 0e155e4f3a | ||
|   | 89febba5a9 | ||
|   | 653330f8e1 | ||
|   | 386c73effc | ||
|   | b385583c5a | ||
|   | e7a9be2f56 | ||
|   | b27f7b0080 | ||
|   | 0a440419ff | ||
|   | 9e189ed5ac | ||
|   | 0fb6aadc72 | ||
|   | 46f3315b02 | ||
|   | c0ff228845 | ||
|   | a0fe8f17a8 | ||
|   | 16bd40fc59 | ||
|   | ffaf683c54 | ||
|   | 26f5f8d3db | ||
|   | 917e25cdc8 | ||
|   | 707bed52c7 | ||
|   | 3795cf8631 | ||
|   | b374f3117a | ||
|   | d581cd9842 | ||
|   | cef50d087d | ||
|   | 6d9adc124e | ||
|   | 52f738b45b | ||
|   | 9ece6778b7 | ||
|   | b7f8b76ace | ||
|   | 326e5fa68e | ||
|   | e9c8f9029e | ||
|   | c00bfa1292 | ||
|   | 11ee4dcf27 | ||
|   | 8974e24dce | ||
|   | 89142e379c | ||
|   | dabc671c27 | ||
|   | ed849e92d1 | ||
|   | 42d2522927 | ||
|   | 6a52b2a8c3 | ||
|   | 02ed5bd5d1 | ||
|   | b685665d0c | ||
|   | 1b030a2855 | ||
|   | dd577f1765 | ||
|   | 0f8350600d | ||
|   | 1777ebb8dc | ||
|   | 2b99f707e7 | ||
|   | 068c94e749 | ||
|   | 0b3bc7e296 | ||
|   | 6ef747bcf8 | ||
|   | a8e843c388 | ||
|   | d3fd593cb0 | ||
|   | 3aec176b8b | ||
|   | 5960f76238 | ||
|   | 1a118da827 | ||
|   | f73cad6f3e | ||
|   | 7130ecf023 | ||
|   | 953c8f08cd | ||
|   | 87b7557f72 | ||
|   | 77f5a156f8 | ||
|   | b34ea40536 | ||
|   | d202a3443d | ||
|   | 8c532c885f | ||
|   | 5fc4cb5f74 | ||
|   | 2a590a99fb | ||
|   | c6ad707ca2 | ||
|   | ee13a43fb6 | ||
|   | 5fbe1d8f71 | ||
|   | 38fe941a8b | ||
|   | e13c8e7af0 | ||
|   | a4988b5b68 | ||
|   | 04df902b6b | ||
|   | 33c71ea868 | ||
|   | 5507126f6b | ||
|   | d381c29452 | ||
|   | ba861d910e | ||
|   | f5d9aafe43 | ||
|   | e80d7e7b03 | ||
|   | 6e0a833fde | ||
|   | c596ee5fa4 | ||
|   | 5e99f1278e | ||
|   | b7158d1ebd | ||
|   | 9b51250ebf | ||
|   | eb61eb7306 | ||
|   | 31ad66685c | ||
|   | beb561ad6a | ||
|   | 8869265d60 | ||
|   | 2739627aff | ||
|   | 58e0be9cf4 | ||
|   | 9e9402f84e | ||
|   | ec18330022 | ||
|   | 8bb80ef575 | ||
|   | c1e6e32809 | ||
|   | 3e7722e2c0 | ||
|   | a1707e73b5 | ||
|   | 9dcbaa0d75 | ||
|   | 23920a7ff1 | ||
|   | 17c373c490 | ||
|   | 5f7c94b349 | ||
|   | c2b739d345 | ||
|   | 67a05cd457 | ||
|   | 554ab758bf | ||
|   | 20e118cd34 | ||
|   | f370ae8ffb | ||
|   | c92c874ea1 | ||
|   | 90a6116c0a | ||
|   | 45208ecbb1 | ||
|   | c28d655fe3 | ||
|   | 4b5905f74e | ||
|   | 6bd01780ec | ||
|   | a6699262f8 | ||
|   | 07b5e66875 | ||
|   | 91f5cf9051 | ||
|   | ea2d38defc | ||
|   | e430b7abe4 | ||
|   | 945eadb617 | ||
|   | c5bffc5eef | ||
|   | 88597fe407 | ||
|   | 53f985533a | ||
|   | a3950f13ad | ||
|   | c878e9dc02 | ||
|   | f2f7cab330 | ||
|   | 0e3b9dc760 | ||
|   | 6c8b0dcfa5 | ||
|   | 7de8a4f084 | ||
|   | 219c6614be | ||
|   | 0f5bb020b9 | ||
|   | 7794ff80ec | ||
|   | 7463ad3352 | ||
|   | 69187e3ed0 | ||
|   | 753032efed | ||
|   | 2b4c43c9af | ||
|   | 711c98d556 | ||
|   | 202205588a | ||
|   | 42c4978b44 | ||
|   | 1a2b7b862d | ||
|   | 8730f434dd | ||
|   | eb253b374f | ||
|   | c4e88e9593 | ||
|   | 08d9d92ae4 | ||
|   | 74841c08dc | ||
|   | 945bacb825 | ||
|   | 0e464495fd | ||
|   | f8944490f8 | ||
|   | 733af004f6 | ||
|   | c03568e1f5 | ||
|   | a0575a5e8b | ||
|   | 0e57ce679e | ||
|   | f9211a8eb4 | ||
|   | 4c31ff9b54 | ||
|   | 8cf610962e | ||
|   | 3071d186ec | ||
|   | df10306059 | ||
|   | ccdb9c5abd | ||
|   | f27209ab87 | ||
|   | e8682ce410 | ||
|   | 2e4003dae0 | ||
|   | 198b0975ce | ||
|   | 24a578a9d2 | ||
|   | 46de24447f | ||
|   | 2a819e94e4 | ||
|   | 6635358ec5 | ||
|   | ac1c008035 | ||
|   | b124487cb1 | ||
|   | 9621d35f79 | ||
|   | 17de0832a6 | ||
|   | f53b222b7f | ||
|   | 0c0cde8406 | ||
|   | 46967065c0 | ||
|   | 8edbff5ac1 | ||
|   | 434f8a8549 | ||
|   | c04be60b13 | ||
|   | 641098f8fb | ||
|   | dba54ac4b2 | ||
|   | c6cd779889 | ||
|   | dfb1692a04 | ||
|   | c83c8f66b3 | ||
|   | cd7b5f38bf | ||
|   | 0c2e73b82e | ||
|   | 7b1838ff8e | ||
|   | 73ff3d1726 | ||
|   | a69572e216 | ||
|   | fa79c3f807 | ||
|   | fde4b275f7 | ||
|   | d66d9373ff | ||
|   | f32ed94e4e | ||
|   | 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 | ||
|   | db3a3644a8 | ||
|   | 556bfa7024 | ||
|   | 4842a1ec30 | ||
|   | 89dac91cc6 | ||
|   | 3809c1bcab | ||
|   | b0388a2972 | ||
|   | bd5a1f18ce | ||
|   | 99798b4816 | ||
|   | f93b6047a8 | ||
|   | daa3f858a0 | ||
|   | 3fded32426 | ||
|   | 8f85006040 | ||
|   | e28b0bd75e | ||
|   | 77e98bafe4 | ||
|   | ff14d0c0c3 | ||
|   | 950be3a182 | ||
|   | 1f20fca9be | ||
|   | 9a0ee889ba | ||
|   | c40066ca4d | ||
|   | a5083cc52f | ||
|   | 6cce9c4d3f | ||
|   | df18c34976 | ||
|   | 22ca83b5b7 | ||
|   | 98233ce148 | ||
|   | b61c63c426 | ||
|   | f02dc215ca | ||
|   | 1c27d0ccdc | ||
|   | 803745d12e | ||
|   | a66be9cd37 | 
							
								
								
									
										16
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										16
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -87,19 +87,15 @@ lint/tmp/ | ||||
| # Android Profiling | ||||
| *.hprof | ||||
|  | ||||
| # Custom | ||||
| .androidide | ||||
| # 忽略 Lint 输出文件 | ||||
| lint-results.xml | ||||
| lint-results.html | ||||
| winboll.properties | ||||
| local.properties | ||||
|  | ||||
| ## 忽略 AndroidIDE 临时文件夹 | ||||
| .androidide | ||||
|  | ||||
| ## 忽略模块应用编译配置 | ||||
| /settings.gradle | ||||
| /gradle.properties | ||||
|  | ||||
| ## 忽略 srv 纠结问题 | ||||
| /srv/ | ||||
|  | ||||
| ## 忽略 winboll-x 文件夹 | ||||
| /winboll-x/ | ||||
| /winboll.properties | ||||
| /local.properties | ||||
|   | ||||
							
								
								
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							| @@ -1,6 +1,3 @@ | ||||
| [submodule "libjc/jcc/libs"] | ||||
| 	path = libjc/jcc/libs | ||||
| 	url = https://gitea.winboll.cc/Studio/APP_libjc_jcc_libs.git | ||||
| [submodule "keystore"] | ||||
| 	path = keystore | ||||
| 	url = https://gitea.winboll.cc/Studio/keystore.git | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -2,6 +2,11 @@ | ||||
| // | ||||
|  | ||||
| android { | ||||
| 	def winbollProps = new Properties() | ||||
|     def winbollPropsFile = rootProject.file("${winbollFilePath}") | ||||
|     assert(winbollPropsFile.exists()) | ||||
|     winbollProps.load(new FileInputStream(winbollPropsFile)) | ||||
|      | ||||
|     // 读取秘钥配置文件 | ||||
|     // | ||||
|     def keyProps = new Properties() | ||||
| @@ -122,6 +127,7 @@ android { | ||||
|                                     FileOutputStream fos = new FileOutputStream(winbollBuildPropsFile) | ||||
|                                     winbollBuildProps.store(fos, "${winbollBuildPropsDesc}"); | ||||
|                                     fos.close(); | ||||
| 									println "\n\n>>> Project build.properties saved.\n\n"; | ||||
|                                      | ||||
|                                     if(winbollBuildProps['libraryProject'] != "") { | ||||
|                                         // 如果应用 build.properties 文件设置了类库模块项目文件名 | ||||
| @@ -133,14 +139,15 @@ android { | ||||
|                                         java.nio.file.Path targetFilePath = libraryProjectBuildPropsFile.toPath(); | ||||
|                                         // 使用copyTo()方法复制文件,如果目标文件存在会被覆盖,可选参数可以选择不覆盖 | ||||
| 	                                    java.nio.file.Files.copy(sourceFilePath, targetFilePath, java.nio.file.StandardCopyOption.REPLACE_EXISTING); | ||||
| 										 | ||||
| 										println "\n\n>>> Library Project build.properties saved.\n\n"; | ||||
|                                     } | ||||
|                                 } | ||||
|                             } | ||||
|                              | ||||
|                             // 如果正在发布,就拷贝到 WinBoLL 标签管理文件夹 | ||||
|                             // 如果正在发布,就拷贝到 WinBoLL 标签管理文件夹,和处理 Git 仓库管理任务。 | ||||
|                             // | ||||
|                             if((variant.flavorName == "stage"&&variant.buildType.name == "debug") | ||||
|                                 || (variant.flavorName == "stage"&&variant.buildType.name == "release")){ | ||||
|                             if(variant.flavorName == "stage"&&variant.buildType.name == "release"){ | ||||
|                                 // 发布 APK 文件 | ||||
|                                 // | ||||
|                                 // 截取版本号的版本字段为短版本名 | ||||
| @@ -221,6 +228,7 @@ android { | ||||
|                                     } | ||||
|                                      | ||||
|                                     // 提交新的编译标志配置 | ||||
| 									println 'exec bashCommitAppPublishBuildFlagInfoFilePath ...' | ||||
|                                     def resultCommitBuildFlag = exec { | ||||
|                                         commandLine 'bash', '--', "${RootProjectDir}/${bashCommitAppPublishBuildFlagInfoFilePath}", "${RootProjectDir}", "${versionName}", variant.buildType.name , rootProject.name | ||||
|                                     } | ||||
| @@ -228,22 +236,78 @@ android { | ||||
|                                     assert(resultCommitBuildFlag.getExitValue() == 0) | ||||
|                                 } | ||||
|                             } //  if(variant.buildType.name == "release"){ | ||||
|                              | ||||
|                             // 如果公共目录存在就拷贝到公共目录并重命名为app.apk | ||||
|                             // | ||||
|                             File outCommonDir = new File("/sdcard/AppProjects") | ||||
|                             String commandAPKName = "app.apk" | ||||
|                             if(outCommonDir.exists()) { | ||||
|                                 copy{ | ||||
|                                     from file.outputFile | ||||
|                                     into outCommonDir | ||||
|                                     rename { | ||||
|                                         String fileName -> "${commandAPKName}" | ||||
| 							 | ||||
| 							// 如果正在调试发布版,就只生成和输出APK文件,不处理 Git 仓库提交与更新问题。 | ||||
| 							// | ||||
| 					        if(variant.flavorName == "stage"&&variant.buildType.name == "debug"){ | ||||
|                                 // 发布 APK 文件 | ||||
|                                 // | ||||
|                                 // 截取版本号的版本字段为短版本名 | ||||
|                                 String szVersionName = "${versionName}" | ||||
|                                 String[] szlistTemp = szVersionName.split("-") | ||||
|                                 String szShortVersionName = szlistTemp[0] | ||||
|                                 //String szCommonTagAPKName = "${rootProject.name}_" + szShortVersionName + ".apk" | ||||
|                                 String szCommonTagAPKName = project.rootDir.name + "_" + szShortVersionName + ".apk" | ||||
|                                 println "CommonTagAPKName is : " + szCommonTagAPKName | ||||
|                                  | ||||
|                                 //File outTagDir = new File(fWinBoLLStudioDir, "/${rootProject.name}/tag/") | ||||
|                                 File outTagDir = new File(fWinBoLLStudioDir, "/" + project.rootDir.name + "/${variant.buildType.name}/") | ||||
|                                 // 创建目标路径目录 | ||||
|                                 if(!outTagDir.exists()) { | ||||
|                                     outTagDir.mkdirs(); | ||||
|                                     println "Output Folder Created.(Tags) : " + outTagDir.getAbsolutePath() | ||||
|                                 } | ||||
|                                  | ||||
|                                 if(outTagDir.exists()) { | ||||
|                                     File targetAPK = new File(outTagDir, "${szCommonTagAPKName}") | ||||
|                                     if(targetAPK.exists()) { | ||||
|                                         // 标签版本APK文件已经存在,构建拷贝任务停止 | ||||
| 										println '如果是在调试 Stage 版应用包构建,请删除(注:在debug目录)现有的 Stage 应用包('+targetAPK.getAbsolutePath()+')。再编译一次。' | ||||
|                                         assert (!targetAPK.exists()) | ||||
|                                         // 可选择删除并继续输出APK文件 | ||||
|                                         //delete targetAPK | ||||
|                                     } | ||||
|                                     println "Output APK (Common): " + outCommonDir.getAbsolutePath() + "/${commandAPKName}" | ||||
|                                     // 复制一个备份 | ||||
|                                     copy{ | ||||
|                                         from file.outputFile | ||||
|                                         into outTagDir | ||||
|                                         rename { | ||||
|                                             String fileName -> "${outputFileName}" | ||||
|                                         } | ||||
|                                         println "Output APK (Tags): "+ outTagDir.getAbsolutePath() + "/${outputFileName}" | ||||
|                                     } | ||||
|                                     // 复制一个并重命名为短版本名 | ||||
|                                     copy{ | ||||
|                                         from file.outputFile | ||||
|                                         into outTagDir | ||||
|                                         rename { | ||||
|                                             String fileName -> "${szCommonTagAPKName}" | ||||
|                                         } | ||||
|                                         println "Output APK (Tags): "+ outTagDir.getAbsolutePath() + "/${szCommonTagAPKName}" | ||||
|                                     } | ||||
|                                      | ||||
|                                     //不保存编译标志配置 | ||||
|                                 } | ||||
|                             } | ||||
|                              | ||||
|                             // 如果配置了APK额外输出路径,就复制一份拷贝到额外路径。 | ||||
|                             // | ||||
| 							if(winbollProps['ExtraAPKOutputPath'] != null ) { | ||||
|                                 File apkFile = new File(winbollProps['ExtraAPKOutputPath']) | ||||
| 								File outCommonDir = apkFile.getParentFile(); | ||||
|                                 String commandAPKName = apkFile.getName(); | ||||
|                                 if(outCommonDir.exists()) { | ||||
|                                     copy{ | ||||
|                                         from file.outputFile | ||||
|                                         into outCommonDir | ||||
|                                         rename { | ||||
|                                             String fileName -> "${commandAPKName}" | ||||
|                                         } | ||||
|                                         println "Output APK (Common): " + outCommonDir.getAbsolutePath() + "/${commandAPKName}" | ||||
|                                     } | ||||
|                                 } | ||||
| 						    } | ||||
|                              | ||||
|                          | ||||
|                     } | ||||
| 	            }// End of (variant.getAssembleProvider().get().doLast {) | ||||
|   | ||||
							
								
								
									
										27
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								README.md
									
									
									
									
									
								
							| @@ -114,9 +114,12 @@ | ||||
|  | ||||
| # 本项目要实际运用需要注意以下几个步骤: | ||||
| # 在项目根目录下: | ||||
| ## 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,并取消相应项目模块的注释。 | ||||
| ## ★. 项目模块编译环境设置(必须) 在根目录拷贝 gradle.properties-androidx-demo 或者 gradle.properties-android-demo 文件为 gradle.properties。 | ||||
| ## ★. 项目 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 库登录用户信息, 和 APK 文件额外输出路径。 | ||||
|  | ||||
|  | ||||
| # ☆类库型项目编译方法 | ||||
| @@ -125,7 +128,8 @@ | ||||
| 设置属性 libraryProject=<类库项目模块文件夹名称> | ||||
| ### 再编译测试项目 | ||||
| $ bash .winboll/bashPublishAPKAddTag.sh <应用项目模块文件夹名称> | ||||
| #### 测试项目编译后,编译器会复制一份 APK 到以下路径:"/sdcard/WinBoLLStudio/APKs/<项目根目录名称>/tag/" 文件夹。 | ||||
| #### 测试项目编译后,编译器会复制一份 APK 到 路径:"/sdcard/WinBoLLStudio/APKs/<项目根目录名称>/tag/" 文件夹。 | ||||
| #### 若是 winboll.properties 文件的 [ExtraAPKOutputPath] 属性设置了路径。编译器也会复制一份 APK 到这个路径。 | ||||
| ### 最后编译类库项目 | ||||
| $ bash .winboll/bashPublishLIBAddTag.sh <类库项目模块文件夹名称> | ||||
| #### 类库模块编译命令执行后,编译器会发布到 WinBoLL Nexus Maven 库:Maven 库地址可以参阅根项目目录配置 build.gradle 文件。 | ||||
| @@ -133,4 +137,17 @@ $ bash .winboll/bashPublishLIBAddTag.sh <类库项目模块文件夹名称> | ||||
| # ☆应用型项目编译方法 | ||||
| ## 直接调用以下命令编译应用型项目 | ||||
| $ bash .winboll/bashPublishAPKAddTag.sh <应用项目模块文件夹名称> | ||||
| #### 应用模块编译命令执行后,编译器会复制一份 APK 到以下路径:"/sdcard/WinBoLLStudio/APKs/<项目根目录名称>/tag/" 文件夹。 | ||||
| #### 应用模块编译命令执行后,编译器会复制一份 APK 到 | ||||
| #### 测试项目编译后,编译器会复制一份 APK 到 路径:"/sdcard/WinBoLLStudio/APKs/<项目根目录名称>/tag/" 文件夹。 | ||||
| #### 若是 winboll.properties 文件的 [ExtraAPKOutputPath] 属性设置了路径。编译器也会复制一份 APK 到这个路径。 | ||||
|  | ||||
| ## ☆应用调试编译方法 | ||||
| 使用以下命令编译调试: | ||||
|  | ||||
| ### Beta调试使用: | ||||
| $ bash gradlew assembleBetaDebug | ||||
|  | ||||
| ### Stage调试使用: | ||||
| $ bash gradlew assembleStageDebug | ||||
|  | ||||
| ### 若是 winboll.properties 文件的 [ExtraAPKOutputPath] 属性设置了路径。编译器也会复制一份 APK 到这个路径。 | ||||
|   | ||||
| @@ -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:16:41 HKT 2025 | ||||
| stageCount=2 | ||||
| #Sun Aug 31 23:40:17 HKT 2025 | ||||
| stageCount=4 | ||||
| libraryProject=libaes | ||||
| baseVersion=15.8 | ||||
| publishVersion=15.8.1 | ||||
| baseVersion=15.9 | ||||
| publishVersion=15.9.3 | ||||
| buildCount=0 | ||||
| baseBetaVersion=15.8.2 | ||||
| baseBetaVersion=15.9.4 | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.aes; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/03/24 23:52:29 | ||||
|  * @Describe AES应用介绍窗口 | ||||
|  */ | ||||
| @@ -83,7 +83,7 @@ public class AboutActivity extends AppCompatActivity implements IWinBoLLActivity | ||||
|         appInfo.setAppGitOwner("Studio"); | ||||
|         appInfo.setAppGitAPPBranch(szBranchName); | ||||
|         appInfo.setAppGitAPPSubProjectFolder(szBranchName); | ||||
|         appInfo.setAppHomePage("https://discuz.winboll.cc/forum.php?mod=viewthread&tid=2&fromuid=1"); | ||||
|         appInfo.setAppHomePage("https://discuz.winboll.cc/forum.php?mod=viewthread&tid=3&extra=page%3D1"); | ||||
|         appInfo.setAppAPKName("AES"); | ||||
|         appInfo.setAppAPKFolderName("AES"); | ||||
|         //appInfo.setIsAddDebugTools(false); | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.aes; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2024/06/13 19:03:58 | ||||
|  * @Describe AES应用类 | ||||
|  */ | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.aes; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2024/06/13 19:05:52 | ||||
|  * @Describe 应用主窗口 | ||||
|  */ | ||||
|   | ||||
| @@ -0,0 +1 @@ | ||||
|  | ||||
|   | ||||
| @@ -67,6 +67,6 @@ dependencies { | ||||
|     // https://mvnrepository.com/artifact/com.android.support/recyclerview-v7 | ||||
|     api 'com.android.support:recyclerview-v7:28.0.0' | ||||
|      | ||||
|     api 'cc.winboll.studio:libapputils:15.8.1' | ||||
|     api 'cc.winboll.studio:libappbase:15.8.1' | ||||
|     api 'cc.winboll.studio:libapputils:15.8.5' | ||||
|     api 'cc.winboll.studio:libappbase:15.9.5' | ||||
| } | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| #Created by .winboll/winboll_app_build.gradle | ||||
| #Mon May 19 21:45:28 GMT 2025 | ||||
| stageCount=0 | ||||
| #Sun Aug 31 05:11:26 CST 2025 | ||||
| stageCount=1 | ||||
| libraryProject= | ||||
| baseVersion=15.0 | ||||
| publishVersion=15.0.0 | ||||
| buildCount=25 | ||||
| buildCount=0 | ||||
| baseBetaVersion=15.0.1 | ||||
|   | ||||
| @@ -0,0 +1 @@ | ||||
|  | ||||
|   | ||||
| @@ -29,7 +29,7 @@ android { | ||||
|         // versionName 更新后需要手动设置  | ||||
|         // .winboll/winbollBuildProps.properties 文件的 stageCount=0 | ||||
|         // Gradle编译环境下合起来的 versionName 就是 "${versionName}.0" | ||||
|         versionName "15.0" | ||||
|         versionName "15.1" | ||||
|         if(true) { | ||||
|             versionName = genVersionName("${versionName}") | ||||
|         } | ||||
| @@ -67,7 +67,7 @@ dependencies { | ||||
|     //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.1' | ||||
|     api 'cc.winboll.studio:libappbase:15.8.1' | ||||
|     api 'cc.winboll.studio:libaes:15.9.3' | ||||
|     api 'cc.winboll.studio:libapputils:15.8.5' | ||||
|     api 'cc.winboll.studio:libappbase:15.9.5' | ||||
| } | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| #Created by .winboll/winboll_app_build.gradle | ||||
| #Mon May 19 21:43:40 GMT 2025 | ||||
| stageCount=0 | ||||
| #Sun Aug 31 05:42:50 CST 2025 | ||||
| stageCount=2 | ||||
| libraryProject= | ||||
| baseVersion=15.0 | ||||
| publishVersion=15.0.0 | ||||
| buildCount=22 | ||||
| baseBetaVersion=15.0.1 | ||||
| baseVersion=15.1 | ||||
| publishVersion=15.1.1 | ||||
| buildCount=0 | ||||
| baseBetaVersion=15.1.2 | ||||
|   | ||||
| @@ -30,7 +30,7 @@ android { | ||||
|         // versionName 更新后需要手动设置  | ||||
|         // .winboll/winbollBuildProps.properties 文件的 stageCount=0 | ||||
|         // Gradle编译环境下合起来的 versionName 就是 "${versionName}.0" | ||||
|         versionName "15.8" | ||||
|         versionName "15.10" | ||||
|         if(true) { | ||||
|             versionName = genVersionName("${versionName}") | ||||
|         } | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| #Created by .winboll/winboll_app_build.gradle | ||||
| #Tue Jun 03 13:40:08 HKT 2025 | ||||
| stageCount=5 | ||||
| #Fri Sep 26 05:36:14 HKT 2025 | ||||
| stageCount=9 | ||||
| libraryProject=libappbase | ||||
| baseVersion=15.8 | ||||
| publishVersion=15.8.4 | ||||
| baseVersion=15.10 | ||||
| publishVersion=15.10.8 | ||||
| buildCount=0 | ||||
| baseBetaVersion=15.8.5 | ||||
| baseBetaVersion=15.10.9 | ||||
|   | ||||
| @@ -2,7 +2,10 @@ | ||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:tools="http://schemas.android.com/tools" > | ||||
|  | ||||
|     <application> | ||||
|     <application | ||||
| 		tools:replace="android:icon,android:roundIcon" | ||||
|         android:icon="@drawable/ic_winboll_beta" | ||||
|         android:roundIcon="@drawable/ic_winboll_beta"> | ||||
|  | ||||
|         <!-- Put flavor specific code here --> | ||||
|  | ||||
|   | ||||
| @@ -2,16 +2,19 @@ | ||||
| <manifest | ||||
|     xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     package="cc.winboll.studio.appbase"> | ||||
|  | ||||
| 	 | ||||
| 	<!-- 网络权限 --> | ||||
| 	<uses-permission android:name="android.permission.INTERNET" /> | ||||
| 		 | ||||
|     <application | ||||
|         android:name=".App" | ||||
|         android:icon="@drawable/ic_miapp" | ||||
|         android:icon="@drawable/ic_winboll" | ||||
|         android:label="@string/app_name" | ||||
|         android:theme="@style/MyAPPBaseTheme" | ||||
|         android:resizeableActivity="true" | ||||
|         android:process=":App"> | ||||
|  | ||||
|         <activity | ||||
| 		<activity | ||||
|             android:name=".MainActivity" | ||||
|             android:label="@string/app_name" | ||||
|             android:exported="true" | ||||
| @@ -24,105 +27,18 @@ | ||||
|  | ||||
|                 <category android:name="android.intent.category.LAUNCHER"/> | ||||
|  | ||||
|                 <action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES"/> | ||||
|  | ||||
|                 <category android:name="android.intent.category.DEFAULT"/> | ||||
|  | ||||
|             </intent-filter> | ||||
|  | ||||
|         </activity> | ||||
|  | ||||
|         <activity | ||||
|             android:name=".activities.NewActivity" | ||||
|             android:label="NewActivity" | ||||
|             android:exported="true" | ||||
|             android:resizeableActivity="true" | ||||
|             android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"/> | ||||
|  | ||||
|         <activity android:name=".activities.New2Activity" | ||||
|             android:label="New2Activity" | ||||
|             android:exported="true" | ||||
|             android:resizeableActivity="true" | ||||
|             android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"/> | ||||
|  | ||||
|         <service | ||||
|             android:name=".MyTileService" | ||||
|             android:exported="true" | ||||
|             android:label="@string/tileservice_name" | ||||
|             android:icon="@drawable/ic_launcher" | ||||
|             android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"> | ||||
|  | ||||
|             <intent-filter> | ||||
|  | ||||
|                 <action android:name="android.service.quicksettings.action.QS_TILE"/> | ||||
|  | ||||
|             </intent-filter> | ||||
|  | ||||
|         </service> | ||||
|  | ||||
|         <service | ||||
|             android:name=".services.MainService" | ||||
|             android:exported="true"/> | ||||
|  | ||||
|         <service | ||||
|             android:name="cc.winboll.studio.appbase.services.TestDemoBindService" | ||||
|             android:exported="true"/> | ||||
|  | ||||
|         <service | ||||
|             android:name="cc.winboll.studio.appbase.services.TestDemoService" | ||||
|             android:exported="true"/> | ||||
|  | ||||
|         <service android:name=".services.AssistantService"/> | ||||
|  | ||||
|         <receiver android:name="cc.winboll.studio.appbase.receivers.MainReceiver" | ||||
|             android:exported="true"> | ||||
|  | ||||
|             <intent-filter> | ||||
|  | ||||
|                 <action android:name="cc.winboll.studio.appbase.receivers.MainReceiver"/> | ||||
|  | ||||
|             </intent-filter> | ||||
|  | ||||
|         </receiver> | ||||
|  | ||||
|         <receiver | ||||
|             android:name=".widgets.APPNewsWidget" | ||||
|             android:exported="true"> | ||||
|  | ||||
|             <intent-filter> | ||||
|  | ||||
|                 <action android:name="cc.winboll.studio.appbase.widgets.APPNewsWidget.ACTION_WAKEUP_SERVICE"/> | ||||
|  | ||||
|                 <action android:name="cc.winboll.studio.appbase.widgets.APPNewsWidget.ACTION_RELOAD_REPORT"/> | ||||
|  | ||||
|                 <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/> | ||||
|  | ||||
|             </intent-filter> | ||||
|  | ||||
|             <meta-data | ||||
|                 android:name="android.appwidget.provider" | ||||
|                 android:resource="@xml/widget_provider_info_sos"/> | ||||
|  | ||||
|         </receiver> | ||||
|  | ||||
|         <receiver android:name=".receivers.APPNewsWidgetClickListener" | ||||
|             android:exported="true"> | ||||
|  | ||||
|             <intent-filter> | ||||
|  | ||||
|                 <action android:name="cc.winboll.studio.appbase.receivers.APPNewsWidgetClickListener.ACTION_PRE"/> | ||||
|  | ||||
|                 <action android:name="cc.winboll.studio.appbase.receivers.APPNewsWidgetClickListener.ACTION_NEXT"/> | ||||
|  | ||||
|             </intent-filter> | ||||
|  | ||||
|         </receiver> | ||||
|  | ||||
| 		 | ||||
|         <activity android:name=".GlobalApplication$CrashActivity"/> | ||||
| 		 | ||||
|         <meta-data | ||||
|             android:name="android.max_aspect" | ||||
|             android:value="4.0"/> | ||||
|  | ||||
|  | ||||
|     </application> | ||||
|  | ||||
| </manifest> | ||||
|   | ||||
| @@ -1,27 +1,19 @@ | ||||
| package cc.winboll.studio.appbase; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/01/05 09:54:42 | ||||
|  * @Describe APPbase 应用类 | ||||
|  */ | ||||
| import cc.winboll.studio.libappbase.GlobalApplication; | ||||
| import android.content.IntentFilter; | ||||
| import cc.winboll.studio.libappbase.sos.SOSCenterServiceReceiver; | ||||
| import cc.winboll.studio.libappbase.sos.SOS; | ||||
| import cc.winboll.studio.libappbase.GlobalApplication; | ||||
|  | ||||
| public class App extends GlobalApplication { | ||||
|  | ||||
|     public static final String TAG = "App"; | ||||
|      | ||||
|     SOSCenterServiceReceiver mSOSCenterServiceReceiver; | ||||
|      | ||||
|     @Override | ||||
|     public void onCreate() { | ||||
|         super.onCreate(); | ||||
|         mSOSCenterServiceReceiver = new SOSCenterServiceReceiver(); | ||||
|         IntentFilter intentFilter = new IntentFilter(); | ||||
|         intentFilter.addAction(SOS.ACTION_SOS); | ||||
|         registerReceiver(mSOSCenterServiceReceiver, intentFilter); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,44 +1,23 @@ | ||||
| package cc.winboll.studio.appbase; | ||||
|  | ||||
| import android.app.Activity; | ||||
| import android.content.ComponentName; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.net.Uri; | ||||
| import android.os.Bundle; | ||||
| import android.view.Menu; | ||||
| import android.view.MenuItem; | ||||
| import android.view.View; | ||||
| import android.widget.CheckBox; | ||||
| import android.widget.Toast; | ||||
| import android.widget.Toolbar; | ||||
| import cc.winboll.studio.appbase.R; | ||||
| import cc.winboll.studio.appbase.activities.NewActivity; | ||||
| import cc.winboll.studio.appbase.activities.WinBoLLActivity; | ||||
| import cc.winboll.studio.appbase.services.MainService; | ||||
| import cc.winboll.studio.appbase.services.TestDemoBindService; | ||||
| import cc.winboll.studio.appbase.services.TestDemoService; | ||||
| import cc.winboll.studio.libappbase.CrashHandler; | ||||
| import cc.winboll.studio.libappbase.GlobalApplication; | ||||
| import cc.winboll.studio.libappbase.GlobalCrashActivity; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| import cc.winboll.studio.libappbase.LogView; | ||||
| import cc.winboll.studio.libappbase.sos.SOS; | ||||
| import cc.winboll.studio.libappbase.utils.ToastUtils; | ||||
| import cc.winboll.studio.libappbase.widgets.StatusWidget; | ||||
| import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity; | ||||
| import cc.winboll.studio.libappbase.LogActivity; | ||||
| import cc.winboll.studio.libappbase.ToastUtils; | ||||
|  | ||||
| public class MainActivity extends WinBoLLActivity implements IWinBoLLActivity { | ||||
| public class MainActivity extends Activity { | ||||
|  | ||||
|     public static final String TAG = "MainActivity"; | ||||
|  | ||||
|     @Override | ||||
|     public Activity getActivity() { | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getTag() { | ||||
|         return TAG; | ||||
|     } | ||||
|  | ||||
|     Toolbar mToolbar; | ||||
|  | ||||
|     @Override | ||||
| @@ -49,9 +28,6 @@ public class MainActivity extends WinBoLLActivity implements IWinBoLLActivity { | ||||
|  | ||||
|         mToolbar = findViewById(R.id.toolbar); | ||||
|         setActionBar(mToolbar); | ||||
|  | ||||
|         CheckBox cbIsDebugMode = findViewById(R.id.activitymainCheckBox1); | ||||
|         cbIsDebugMode.setChecked(GlobalApplication.isDebuging()); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
| @@ -62,117 +38,37 @@ public class MainActivity extends WinBoLLActivity implements IWinBoLLActivity { | ||||
|  | ||||
|     @Override | ||||
|     public boolean onOptionsItemSelected(MenuItem item) { | ||||
| 		switch (item.getItemId()) { | ||||
| 			case R.id.item_home : { | ||||
| 					openWebsiteInBrowser(this); | ||||
| 				} | ||||
| 		} | ||||
|         // 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。 | ||||
|         return super.onOptionsItemSelected(item); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onDestroy() { | ||||
|         super.onDestroy(); | ||||
|         Intent intentAPPWidget = new Intent(this, StatusWidget.class); | ||||
|         intentAPPWidget.setAction(StatusWidget.ACTION_STATUS_UPDATE); | ||||
|         sendBroadcast(intentAPPWidget); | ||||
|     public void onCrashTest(View view) { | ||||
| 		for (int i = Integer.MIN_VALUE; i < Integer.MAX_VALUE; i++) { | ||||
| 			getString(i); | ||||
| 		} | ||||
|     } | ||||
|  | ||||
| 	public void onSwitchDebugMode(View view) { | ||||
|         boolean isDebuging = ((CheckBox)view).isChecked(); | ||||
|         GlobalApplication.setIsDebuging(isDebuging); | ||||
|         GlobalApplication.saveDebugStatus(); | ||||
|     public void onLogTest(View view) { | ||||
|         LogActivity.startLogActivity(this); | ||||
|     } | ||||
|  | ||||
|     public void onPreviewGlobalCrashActivity(View view) { | ||||
|         Intent intent = new Intent(this, GlobalCrashActivity.class); | ||||
|         intent.putExtra(CrashHandler.EXTRA_CRASH_INFO, "Demo log..."); | ||||
|         startActivity(intent); | ||||
|     } | ||||
|  | ||||
|     public void onStartCenter(View view) { | ||||
|         MainService.startMainService(this); | ||||
|     } | ||||
|  | ||||
|     public void onStopCenter(View view) { | ||||
|         MainService.stopMainService(this); | ||||
|     } | ||||
|  | ||||
|     public void onTestStopMainServiceWithoutSettingEnable(View view) { | ||||
|         LogUtils.d(TAG, "onTestStopMainServiceWithoutSettingEnable"); | ||||
|         stopService(new Intent(this, MainService.class)); | ||||
|     } | ||||
|  | ||||
|     public void onTestUseComponentStartService(View view) { | ||||
|         LogUtils.d(TAG, "onTestUseComponentStartService"); | ||||
|  | ||||
|         // 目标服务的包名和类名 | ||||
|         String packageName = this.getPackageName(); | ||||
|         String serviceClassName = TestDemoService.class.getName(); | ||||
|  | ||||
|         // 构建Intent | ||||
|         Intent intentService = new Intent(); | ||||
|         intentService.setComponent(new ComponentName(packageName, serviceClassName)); | ||||
|  | ||||
|         startService(intentService); | ||||
|     } | ||||
|  | ||||
|     public void onTestDemoServiceSOS(View view) { | ||||
|         Intent intent = new Intent(this, TestDemoService.class); | ||||
|         stopService(intent); | ||||
|         if (App.isDebuging()) { | ||||
|             SOS.sosToAppBaseBeta(this, TestDemoService.class.getName()); | ||||
|         } else { | ||||
|             SOS.sosToAppBase(this, TestDemoService.class.getName()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public void onSartTestDemoService(View view) { | ||||
|         Intent intent = new Intent(this, TestDemoService.class); | ||||
|         intent.setAction(TestDemoService.ACTION_ENABLE); | ||||
|         startService(intent); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     public void onStopTestDemoService(View view) { | ||||
|         Intent intent = new Intent(this, TestDemoService.class); | ||||
|         intent.setAction(TestDemoService.ACTION_DISABLE); | ||||
|         startService(intent); | ||||
|  | ||||
|         Intent intentStop = new Intent(this, TestDemoService.class); | ||||
|         stopService(intentStop); | ||||
|     } | ||||
|  | ||||
|     public void onStopTestDemoServiceNoSettings(View view) { | ||||
|         Intent intent = new Intent(this, TestDemoService.class); | ||||
|         stopService(intent); | ||||
|     } | ||||
|  | ||||
|     public void onSartTestDemoBindService(View view) { | ||||
|         Intent intent = new Intent(this, TestDemoBindService.class); | ||||
|         intent.setAction(TestDemoBindService.ACTION_ENABLE); | ||||
|         startService(intent); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     public void onStopTestDemoBindService(View view) { | ||||
|         Intent intent = new Intent(this, TestDemoBindService.class); | ||||
|         intent.setAction(TestDemoBindService.ACTION_DISABLE); | ||||
|         startService(intent); | ||||
|  | ||||
|         Intent intentStop = new Intent(this, TestDemoBindService.class); | ||||
|         stopService(intentStop); | ||||
|     } | ||||
|  | ||||
|     public void onStopTestDemoBindServiceNoSettings(View view) { | ||||
|         Intent intent = new Intent(this, TestDemoBindService.class); | ||||
|         stopService(intent); | ||||
|     } | ||||
|  | ||||
|     public void onTestOpenNewActivity(View view) { | ||||
|         GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(this, NewActivity.class); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onResume() { | ||||
|         super.onResume(); | ||||
|     } | ||||
|  | ||||
|  | ||||
| 	/** | ||||
| 	 * 唤起默认浏览器打开指定网站 | ||||
| 	 * @param context 上下文(如 Activity.this) | ||||
| 	 */ | ||||
| 	public void openWebsiteInBrowser(Context context) { | ||||
| 		// 目标网站地址 | ||||
| 		String url = "https://www.winboll.cc"; | ||||
| 		// 构建打开浏览器的意图 | ||||
| 		Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); | ||||
| 		// 设置标志:避免创建新的任务栈(可选,按需求调整) | ||||
| 		intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||||
| 		context.startActivity(intent); | ||||
| 		 | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -1,79 +0,0 @@ | ||||
| package cc.winboll.studio.appbase; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Date 2025/02/13 19:30:10 | ||||
|  */ | ||||
| import android.content.Context; | ||||
| import android.service.quicksettings.Tile; | ||||
| import android.service.quicksettings.TileService; | ||||
| import cc.winboll.studio.appbase.models.MainServiceBean; | ||||
| import cc.winboll.studio.appbase.services.MainService; | ||||
|  | ||||
| public class MyTileService extends TileService { | ||||
|     public static final String TAG = "MyTileService"; | ||||
|  | ||||
|     volatile static MyTileService _MyTileService; | ||||
|  | ||||
|     @Override | ||||
|     public void onStartListening() { | ||||
|         super.onStartListening(); | ||||
|         _MyTileService = this; | ||||
|         Tile tile = getQsTile(); | ||||
|         MainServiceBean bean = MainServiceBean.loadBean(this, MainServiceBean.class); | ||||
|         if (bean != null && bean.isEnable()) { | ||||
|             //MainService.startMainService(context); | ||||
|             tile.setState(Tile.STATE_ACTIVE); | ||||
|             tile.setIcon(android.graphics.drawable.Icon.createWithResource(this, R.drawable.ic_cloud)); | ||||
|         } else { | ||||
|             //MainService.stopMainService(context); | ||||
|             tile.setState(Tile.STATE_INACTIVE); | ||||
|             tile.setIcon(android.graphics.drawable.Icon.createWithResource(this, R.drawable.ic_cloud_outline)); | ||||
|         } | ||||
|         tile.updateTile(); | ||||
| //        Tile tile = getQsTile(); | ||||
| //        tile.setState(Tile.STATE_INACTIVE); | ||||
| //        tile.setLabel(getString(R.string.tileservice_name)); | ||||
| //        tile.setIcon(android.graphics.drawable.Icon.createWithResource(this, R.drawable.ic_cloud_outline)); | ||||
| //        tile.updateTile(); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onClick() { | ||||
|         super.onClick(); | ||||
|         Tile tile = getQsTile(); | ||||
|         MainServiceBean bean = MainServiceBean.loadBean(this, MainServiceBean.class); | ||||
|         if (bean == null) { | ||||
|             bean = new MainServiceBean(); | ||||
|         } | ||||
|  | ||||
|         if (tile.getState() == Tile.STATE_ACTIVE) { | ||||
|             bean.setIsEnable(false); | ||||
|             MainServiceBean.saveBean(this, bean); | ||||
|             MainService.stopMainService(this); | ||||
|         } else if (tile.getState() == Tile.STATE_INACTIVE) { | ||||
|             bean.setIsEnable(true); | ||||
|             MainServiceBean.saveBean(this, bean); | ||||
|             MainService.startMainService(this); | ||||
|         } | ||||
|         updateServiceIconStatus(this); | ||||
|     } | ||||
|  | ||||
|     public static void updateServiceIconStatus(Context context) { | ||||
|         if (_MyTileService == null) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         Tile tile = _MyTileService.getQsTile(); | ||||
|         MainServiceBean bean = MainServiceBean.loadBean(context, MainServiceBean.class); | ||||
|         if (bean != null && bean.isEnable()) { | ||||
|             tile.setState(Tile.STATE_ACTIVE); | ||||
|             tile.setIcon(android.graphics.drawable.Icon.createWithResource(context, R.drawable.ic_cloud)); | ||||
|         } else { | ||||
|             tile.setState(Tile.STATE_INACTIVE); | ||||
|             tile.setIcon(android.graphics.drawable.Icon.createWithResource(context, R.drawable.ic_cloud_outline)); | ||||
|         } | ||||
|         tile.updateTile(); | ||||
|     } | ||||
| } | ||||
| @@ -1,77 +0,0 @@ | ||||
| package cc.winboll.studio.appbase.activities; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Date 2025/03/25 11:46:40 | ||||
|  * @Describe 测试窗口2 | ||||
|  */ | ||||
| import android.app.Activity; | ||||
| import android.os.Bundle; | ||||
| import android.view.Menu; | ||||
| import android.view.MenuItem; | ||||
| import android.view.View; | ||||
| import android.widget.Toolbar; | ||||
| import cc.winboll.studio.appbase.R; | ||||
| import cc.winboll.studio.libappbase.GlobalApplication; | ||||
| import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity; | ||||
|  | ||||
| public class New2Activity extends WinBoLLActivity implements IWinBoLLActivity { | ||||
|  | ||||
|     public static final String TAG = "New2Activity"; | ||||
|  | ||||
|     Toolbar mToolbar; | ||||
|     //LogView mLogView; | ||||
|  | ||||
|     @Override | ||||
|     public Activity getActivity() { | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getTag() { | ||||
|         return TAG; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         setContentView(R.layout.activity_new2); | ||||
|  | ||||
| //        mLogView = findViewById(R.id.logview); | ||||
| //        mLogView.start(); | ||||
|         mToolbar = findViewById(R.id.toolbar); | ||||
|         setActionBar(mToolbar); | ||||
|          | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onResume() { | ||||
|         super.onResume(); | ||||
|         //mLogView.start(); | ||||
|     } | ||||
|  | ||||
|     public void onCloseThisActivity(View view) { | ||||
|         GlobalApplication.getWinBoLLActivityManager().finish(this); | ||||
|     } | ||||
|  | ||||
|     public void onCloseAllActivity(View view) { | ||||
|         GlobalApplication.getWinBoLLActivityManager().finishAll(); | ||||
|     } | ||||
|  | ||||
|     public void onNewActivity(View view) { | ||||
|         GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(this, NewActivity.class); | ||||
|     } | ||||
|      | ||||
|  | ||||
|     @Override | ||||
|     public boolean onCreateOptionsMenu(Menu menu) { | ||||
|         getMenuInflater().inflate(R.menu.toolbar_main, menu); | ||||
|         return super.onCreateOptionsMenu(menu); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean onOptionsItemSelected(MenuItem item) { | ||||
|         // 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。 | ||||
|         return super.onOptionsItemSelected(item); | ||||
|     } | ||||
| } | ||||
| @@ -1,75 +0,0 @@ | ||||
| package cc.winboll.studio.appbase.activities; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Date 2025/03/25 05:04:22 | ||||
|  */ | ||||
| import android.app.Activity; | ||||
| import android.os.Bundle; | ||||
| import android.view.Menu; | ||||
| import android.view.MenuItem; | ||||
| import android.view.View; | ||||
| import android.widget.Toolbar; | ||||
| import cc.winboll.studio.appbase.R; | ||||
| import cc.winboll.studio.libappbase.GlobalApplication; | ||||
| import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity; | ||||
|  | ||||
| public class NewActivity extends WinBoLLActivity implements IWinBoLLActivity { | ||||
|  | ||||
|     public static final String TAG = "NewActivity"; | ||||
|  | ||||
|     Toolbar mToolbar; | ||||
|     //LogView mLogView; | ||||
|  | ||||
|     @Override | ||||
|     public Activity getActivity() { | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getTag() { | ||||
|         return TAG; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         setContentView(R.layout.activity_new); | ||||
| //        mLogView = findViewById(R.id.logview); | ||||
| //        mLogView.start(); | ||||
|         mToolbar = findViewById(R.id.toolbar); | ||||
|         setActionBar(mToolbar); | ||||
|          | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onResume() { | ||||
|         super.onResume(); | ||||
|         //mLogView.start(); | ||||
|     } | ||||
|  | ||||
|     public void onCloseThisActivity(View view) { | ||||
|         GlobalApplication.getWinBoLLActivityManager().finish(this); | ||||
|     } | ||||
|  | ||||
|     public void onCloseAllActivity(View view) { | ||||
|         GlobalApplication.getWinBoLLActivityManager().finishAll(); | ||||
|     } | ||||
|  | ||||
|     public void onNew2Activity(View view) { | ||||
|         GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(this, New2Activity.class); | ||||
|     } | ||||
|      | ||||
|  | ||||
|     @Override | ||||
|     public boolean onCreateOptionsMenu(Menu menu) { | ||||
|         getMenuInflater().inflate(R.menu.toolbar_main, menu); | ||||
|         return super.onCreateOptionsMenu(menu); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean onOptionsItemSelected(MenuItem item) { | ||||
|         // 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。 | ||||
|         return super.onOptionsItemSelected(item); | ||||
|     } | ||||
| } | ||||
| @@ -1,38 +0,0 @@ | ||||
| package cc.winboll.studio.appbase.handlers; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Date 2025/02/14 03:51:40 | ||||
|  */ | ||||
| import android.os.Handler; | ||||
| import android.os.Message; | ||||
| import cc.winboll.studio.appbase.services.MainService; | ||||
| import java.lang.ref.WeakReference; | ||||
|  | ||||
| public class MainServiceHandler extends Handler { | ||||
|     public static final String TAG = "MainServiceHandler"; | ||||
|  | ||||
|     public static final int MSG_REMINDTHREAD = 0; | ||||
|  | ||||
|     WeakReference<MainService> serviceWeakReference; | ||||
|     public MainServiceHandler(MainService service) { | ||||
|         serviceWeakReference = new WeakReference<MainService>(service); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void handleMessage(Message msg) { | ||||
|         switch (msg.what) { | ||||
|             case MSG_REMINDTHREAD: // 处理下载完成消息,更新UI | ||||
|                 { | ||||
|                     // 显示提醒消息 | ||||
|                     // | ||||
|                     //LogUtils.d(TAG, "显示提醒消息"); | ||||
|                     MainService mainService = serviceWeakReference.get(); | ||||
|                     if (mainService != null) { | ||||
|                         mainService.appenMessage((String)msg.obj); | ||||
|                     } | ||||
|                     break; | ||||
|                 } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,67 +0,0 @@ | ||||
| package cc.winboll.studio.appbase.models; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Date 2025/02/13 07:06:13 | ||||
|  */ | ||||
| import android.util.JsonReader; | ||||
| import android.util.JsonWriter; | ||||
| import cc.winboll.studio.libappbase.BaseBean; | ||||
| import java.io.IOException; | ||||
|  | ||||
| public class MainServiceBean extends BaseBean { | ||||
|  | ||||
|     public static final String TAG = "MainServiceBean"; | ||||
|  | ||||
|     boolean isEnable; | ||||
|  | ||||
|     public MainServiceBean() { | ||||
|         this.isEnable = false; | ||||
|     } | ||||
|  | ||||
|     public void setIsEnable(boolean isEnable) { | ||||
|         this.isEnable = isEnable; | ||||
|     } | ||||
|  | ||||
|     public boolean isEnable() { | ||||
|         return isEnable; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getName() { | ||||
|         return MainServiceBean.class.getName(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { | ||||
|         super.writeThisToJsonWriter(jsonWriter); | ||||
|         jsonWriter.name("isEnable").value(isEnable()); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException { | ||||
|         if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else { | ||||
|             if (name.equals("isEnable")) { | ||||
|                 setIsEnable(jsonReader.nextBoolean()); | ||||
|             } else { | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException { | ||||
|         jsonReader.beginObject(); | ||||
|         while (jsonReader.hasNext()) { | ||||
|             String name = jsonReader.nextName(); | ||||
|             if (!initObjectsFromJsonReader(jsonReader, name)) { | ||||
|                 jsonReader.skipValue(); | ||||
|             } | ||||
|         } | ||||
|         // 结束 JSON 对象 | ||||
|         jsonReader.endObject(); | ||||
|         return this; | ||||
|     } | ||||
| } | ||||
| @@ -1,67 +0,0 @@ | ||||
| package cc.winboll.studio.appbase.models; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Date 2025/03/07 12:47:22 | ||||
|  * @Describe TestServiceBean | ||||
|  */ | ||||
| import android.util.JsonReader; | ||||
| import android.util.JsonWriter; | ||||
| import cc.winboll.studio.libappbase.BaseBean; | ||||
| import java.io.IOException; | ||||
|  | ||||
| public class TestDemoBindServiceBean extends BaseBean { | ||||
|  | ||||
|     public static final String TAG = "TestServiceBean"; | ||||
|  | ||||
|     boolean isEnable; | ||||
|  | ||||
|     public TestDemoBindServiceBean() { | ||||
|         this.isEnable = false; | ||||
|     } | ||||
|  | ||||
|     public void setIsEnable(boolean isEnable) { | ||||
|         this.isEnable = isEnable; | ||||
|     } | ||||
|  | ||||
|     public boolean isEnable() { | ||||
|         return isEnable; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getName() { | ||||
|         return TestDemoBindServiceBean.class.getName(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { | ||||
|         super.writeThisToJsonWriter(jsonWriter); | ||||
|         jsonWriter.name("isEnable").value(isEnable()); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException { | ||||
|         if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else { | ||||
|             if (name.equals("isEnable")) { | ||||
|                 setIsEnable(jsonReader.nextBoolean()); | ||||
|             } else { | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException { | ||||
|         jsonReader.beginObject(); | ||||
|         while (jsonReader.hasNext()) { | ||||
|             String name = jsonReader.nextName(); | ||||
|             if (!initObjectsFromJsonReader(jsonReader, name)) { | ||||
|                 jsonReader.skipValue(); | ||||
|             } | ||||
|         } | ||||
|         // 结束 JSON 对象 | ||||
|         jsonReader.endObject(); | ||||
|         return this; | ||||
|     } | ||||
| } | ||||
| @@ -1,68 +0,0 @@ | ||||
| package cc.winboll.studio.appbase.models; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Date 2025/03/07 12:49:21 | ||||
|  * @Describe TestDemoServiceBean | ||||
|  */ | ||||
| import android.util.JsonReader; | ||||
| import android.util.JsonWriter; | ||||
| import cc.winboll.studio.libappbase.BaseBean; | ||||
| import java.io.IOException; | ||||
|  | ||||
| public class TestDemoServiceBean extends BaseBean { | ||||
|  | ||||
|     public static final String TAG = "TestDemoServiceBean"; | ||||
|  | ||||
|     boolean isEnable; | ||||
|  | ||||
|     public TestDemoServiceBean() { | ||||
|         this.isEnable = false; | ||||
|     } | ||||
|  | ||||
|     public void setIsEnable(boolean isEnable) { | ||||
|         this.isEnable = isEnable; | ||||
|     } | ||||
|  | ||||
|     public boolean isEnable() { | ||||
|         return isEnable; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getName() { | ||||
|         return TestDemoServiceBean.class.getName(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { | ||||
|         super.writeThisToJsonWriter(jsonWriter); | ||||
|         jsonWriter.name("isEnable").value(isEnable()); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException { | ||||
|         if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else { | ||||
|             if (name.equals("isEnable")) { | ||||
|                 setIsEnable(jsonReader.nextBoolean()); | ||||
|             } else { | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException { | ||||
|         jsonReader.beginObject(); | ||||
|         while (jsonReader.hasNext()) { | ||||
|             String name = jsonReader.nextName(); | ||||
|             if (!initObjectsFromJsonReader(jsonReader, name)) { | ||||
|                 jsonReader.skipValue(); | ||||
|             } | ||||
|         } | ||||
|         // 结束 JSON 对象 | ||||
|         jsonReader.endObject(); | ||||
|         return this; | ||||
|     } | ||||
| } | ||||
| @@ -1,36 +0,0 @@ | ||||
| package cc.winboll.studio.appbase.receivers; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Date 2025/03/24 07:11:44 | ||||
|  */ | ||||
| import android.content.BroadcastReceiver; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import cc.winboll.studio.appbase.widgets.APPNewsWidget; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
|  | ||||
| public class APPNewsWidgetClickListener extends BroadcastReceiver { | ||||
|  | ||||
|     public static final String TAG = "APPNewsWidgetClickListener"; | ||||
|     public static final String ACTION_PRE = APPNewsWidgetClickListener.class.getName() + ".ACTION_PRE"; | ||||
|     public static final String ACTION_NEXT = APPNewsWidgetClickListener.class.getName() + ".ACTION_NEXT"; | ||||
|  | ||||
|     @Override | ||||
|     public void onReceive(Context context, Intent intent) { | ||||
|         String action = intent.getAction(); | ||||
|         if (action == null) { | ||||
|             LogUtils.d(TAG, String.format("action %s", action)); | ||||
|             return; | ||||
|         } | ||||
|         if (action.equals(ACTION_PRE)) { | ||||
|             LogUtils.d(TAG, "ACTION_PRE"); | ||||
|             APPNewsWidget.prePage(context); | ||||
|         } else if (action.equals(ACTION_NEXT)) { | ||||
|             LogUtils.d(TAG, "ACTION_NEXT"); | ||||
|             APPNewsWidget.nextPage(context); | ||||
|         } else { | ||||
|             LogUtils.d(TAG, String.format("action %s", action)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,119 +0,0 @@ | ||||
| package cc.winboll.studio.appbase.receivers; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Date 2025/02/13 06:58:04 | ||||
|  * @Describe 主要广播接收器 | ||||
|  */ | ||||
| import android.content.BroadcastReceiver; | ||||
| import android.content.ComponentName; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.content.IntentFilter; | ||||
| import cc.winboll.studio.appbase.services.MainService; | ||||
| import cc.winboll.studio.appbase.widgets.APPNewsWidget; | ||||
| import cc.winboll.studio.libappbase.AppUtils; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| import cc.winboll.studio.libappbase.models.APPModel; | ||||
| import cc.winboll.studio.libappbase.models.WinBoLLModel; | ||||
| import cc.winboll.studio.libappbase.models.WinBoLLNewsBean; | ||||
| import cc.winboll.studio.libappbase.sos.SOS; | ||||
| import cc.winboll.studio.libappbase.sos.SOSObject; | ||||
| import cc.winboll.studio.libappbase.utils.ToastUtils; | ||||
| import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity; | ||||
| import cc.winboll.studio.libappbase.winboll.WinBoLL; | ||||
| import java.io.IOException; | ||||
| import java.lang.ref.WeakReference; | ||||
| import java.text.SimpleDateFormat; | ||||
| import java.util.Date; | ||||
|  | ||||
| public class MainReceiver extends BroadcastReceiver { | ||||
|  | ||||
|     public static final String TAG = "MainReceiver"; | ||||
|  | ||||
|     public static final String ACTION_BOOT_COMPLETED = "android.intent.action.BOOT_COMPLETED"; | ||||
|  | ||||
|     WeakReference<MainService> mwrService; | ||||
|  | ||||
|     public MainReceiver(MainService service) { | ||||
|         mwrService = new WeakReference<MainService>(service); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onReceive(Context context, Intent intent) { | ||||
|         String szAction = intent.getAction(); | ||||
|         if (szAction.equals(ACTION_BOOT_COMPLETED)) { | ||||
|             ToastUtils.show("ACTION_BOOT_COMPLETED"); | ||||
|         } else if (szAction.equals(IWinBoLLActivity.ACTION_BIND)) { | ||||
|             LogUtils.d(TAG, "ACTION_BIND"); | ||||
|             LogUtils.d(TAG, String.format("context.getPackageName() %s", context.getPackageName())); | ||||
|             LogUtils.d(TAG, String.format("intent.getAction() %s", intent.getAction())); | ||||
|             String szWinBoLLModel = intent.getStringExtra(WinBoLL.EXTRA_WINBOLLMODEL); | ||||
|             LogUtils.d(TAG, String.format("szAPPModel %s", szWinBoLLModel)); | ||||
|             if (szWinBoLLModel != null && !szWinBoLLModel.equals("")) { | ||||
|                 try { | ||||
|                     WinBoLLModel bean = WinBoLLModel.parseStringToBean(szWinBoLLModel, WinBoLLModel.class); | ||||
|                     if (bean != null) { | ||||
|                         String szAppPackageName = bean.getAppPackageName(); | ||||
|                         LogUtils.d(TAG, String.format("szAppPackageName %s", szAppPackageName)); | ||||
|                         String szAppMainServiveName = bean.getAppMainServiveName(); | ||||
|                         LogUtils.d(TAG, String.format("szAppMainServiveName %s", szAppMainServiveName)); | ||||
|                         mwrService.get().bindWinBoLLModelConnection(bean); | ||||
|                     } | ||||
|                 } catch (IOException e) { | ||||
|                     LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|                 } | ||||
|             } | ||||
|         } else if (intent.getAction().equals(SOS.ACTION_SOS)) { | ||||
|             LogUtils.d(TAG, "ACTION_SOS"); | ||||
|             String sos = intent.getStringExtra(SOS.EXTRA_OBJECT); | ||||
|             LogUtils.d(TAG, String.format("SOS %s", sos)); | ||||
|             if (sos != null && !sos.equals("")) { | ||||
|                 SOSObject bean = SOS.parseSOSObject(sos); | ||||
|                 if (bean != null) { | ||||
|                     String szObjectPackageName = bean.getObjectPackageName(); | ||||
|                     LogUtils.d(TAG, String.format("szObjectPackageName %s", szObjectPackageName)); | ||||
|                     String szObjectServiveName = bean.getObjectServiveName(); | ||||
|                     LogUtils.d(TAG, String.format("szObjectServiveName %s", szObjectServiveName)); | ||||
|  | ||||
|                     Intent intentService = new Intent(); | ||||
|                     intentService.setComponent(new ComponentName(szObjectPackageName, szObjectServiveName)); | ||||
|                     context.startService(intentService); | ||||
|  | ||||
|                     String appName = AppUtils.getAppNameByPackageName(context, szObjectPackageName); | ||||
|                     LogUtils.d(TAG, String.format("appName %s", appName)); | ||||
|                     WinBoLLNewsBean appWinBoLLNewsBean = new WinBoLLNewsBean(appName); | ||||
|                     SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); | ||||
|                     String currentTime = sdf.format(new Date()); | ||||
|                     StringBuilder sbLine = new StringBuilder(); | ||||
|                     sbLine.append("["); | ||||
|                     sbLine.append(currentTime); | ||||
|                     sbLine.append("] Power to "); | ||||
|                     sbLine.append(appName); | ||||
|                     appWinBoLLNewsBean.setMessage(sbLine.toString()); | ||||
|  | ||||
|                     APPNewsWidget.addWinBoLLNewsBean(context, appWinBoLLNewsBean); | ||||
|  | ||||
|                     Intent intentWidget = new Intent(context, APPNewsWidget.class); | ||||
|                     intentWidget.setAction(APPNewsWidget.ACTION_RELOAD_REPORT); | ||||
|                     context.sendBroadcast(intentWidget); | ||||
|                 } | ||||
|  | ||||
|  | ||||
|             } | ||||
|         } else { | ||||
|             ToastUtils.show(szAction); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // 注册 Receiver | ||||
|     // | ||||
|     public void registerAction(MainService service) { | ||||
|         IntentFilter filter=new IntentFilter(); | ||||
|         filter.addAction(ACTION_BOOT_COMPLETED); | ||||
|         filter.addAction(SOS.ACTION_SOS); | ||||
|         filter.addAction(WinBoLL.ACTION_BIND); | ||||
|         //filter.addAction(Intent.ACTION_BATTERY_CHANGED); | ||||
|         service.registerReceiver(this, filter); | ||||
|     } | ||||
| } | ||||
| @@ -1,138 +0,0 @@ | ||||
| package cc.winboll.studio.appbase.services; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Date 2025/02/14 03:38:31 | ||||
|  * @Describe 守护进程服务 | ||||
|  */ | ||||
| import android.app.Service; | ||||
| import android.content.ComponentName; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.content.ServiceConnection; | ||||
| import android.os.IBinder; | ||||
| import cc.winboll.studio.appbase.models.MainServiceBean; | ||||
| import cc.winboll.studio.appbase.services.AssistantService; | ||||
| import cc.winboll.studio.appbase.services.MainService; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| import android.os.Binder; | ||||
|  | ||||
| public class AssistantService extends Service { | ||||
|  | ||||
|     public static final String TAG = "AssistantService"; | ||||
|  | ||||
|     MainServiceBean mMainServiceBean; | ||||
|     MyServiceConnection mMyServiceConnection; | ||||
|     MainService mMainService; | ||||
|     boolean isBound = false; | ||||
|     volatile boolean isThreadAlive = false; | ||||
|  | ||||
|     public synchronized void setIsThreadAlive(boolean isThreadAlive) { | ||||
|         LogUtils.d(TAG, "setIsThreadAlive(...)"); | ||||
|         LogUtils.d(TAG, String.format("isThreadAlive %s", isThreadAlive)); | ||||
|         this.isThreadAlive = isThreadAlive; | ||||
|     } | ||||
|  | ||||
|     public boolean isThreadAlive() { | ||||
|         return isThreadAlive; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public IBinder onBind(Intent intent) { | ||||
|         return new MyBinder(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onCreate() { | ||||
|         LogUtils.d(TAG, "onCreate"); | ||||
|         super.onCreate(); | ||||
|  | ||||
|         //mMyBinder = new MyBinder(); | ||||
|         if (mMyServiceConnection == null) { | ||||
|             mMyServiceConnection = new MyServiceConnection(); | ||||
|         } | ||||
|         // 设置运行参数 | ||||
|         setIsThreadAlive(false); | ||||
|         assistantService(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int onStartCommand(Intent intent, int flags, int startId) { | ||||
|         LogUtils.d(TAG, "call onStartCommand(...)"); | ||||
|         assistantService(); | ||||
|         return START_STICKY; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onDestroy() { | ||||
|         //LogUtils.d(TAG, "onDestroy"); | ||||
|         setIsThreadAlive(false); | ||||
|         // 解除绑定 | ||||
|         if (isBound) { | ||||
|             unbindService(mMyServiceConnection); | ||||
|             isBound = false; | ||||
|         } | ||||
|         super.onDestroy(); | ||||
|     } | ||||
|  | ||||
|     // 运行服务内容 | ||||
|     // | ||||
|     void assistantService() { | ||||
|         LogUtils.d(TAG, "assistantService()"); | ||||
|         mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class); | ||||
|         LogUtils.d(TAG, String.format("mMainServiceBean.isEnable() %s", mMainServiceBean.isEnable())); | ||||
|         if (mMainServiceBean.isEnable()) { | ||||
|             LogUtils.d(TAG, String.format("mIsThreadAlive %s", isThreadAlive())); | ||||
|             if (isThreadAlive() == false) { | ||||
|                 // 设置运行状态 | ||||
|                 setIsThreadAlive(true); | ||||
|                 // 唤醒和绑定主进程 | ||||
|                 wakeupAndBindMain(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // 唤醒和绑定主进程 | ||||
|     // | ||||
|     void wakeupAndBindMain() { | ||||
|         LogUtils.d(TAG, "wakeupAndBindMain()"); | ||||
|         // 绑定服务的Intent | ||||
|         Intent intent = new Intent(this, MainService.class); | ||||
|         startService(new Intent(this, MainService.class)); | ||||
|         bindService(intent, mMyServiceConnection, Context.BIND_IMPORTANT); | ||||
|      | ||||
| //        startService(new Intent(this, MainService.class)); | ||||
| //        bindService(new Intent(AssistantService.this, MainService.class), mMyServiceConnection, Context.BIND_IMPORTANT); | ||||
|     } | ||||
|  | ||||
|     // 主进程与守护进程连接时需要用到此类 | ||||
|     // | ||||
|     class MyServiceConnection implements ServiceConnection { | ||||
|         @Override | ||||
|         public void onServiceConnected(ComponentName name, IBinder service) { | ||||
|             LogUtils.d(TAG, "onServiceConnected(...)"); | ||||
|             MainService.MyBinder binder = (MainService.MyBinder) service; | ||||
|             mMainService = binder.getService(); | ||||
|             isBound = true; | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void onServiceDisconnected(ComponentName name) { | ||||
|             LogUtils.d(TAG, "onServiceDisconnected(...)"); | ||||
|             mMainServiceBean = MainServiceBean.loadBean(AssistantService.this, MainServiceBean.class); | ||||
|             if (mMainServiceBean.isEnable()) { | ||||
|                 wakeupAndBindMain(); | ||||
|             } | ||||
|             isBound = false; | ||||
|             mMainService = null; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     // 用于返回服务实例的Binder | ||||
|     public class MyBinder extends Binder { | ||||
|         AssistantService getService() { | ||||
|             LogUtils.d(TAG, "AssistantService MyBinder getService()"); | ||||
|             return AssistantService.this; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,317 +0,0 @@ | ||||
| package cc.winboll.studio.appbase.services; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Date 2025/02/13 06:56:41 | ||||
|  * @Describe 拨号主服务 | ||||
|  * 参考: | ||||
|  * 进程保活-双进程守护的正确姿势 | ||||
|  * https://blog.csdn.net/sinat_35159441/article/details/75267380 | ||||
|  * Android Service之onStartCommand方法研究 | ||||
|  * https://blog.csdn.net/cyp331203/article/details/38920491 | ||||
|  */ | ||||
| import android.app.Service; | ||||
| import android.content.ComponentName; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.content.ServiceConnection; | ||||
| import android.os.Binder; | ||||
| import android.os.IBinder; | ||||
| import cc.winboll.studio.appbase.MyTileService; | ||||
| import cc.winboll.studio.appbase.handlers.MainServiceHandler; | ||||
| import cc.winboll.studio.appbase.models.MainServiceBean; | ||||
| import cc.winboll.studio.appbase.receivers.MainReceiver; | ||||
| import cc.winboll.studio.appbase.services.AssistantService; | ||||
| import cc.winboll.studio.appbase.threads.MainServiceThread; | ||||
| import cc.winboll.studio.appbase.widgets.APPNewsWidget; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| import cc.winboll.studio.libappbase.models.APPModel; | ||||
| import cc.winboll.studio.libappbase.models.WinBoLLModel; | ||||
| import java.util.ArrayList; | ||||
|  | ||||
| public class MainService extends Service { | ||||
|  | ||||
|     public static final String TAG = "MainService"; | ||||
|  | ||||
|     public static final int MSG_UPDATE_STATUS = 0; | ||||
|  | ||||
|     static MainService _mControlCenterService; | ||||
|  | ||||
|     volatile boolean isServiceRunning; | ||||
|  | ||||
|     MainServiceBean mMainServiceBean; | ||||
|     MainServiceThread mMainServiceThread; | ||||
|     MainServiceHandler mMainServiceHandler; | ||||
|     MyServiceConnection mMyServiceConnection; | ||||
|     AssistantService mAssistantService; | ||||
|     boolean isBound = false; | ||||
|     MainReceiver mMainReceiver; | ||||
|     ArrayList<APPConnection> mAPPModelConnectionList; | ||||
|  | ||||
|     @Override | ||||
|     public IBinder onBind(Intent intent) { | ||||
|         return new MyBinder(); | ||||
|     } | ||||
|  | ||||
|     public MainServiceThread getRemindThread() { | ||||
|         return mMainServiceThread; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onCreate() { | ||||
|         super.onCreate(); | ||||
|         LogUtils.d(TAG, "onCreate()"); | ||||
|         mAPPModelConnectionList = new ArrayList<APPConnection>(); | ||||
|  | ||||
|         _mControlCenterService = MainService.this; | ||||
|         isServiceRunning = false; | ||||
|         mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class); | ||||
|  | ||||
|         if (mMyServiceConnection == null) { | ||||
|             mMyServiceConnection = new MyServiceConnection(); | ||||
|         } | ||||
|         mMainServiceHandler = new MainServiceHandler(this); | ||||
|  | ||||
|         // 运行服务内容 | ||||
|         mainService(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int onStartCommand(Intent intent, int flags, int startId) { | ||||
|         LogUtils.d(TAG, "onStartCommand(...)"); | ||||
|         // 运行服务内容 | ||||
|         mainService(); | ||||
|         return (mMainServiceBean.isEnable()) ? START_STICKY : super.onStartCommand(intent, flags, startId); | ||||
|     } | ||||
|  | ||||
|     // 运行服务内容 | ||||
|     // | ||||
|     void mainService() { | ||||
|         LogUtils.d(TAG, "mainService()"); | ||||
|         mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class); | ||||
|         if (mMainServiceBean.isEnable() && isServiceRunning == false) { | ||||
|             LogUtils.d(TAG, "mainService() start running"); | ||||
|             isServiceRunning = true; | ||||
|             // 唤醒守护进程 | ||||
|             wakeupAndBindAssistant(); | ||||
|  | ||||
|             if (mMainReceiver == null) { | ||||
|                 // 注册广播接收器 | ||||
|                 mMainReceiver = new MainReceiver(this); | ||||
|                 mMainReceiver.registerAction(this); | ||||
|             } | ||||
|  | ||||
|             // 启动小部件 | ||||
|             Intent intentTimeWidget = new Intent(this, APPNewsWidget.class); | ||||
|             intentTimeWidget.setAction(APPNewsWidget.ACTION_RELOAD_REPORT); | ||||
|             this.sendBroadcast(intentTimeWidget); | ||||
|  | ||||
|             startMainServiceThread(); | ||||
|  | ||||
|             MyTileService.updateServiceIconStatus(this); | ||||
|  | ||||
|             LogUtils.i(TAG, "Main Service Is Start."); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // 唤醒和绑定守护进程 | ||||
|     // | ||||
|     void wakeupAndBindAssistant() { | ||||
|         LogUtils.d(TAG, "wakeupAndBindAssistant()"); | ||||
|  | ||||
|         Intent intent = new Intent(this, AssistantService.class); | ||||
|         startService(intent); | ||||
|         // 绑定服务的Intent | ||||
|         bindService(intent, mMyServiceConnection, Context.BIND_IMPORTANT); | ||||
|     } | ||||
|  | ||||
|     // 开启提醒铃声线程 | ||||
|     // | ||||
|     public void startMainServiceThread() { | ||||
|         LogUtils.d(TAG, "startMainServiceThread"); | ||||
|         if (mMainServiceThread == null) { | ||||
|             mMainServiceThread = new MainServiceThread(this, mMainServiceHandler); | ||||
|             LogUtils.d(TAG, "new MainServiceThread"); | ||||
|         } else { | ||||
|             if (mMainServiceThread.isExist() == true) { | ||||
|                 mMainServiceThread = new MainServiceThread(this, mMainServiceHandler); | ||||
|                 LogUtils.d(TAG, "renew MainServiceThread"); | ||||
|             } else { | ||||
|                 // 提醒进程正在进行中就更新状态后退出 | ||||
|                 LogUtils.d(TAG, "A mMainServiceThread running."); | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|         mMainServiceThread.start(); | ||||
|     } | ||||
|  | ||||
|     public void stopRemindThread() { | ||||
|         if (mMainServiceThread != null) { | ||||
|             mMainServiceThread.setIsExist(true); | ||||
|             mMainServiceThread = null; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onDestroy() { | ||||
|         //LogUtils.d(TAG, "onDestroy"); | ||||
|         mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class); | ||||
|         if (mMainServiceBean.isEnable() == false) { | ||||
|             // 设置运行状态 | ||||
|             isServiceRunning = false;// 解除绑定 | ||||
|             if (isBound) { | ||||
|                 unbindService(mMyServiceConnection); | ||||
|                 isBound = false; | ||||
|             } | ||||
|             // 停止守护进程 | ||||
|             Intent intent = new Intent(this, AssistantService.class); | ||||
|             stopService(intent); | ||||
|             // 停止Receiver | ||||
|             if (mMainReceiver != null) { | ||||
|                 unregisterReceiver(mMainReceiver); | ||||
|                 mMainReceiver = null; | ||||
|             } | ||||
|             // 停止前台通知栏 | ||||
|             stopForeground(true); | ||||
|             // 停止消息提醒进程 | ||||
|             stopRemindThread(); | ||||
|  | ||||
|             MyTileService.updateServiceIconStatus(this); | ||||
|  | ||||
|             super.onDestroy(); | ||||
|             //LogUtils.d(TAG, "onDestroy done"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public void bindWinBoLLModelConnection(WinBoLLModel bean) { | ||||
|         LogUtils.d(TAG, "bindAPPModelConnection(...)"); | ||||
|         // 清理旧的绑定链接 | ||||
|         for (int i = mAPPModelConnectionList.size() - 1; i > -1; i--) { | ||||
|             APPConnection item = mAPPModelConnectionList.get(i); | ||||
|             if (item.isBindToAPP(bean)) { | ||||
|                 LogUtils.d(TAG, "Bind Servive exist."); | ||||
|                 unbindService(item); | ||||
|                 mAPPModelConnectionList.remove(i); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // 绑定服务 | ||||
|         APPConnection appConnection = new APPConnection(); | ||||
|         Intent intentService = new Intent(); | ||||
|         intentService.setComponent(new ComponentName(bean.getAppPackageName(), bean.getAppMainServiveName())); | ||||
|         bindService(intentService, appConnection, Context.BIND_IMPORTANT); | ||||
|         mAPPModelConnectionList.add(appConnection); | ||||
|          | ||||
|         Intent intentWidget = new Intent(this, APPNewsWidget.class); | ||||
|         intentWidget.setAction(APPNewsWidget.ACTION_WAKEUP_SERVICE); | ||||
|         WinBoLLModel appSOSBean = new WinBoLLModel(bean.getAppPackageName(), bean.getAppMainServiveName()); | ||||
|         intentWidget.putExtra("APPSOSBean", appSOSBean.toString()); | ||||
|         sendBroadcast(intentWidget); | ||||
|     } | ||||
|  | ||||
|     public class APPConnection implements ServiceConnection { | ||||
|  | ||||
|         ComponentName mComponentName; | ||||
|  | ||||
|         boolean isBindToAPP(WinBoLLModel bean) { | ||||
|             return mComponentName != null | ||||
|                 && mComponentName.getClassName().equals(bean.getAppMainServiveName()) | ||||
|                 && mComponentName.getPackageName().equals(bean.getAppPackageName()); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void onServiceConnected(ComponentName name, IBinder service) { | ||||
|             LogUtils.d(TAG, "onServiceConnected(...)"); | ||||
|             mComponentName = name; | ||||
|             LogUtils.d(TAG, String.format("onServiceConnected : \ngetClassName %s\ngetPackageName %s", name.getClassName(), name.getPackageName())); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void onServiceDisconnected(ComponentName name) { | ||||
|             LogUtils.d(TAG, "onServiceDisconnected(...)"); | ||||
|             LogUtils.d(TAG, String.format("onServiceDisconnected : \ngetClassName %s\ngetPackageName %s", name.getClassName(), name.getPackageName())); | ||||
|  | ||||
|             // 尝试无参数启动一下服务 | ||||
|             String appPackage = mComponentName.getPackageName(); | ||||
|             LogUtils.d(TAG, String.format("appPackage %s", appPackage)); | ||||
|             String appMainServiceClassName = mComponentName.getClassName(); | ||||
|             LogUtils.d(TAG, String.format("appMainServiceClassName %s", appMainServiceClassName)); | ||||
|  | ||||
|             Intent intentService = new Intent(); | ||||
|             intentService.setComponent(new ComponentName(appPackage, appMainServiceClassName)); | ||||
|             startService(intentService); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     // 主进程与守护进程连接时需要用到此类 | ||||
|     // | ||||
|     private class MyServiceConnection implements ServiceConnection { | ||||
|         @Override | ||||
|         public void onServiceConnected(ComponentName name, IBinder service) { | ||||
|             LogUtils.d(TAG, "onServiceConnected(...)"); | ||||
|             AssistantService.MyBinder binder = (AssistantService.MyBinder) service; | ||||
|             mAssistantService = binder.getService(); | ||||
|             isBound = true; | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void onServiceDisconnected(ComponentName name) { | ||||
|             LogUtils.d(TAG, "onServiceDisconnected(...)"); | ||||
|  | ||||
|             if (mMainServiceBean.isEnable()) { | ||||
|                 // 唤醒守护进程 | ||||
|                 wakeupAndBindAssistant(); | ||||
|             } | ||||
|             isBound = false; | ||||
|             mAssistantService = null; | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|  | ||||
|     // 用于返回服务实例的Binder | ||||
|     public class MyBinder extends Binder { | ||||
|         MainService getService() { | ||||
|             LogUtils.d(TAG, "MainService MyBinder getService()"); | ||||
|             return MainService.this; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| //    // | ||||
| //    // 启动服务 | ||||
| //    // | ||||
| //    public static void startControlCenterService(Context context) { | ||||
| //        Intent intent = new Intent(context, MainService.class); | ||||
| //        context.startForegroundService(intent); | ||||
| //    } | ||||
| // | ||||
| //    // | ||||
| //    // 停止服务 | ||||
| //    // | ||||
| //    public static void stopControlCenterService(Context context) { | ||||
| //        Intent intent = new Intent(context, MainService.class); | ||||
| //        context.stopService(intent); | ||||
| //    } | ||||
|  | ||||
|     public void appenMessage(String message) { | ||||
|         LogUtils.d(TAG, String.format("Message : %s", message)); | ||||
|     } | ||||
|  | ||||
|     public static void stopMainService(Context context) { | ||||
|         LogUtils.d(TAG, "stopMainService"); | ||||
|         MainServiceBean bean = new MainServiceBean(); | ||||
|         bean.setIsEnable(false); | ||||
|         MainServiceBean.saveBean(context, bean); | ||||
|         context.stopService(new Intent(context, MainService.class)); | ||||
|     } | ||||
|  | ||||
|     public static void startMainService(Context context) { | ||||
|         LogUtils.d(TAG, "startMainService"); | ||||
|         MainServiceBean bean = new MainServiceBean(); | ||||
|         bean.setIsEnable(true); | ||||
|         MainServiceBean.saveBean(context, bean); | ||||
|         context.startService(new Intent(context, MainService.class)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -1,179 +0,0 @@ | ||||
| package cc.winboll.studio.appbase.services; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Date 2025/03/07 12:45:49 | ||||
|  * @Describe 启动时申请绑定到APPBase主服务的服务示例 | ||||
|  */ | ||||
| import android.app.Service; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.os.Binder; | ||||
| import android.os.IBinder; | ||||
| import cc.winboll.studio.appbase.App; | ||||
| import cc.winboll.studio.appbase.models.TestDemoBindServiceBean; | ||||
| import cc.winboll.studio.appbase.services.TestDemoBindService; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| import cc.winboll.studio.libappbase.sos.SOS; | ||||
| import cc.winboll.studio.libappbase.winboll.WinBoLL; | ||||
|  | ||||
| public class TestDemoBindService extends Service { | ||||
|  | ||||
|     public static final String TAG = "TestDemoBindService"; | ||||
|  | ||||
|     public static final String ACTION_ENABLE = TestDemoBindService.class.getName() + ".ACTION_ENABLE"; | ||||
|     public static final String ACTION_DISABLE = TestDemoBindService.class.getName() + ".ACTION_DISABLE"; | ||||
|  | ||||
|     volatile static TestThread _TestThread; | ||||
|  | ||||
|     volatile static boolean _IsRunning; | ||||
|  | ||||
|     public synchronized static void setIsRunning(boolean isRunning) { | ||||
|         _IsRunning = isRunning; | ||||
|     } | ||||
|  | ||||
|     public static boolean isRunning() { | ||||
|         return _IsRunning; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public IBinder onBind(Intent intent) { | ||||
|         return new MyBinder(); | ||||
|     } | ||||
|  | ||||
|     public class MyBinder extends Binder { | ||||
|         public TestDemoBindService getService() { | ||||
|             return TestDemoBindService.this; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onCreate() { | ||||
|         super.onCreate(); | ||||
|         LogUtils.d(TAG, "onCreate()"); | ||||
|  | ||||
|         run(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int onStartCommand(Intent intent, int flags, int startId) { | ||||
|         LogUtils.d(TAG, "onStartCommand(...)"); | ||||
|         TestDemoBindServiceBean bean = TestDemoBindServiceBean.loadBean(this, TestDemoBindServiceBean.class); | ||||
|         if (bean == null) { | ||||
|             bean = new TestDemoBindServiceBean(); | ||||
|         } | ||||
|  | ||||
|         if (intent.getAction() != null) { | ||||
|             if (intent.getAction().equals(ACTION_ENABLE)) { | ||||
|                 bean.setIsEnable(true); | ||||
|                 LogUtils.d(TAG, "setIsEnable(true);"); | ||||
|                 TestDemoBindServiceBean.saveBean(this, bean); | ||||
|             } else if (intent.getAction().equals(ACTION_DISABLE)) { | ||||
|                 bean.setIsEnable(false); | ||||
|                 LogUtils.d(TAG, "setIsEnable(false);"); | ||||
|                 TestDemoBindServiceBean.saveBean(this, bean); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         run(); | ||||
|  | ||||
|         return (bean.isEnable()) ? START_STICKY : super.onStartCommand(intent, flags, startId); | ||||
|         //return super.onStartCommand(intent, flags, startId); | ||||
|     } | ||||
|  | ||||
|     void run() { | ||||
|         LogUtils.d(TAG, "run()"); | ||||
|         TestDemoBindServiceBean bean = TestDemoBindServiceBean.loadBean(this, TestDemoBindServiceBean.class); | ||||
|         if (bean == null) { | ||||
|             bean = new TestDemoBindServiceBean(); | ||||
|             TestDemoBindServiceBean.saveBean(this, bean); | ||||
|         } | ||||
|         if (bean.isEnable()) { | ||||
|             LogUtils.d(TAG, "run() bean.isEnable()"); | ||||
|             TestThread.getInstance(this).start(); | ||||
|             LogUtils.d(TAG, "_TestThread.start()"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     @Override | ||||
|     public void onDestroy() { | ||||
|         super.onDestroy(); | ||||
|         LogUtils.d(TAG, "onDestroy()"); | ||||
|         TestDemoBindServiceBean bean = TestDemoBindServiceBean.loadBean(this, TestDemoBindServiceBean.class); | ||||
|         if (bean == null) { | ||||
|             bean = new TestDemoBindServiceBean(); | ||||
|         } | ||||
|  | ||||
|         TestThread.getInstance(this).setIsExit(true); | ||||
|          | ||||
|         // 预防 APPBase 应用重启绑定失效。 | ||||
|         // 所以退出时检查本服务是否配置启用,如果启用就发送一个 SOS 信号。 | ||||
|         // 这样 APPBase 就会用组件方式启动本服务。 | ||||
|         if (bean.isEnable()) { | ||||
|             if (App.isDebuging()) { | ||||
|                 SOS.sosToAppBaseBeta(this, TestDemoBindService.class.getName()); | ||||
|             } else { | ||||
|                 SOS.sosToAppBase(this, TestDemoBindService.class.getName()); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         _IsRunning = false; | ||||
|     } | ||||
|  | ||||
|     static class TestThread extends Thread { | ||||
|  | ||||
|         volatile static TestThread _TestThread; | ||||
|         Context mContext; | ||||
|         volatile boolean isStarted = false; | ||||
|         volatile boolean isExit = false; | ||||
|  | ||||
|         TestThread(Context context) { | ||||
|             super(); | ||||
|             mContext = context; | ||||
|         } | ||||
|  | ||||
|         public static synchronized TestThread getInstance(Context context) { | ||||
|             if (_TestThread != null) { | ||||
|                 _TestThread.setIsExit(true); | ||||
|             } | ||||
|             _TestThread = new TestThread(context); | ||||
|  | ||||
|             return _TestThread; | ||||
|         } | ||||
|  | ||||
|         public synchronized void setIsExit(boolean isExit) { | ||||
|             this.isExit = isExit; | ||||
|         } | ||||
|  | ||||
|         public boolean isExit() { | ||||
|             return isExit; | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void run() { | ||||
|             if (isStarted == false) { | ||||
|                 isStarted = true; | ||||
|                 super.run(); | ||||
|                 LogUtils.d(TAG, "run() start"); | ||||
|                 if (App.isDebuging()) { | ||||
|                     WinBoLL.bindToAPPBaseBeta(mContext, TestDemoBindService.class.getName()); | ||||
|                 } else { | ||||
|                     WinBoLL.bindToAPPBase(mContext, TestDemoBindService.class.getName()); | ||||
|                 } | ||||
|  | ||||
|                 while (!isExit()) { | ||||
|                     LogUtils.d(TAG, "run()"); | ||||
|  | ||||
|                     try { | ||||
|                         Thread.sleep(1000); | ||||
|                     } catch (InterruptedException e) { | ||||
|                         LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 LogUtils.d(TAG, "run() exit"); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,155 +0,0 @@ | ||||
| package cc.winboll.studio.appbase.services; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Date 2025/03/07 12:39:24 | ||||
|  * @Describe 普通服务示例 | ||||
|  */ | ||||
| import android.app.Service; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.os.Binder; | ||||
| import android.os.IBinder; | ||||
| import cc.winboll.studio.appbase.models.TestDemoServiceBean; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
|  | ||||
| public class TestDemoService extends Service { | ||||
|  | ||||
|     public static final String TAG = "TestDemoService"; | ||||
|  | ||||
|     public static final String ACTION_ENABLE = TestDemoService.class.getName() + ".ACTION_ENABLE"; | ||||
|     public static final String ACTION_DISABLE = TestDemoService.class.getName() + ".ACTION_DISABLE"; | ||||
|  | ||||
|     volatile static TestThread _TestThread; | ||||
|  | ||||
|     volatile static boolean _IsRunning; | ||||
|  | ||||
|     public synchronized static void setIsRunning(boolean isRunning) { | ||||
|         _IsRunning = isRunning; | ||||
|     } | ||||
|  | ||||
|     public static boolean isRunning() { | ||||
|         return _IsRunning; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public IBinder onBind(Intent intent) { | ||||
|         return new MyBinder(); | ||||
|     } | ||||
|  | ||||
|     public class MyBinder extends Binder { | ||||
|         public TestDemoService getService() { | ||||
|             return TestDemoService.this; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onCreate() { | ||||
|         super.onCreate(); | ||||
|         LogUtils.d(TAG, "onCreate()"); | ||||
|  | ||||
|  | ||||
|         run(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int onStartCommand(Intent intent, int flags, int startId) { | ||||
|         LogUtils.d(TAG, "onStartCommand(...)"); | ||||
|         TestDemoServiceBean bean = TestDemoServiceBean.loadBean(this, TestDemoServiceBean.class); | ||||
|         if (bean == null) { | ||||
|             bean = new TestDemoServiceBean(); | ||||
|         } | ||||
|  | ||||
|         if (intent.getAction() != null) { | ||||
|             if (intent.getAction().equals(ACTION_ENABLE)) { | ||||
|                 bean.setIsEnable(true); | ||||
|                 LogUtils.d(TAG, "setIsEnable(true);"); | ||||
|                 TestDemoServiceBean.saveBean(this, bean); | ||||
|             } else if (intent.getAction().equals(ACTION_DISABLE)) { | ||||
|                 bean.setIsEnable(false); | ||||
|                 LogUtils.d(TAG, "setIsEnable(false);"); | ||||
|                 TestDemoServiceBean.saveBean(this, bean); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         run(); | ||||
|  | ||||
|         return (bean.isEnable()) ? START_STICKY : super.onStartCommand(intent, flags, startId); | ||||
|         //return super.onStartCommand(intent, flags, startId); | ||||
|     } | ||||
|  | ||||
|     void run() { | ||||
|         LogUtils.d(TAG, "run()"); | ||||
|         TestDemoServiceBean bean = TestDemoServiceBean.loadBean(this, TestDemoServiceBean.class); | ||||
|         if (bean == null) { | ||||
|             bean = new TestDemoServiceBean(); | ||||
|             TestDemoServiceBean.saveBean(this, bean); | ||||
|         } | ||||
|         if (bean.isEnable()) { | ||||
|             LogUtils.d(TAG, "run() bean.isEnable()"); | ||||
|             TestThread.getInstance(this).start(); | ||||
|             LogUtils.d(TAG, "_TestThread.start()"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     @Override | ||||
|     public void onDestroy() { | ||||
|         super.onDestroy(); | ||||
|         LogUtils.d(TAG, "onDestroy()"); | ||||
|         TestThread.getInstance(this).setIsExit(true); | ||||
|  | ||||
|         _IsRunning = false; | ||||
|     } | ||||
|  | ||||
|     static class TestThread extends Thread { | ||||
|  | ||||
|         volatile static TestThread _TestThread; | ||||
|         Context mContext; | ||||
|         volatile boolean isStarted = false; | ||||
|         volatile boolean isExit = false; | ||||
|  | ||||
|         TestThread(Context context) { | ||||
|             super(); | ||||
|             mContext = context; | ||||
|         } | ||||
|  | ||||
|         public static synchronized TestThread getInstance(Context context) { | ||||
|             if (_TestThread != null) { | ||||
|                 _TestThread.setIsExit(true); | ||||
|             } | ||||
|             _TestThread = new TestThread(context); | ||||
|  | ||||
|             return _TestThread; | ||||
|         } | ||||
|  | ||||
|         public synchronized void setIsExit(boolean isExit) { | ||||
|             this.isExit = isExit; | ||||
|         } | ||||
|  | ||||
|         public boolean isExit() { | ||||
|             return isExit; | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void run() { | ||||
|             if (isStarted == false) { | ||||
|                 isStarted = true; | ||||
|                 super.run(); | ||||
|                 LogUtils.d(TAG, "run() start"); | ||||
|  | ||||
|                 while (!isExit()) { | ||||
|                     LogUtils.d(TAG, "run()"); | ||||
|  | ||||
|                     try { | ||||
|                         Thread.sleep(1000); | ||||
|                     } catch (InterruptedException e) { | ||||
|                         LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 LogUtils.d(TAG, "run() exit"); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,54 +0,0 @@ | ||||
| package cc.winboll.studio.appbase.threads; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Date 2025/02/14 03:46:44 | ||||
|  */ | ||||
| import android.content.Context; | ||||
| import cc.winboll.studio.appbase.handlers.MainServiceHandler; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| import java.lang.ref.WeakReference; | ||||
|  | ||||
| public class MainServiceThread extends Thread { | ||||
|  | ||||
|     public static final String TAG = "MainServiceThread"; | ||||
|  | ||||
|     Context mContext; | ||||
|  | ||||
|     // 控制线程是否退出的标志 | ||||
|     volatile boolean isExist = false; | ||||
|  | ||||
|     // 服务Handler, 用于线程发送消息使用 | ||||
|     WeakReference<MainServiceHandler> mwrMainServiceHandler; | ||||
|  | ||||
|     public void setIsExist(boolean isExist) { | ||||
|         this.isExist = isExist; | ||||
|     } | ||||
|  | ||||
|     public boolean isExist() { | ||||
|         return isExist; | ||||
|     } | ||||
|  | ||||
|     public MainServiceThread(Context context, MainServiceHandler handler) { | ||||
|         mContext = context; | ||||
|         mwrMainServiceHandler = new WeakReference<MainServiceHandler>(handler); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void run() { | ||||
|         LogUtils.d(TAG, "run()"); | ||||
|  | ||||
|         while (!isExist()) { | ||||
|             //ToastUtils.show("run()"); | ||||
|             //LogUtils.d(TAG, "run()"); | ||||
|  | ||||
|             try { | ||||
|                 Thread.sleep(1000); | ||||
|             } catch (InterruptedException e) { | ||||
|                 LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|             } | ||||
|         } | ||||
|         LogUtils.d(TAG, "run() exit."); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,187 +0,0 @@ | ||||
| package cc.winboll.studio.appbase.widgets; | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Date 2025/02/15 14:41:25 | ||||
|  * @Describe TimeWidget | ||||
|  */ | ||||
| import android.app.PendingIntent; | ||||
| import android.appwidget.AppWidgetManager; | ||||
| import android.appwidget.AppWidgetProvider; | ||||
| import android.content.ComponentName; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.widget.RemoteViews; | ||||
| import cc.winboll.studio.appbase.R; | ||||
| import cc.winboll.studio.appbase.receivers.APPNewsWidgetClickListener; | ||||
| import cc.winboll.studio.libappbase.AppUtils; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| import cc.winboll.studio.libappbase.models.APPModel; | ||||
| import cc.winboll.studio.libappbase.models.WinBoLLNewsBean; | ||||
| import cc.winboll.studio.libappbase.winboll.WinBoLL; | ||||
| import java.io.IOException; | ||||
| import java.text.SimpleDateFormat; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Date; | ||||
| import cc.winboll.studio.libappbase.models.WinBoLLModel; | ||||
|  | ||||
| public class APPNewsWidget extends AppWidgetProvider { | ||||
|  | ||||
|     public static final String TAG = "APPNewsWidget"; | ||||
|      | ||||
|     public static final String ACTION_WAKEUP_SERVICE = APPNewsWidget.class.getName() + ".ACTION_WAKEUP_SERVICE"; | ||||
|     public static final String ACTION_RELOAD_REPORT = APPNewsWidget.class.getName() + ".ACTION_RELOAD_REPORT"; | ||||
|  | ||||
|  | ||||
|     volatile static ArrayList<WinBoLLNewsBean> _WinBoLLNewsBeanList; | ||||
|     final static int _MAX_PAGES = 10; | ||||
|     final static int _OnePageLinesCount = 5; | ||||
|     volatile static int _CurrentPageIndex = 0; | ||||
|  | ||||
|     @Override | ||||
|     public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { | ||||
|         initWinBoLLNewsBeanList(context); | ||||
|         for (int appWidgetId : appWidgetIds) { | ||||
|             updateAppWidget(context, appWidgetManager, appWidgetId); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onReceive(Context context, Intent intent) { | ||||
|         super.onReceive(context, intent); | ||||
|         initWinBoLLNewsBeanList(context); | ||||
|         if (intent.getAction().equals(ACTION_RELOAD_REPORT)) { | ||||
|             LogUtils.d(TAG, "ACTION_RELOAD_REPORT"); | ||||
|             AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); | ||||
|             int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, APPNewsWidget.class)); | ||||
|             for (int appWidgetId : appWidgetIds) { | ||||
|                 updateAppWidget(context, appWidgetManager, appWidgetId); | ||||
|             } | ||||
|         }else if (intent.getAction().equals(ACTION_WAKEUP_SERVICE)) { | ||||
|             LogUtils.d(TAG, "ACTION_WAKEUP_SERVICE"); | ||||
|             String szWinBoLLModel = intent.getStringExtra(WinBoLL.EXTRA_WINBOLLMODEL); | ||||
|             LogUtils.d(TAG, String.format("szWinBoLLModel %s", szWinBoLLModel)); | ||||
|             if (szWinBoLLModel != null && !szWinBoLLModel.equals("")) { | ||||
|                 try { | ||||
|                     WinBoLLModel bean = WinBoLLModel.parseStringToBean(szWinBoLLModel, WinBoLLModel.class); | ||||
|                     if (bean != null) { | ||||
|                         String szAppPackageName = bean.getAppPackageName(); | ||||
|                         LogUtils.d(TAG, String.format("szAppPackageName %s", szAppPackageName)); | ||||
|                         String szAppMainServiveName = bean.getAppMainServiveName(); | ||||
|                         LogUtils.d(TAG, String.format("szAppMainServiveName %s", szAppMainServiveName)); | ||||
|  | ||||
|                          | ||||
|                         String appName = AppUtils.getAppNameByPackageName(context, szAppPackageName); | ||||
|                         LogUtils.d(TAG, String.format("appName %s", appName)); | ||||
|                         WinBoLLNewsBean winBollNewsBean = new WinBoLLNewsBean(appName); | ||||
|                         SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); | ||||
|                         String currentTime = sdf.format(new Date()); | ||||
|                         StringBuilder sbLine = new StringBuilder(); | ||||
|                         sbLine.append("["); | ||||
|                         sbLine.append(currentTime); | ||||
|                         sbLine.append("] Wake up "); | ||||
|                         sbLine.append(appName); | ||||
|                         winBollNewsBean.setMessage(sbLine.toString()); | ||||
|                          | ||||
|                         addWinBoLLNewsBean(context, winBollNewsBean); | ||||
|  | ||||
|                         AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); | ||||
|                         int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, APPNewsWidget.class)); | ||||
|                         for (int appWidgetId : appWidgetIds) { | ||||
|                             updateAppWidget(context, appWidgetManager, appWidgetId); | ||||
|                         } | ||||
|                     } | ||||
|                 } catch (IOException e) { | ||||
|                     LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     // 加入新报告信息 | ||||
|     // | ||||
|     public synchronized static void addWinBoLLNewsBean(Context context, WinBoLLNewsBean bean) { | ||||
|         initWinBoLLNewsBeanList(context); | ||||
|         _WinBoLLNewsBeanList.add(0, bean); | ||||
|         // 控制记录总数 | ||||
|         while (_WinBoLLNewsBeanList.size() > _MAX_PAGES * _OnePageLinesCount) { | ||||
|             _WinBoLLNewsBeanList.remove(_WinBoLLNewsBeanList.size() - 1); | ||||
|         } | ||||
|         WinBoLLNewsBean.saveBeanList(context, _WinBoLLNewsBeanList, WinBoLLNewsBean.class); | ||||
|     } | ||||
|  | ||||
|     synchronized static void initWinBoLLNewsBeanList(Context context) { | ||||
|         if (_WinBoLLNewsBeanList == null) { | ||||
|             _WinBoLLNewsBeanList = new ArrayList<WinBoLLNewsBean>(); | ||||
|             WinBoLLNewsBean.loadBeanList(context, _WinBoLLNewsBeanList, WinBoLLNewsBean.class); | ||||
|         } | ||||
|         if (_WinBoLLNewsBeanList == null) { | ||||
|             _WinBoLLNewsBeanList = new ArrayList<WinBoLLNewsBean>(); | ||||
|             WinBoLLNewsBean.saveBeanList(context, _WinBoLLNewsBeanList, WinBoLLNewsBean.class); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) { | ||||
|         LogUtils.d(TAG, "updateAppWidget(...)"); | ||||
|  | ||||
|         RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_news); | ||||
|         //设置按钮点击事件 | ||||
|         Intent intentPre = new Intent(context, APPNewsWidgetClickListener.class); | ||||
|         intentPre.setAction(APPNewsWidgetClickListener.ACTION_PRE); | ||||
|         PendingIntent pendingIntentPre = PendingIntent.getBroadcast(context, 0, intentPre, PendingIntent.FLAG_UPDATE_CURRENT); | ||||
|         views.setOnClickPendingIntent(R.id.widget_button_pre, pendingIntentPre); | ||||
|         Intent intentNext = new Intent(context, APPNewsWidgetClickListener.class); | ||||
|         intentNext.setAction(APPNewsWidgetClickListener.ACTION_NEXT); | ||||
|         PendingIntent pendingIntentNext = PendingIntent.getBroadcast(context, 0, intentNext, PendingIntent.FLAG_UPDATE_CURRENT); | ||||
|         views.setOnClickPendingIntent(R.id.widget_button_next, pendingIntentNext); | ||||
|  | ||||
|         views.setTextViewText(R.id.tv_msg, getPageInfo()); | ||||
|         views.setTextViewText(R.id.tv_news, getMessage()); | ||||
|         appWidgetManager.updateAppWidget(appWidgetId, views); | ||||
|     } | ||||
|  | ||||
|     public static String getMessage() { | ||||
|         ArrayList<String> msgTemp = new ArrayList<String>(); | ||||
|         if (_WinBoLLNewsBeanList != null) { | ||||
|             int start = _OnePageLinesCount * _CurrentPageIndex; | ||||
|             start = _WinBoLLNewsBeanList.size() > start ? start : _WinBoLLNewsBeanList.size() - 1; | ||||
|             for (int i = start, j = 0; i < _WinBoLLNewsBeanList.size() && j < _OnePageLinesCount && start > -1; i++, j++) { | ||||
|                 msgTemp.add(_WinBoLLNewsBeanList.get(i).getMessage()); | ||||
|             } | ||||
|             String message = String.join("\n", msgTemp); | ||||
|             return message; | ||||
|         } | ||||
|         return ""; | ||||
|     } | ||||
|  | ||||
|     public static void prePage(Context context) { | ||||
|         if (_WinBoLLNewsBeanList != null) { | ||||
|             if (_CurrentPageIndex > 0) { | ||||
|                 _CurrentPageIndex = _CurrentPageIndex - 1; | ||||
|             } | ||||
|             Intent intentWidget = new Intent(context, APPNewsWidget.class); | ||||
|             intentWidget.setAction(APPNewsWidget.ACTION_RELOAD_REPORT); | ||||
|             context.sendBroadcast(intentWidget); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static void nextPage(Context context) { | ||||
|         if (_WinBoLLNewsBeanList != null) { | ||||
|             if ((_CurrentPageIndex + 1) * _OnePageLinesCount < _WinBoLLNewsBeanList.size()) { | ||||
|                 _CurrentPageIndex = _CurrentPageIndex + 1; | ||||
|             } | ||||
|             Intent intentWidget = new Intent(context, APPNewsWidget.class); | ||||
|             intentWidget.setAction(APPNewsWidget.ACTION_RELOAD_REPORT); | ||||
|             context.sendBroadcast(intentWidget); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     String getPageInfo() { | ||||
|         if (_WinBoLLNewsBeanList == null) { | ||||
|             return "0/0"; | ||||
|         } | ||||
|         int leftCount = _WinBoLLNewsBeanList.size() % _OnePageLinesCount; | ||||
|         int currentPageCount = _WinBoLLNewsBeanList.size() / _OnePageLinesCount + (leftCount == 0 ?0: 1); | ||||
|         return String.format("%d/%d", _CurrentPageIndex + 1, currentPageCount); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										7
									
								
								appbase/src/main/res/drawable/btn_light_blue.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								appbase/src/main/res/drawable/btn_light_blue.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <shape xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:shape="rectangle"> | ||||
|     <solid android:color="#81C7F5"/> <!-- 浅蓝色填充 --> | ||||
|     <corners android:radius="8dp"/> <!-- 8dp 圆角 --> | ||||
| </shape> | ||||
|  | ||||
| @@ -1,11 +0,0 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:width="24dp" | ||||
|     android:height="24dp" | ||||
|     android:viewportHeight="24" | ||||
|     android:viewportWidth="24"> | ||||
|     <path | ||||
|         android:fillColor="#ff000000" | ||||
|         android:pathData="M6.5,20Q4.22,20 2.61,18.43 1,16.85 1,14.58 1,12.63 2.17,11.1 3.35,9.57 5.25,9.15 5.88,6.85 7.75,5.43 9.63,4 12,4 14.93,4 16.96,6.04 19,8.07 19,11 20.73,11.2 21.86,12.5 23,13.78 23,15.5 23,17.38 21.69,18.69 20.38,20 18.5,20Z"/> | ||||
|  | ||||
| </vector> | ||||
| @@ -1,11 +0,0 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:width="24dp" | ||||
|     android:height="24dp" | ||||
|     android:viewportHeight="24" | ||||
|     android:viewportWidth="24"> | ||||
|     <path | ||||
|         android:fillColor="#ff000000" | ||||
|         android:pathData="M6.5,20Q4.22,20 2.61,18.43 1,16.85 1,14.58 1,12.63 2.17,11.1 3.35,9.57 5.25,9.15 5.88,6.85 7.75,5.43 9.63,4 12,4 14.93,4 16.96,6.04 19,8.07 19,11 20.73,11.2 21.86,12.5 23,13.78 23,15.5 23,17.38 21.69,18.69 20.38,20 18.5,20M6.5,18H18.5Q19.55,18 20.27,17.27 21,16.55 21,15.5 21,14.45 20.27,13.73 19.55,13 18.5,13H17V11Q17,8.93 15.54,7.46 14.08,6 12,6 9.93,6 8.46,7.46 7,8.93 7,11H6.5Q5.05,11 4.03,12.03 3,13.05 3,14.5 3,15.95 4.03,17 5.05,18 6.5,18M12,12Z"/> | ||||
|  | ||||
| </vector> | ||||
| @@ -3,219 +3,45 @@ | ||||
| 	xmlns:android="http://schemas.android.com/apk/res/android" | ||||
| 	android:orientation="vertical" | ||||
| 	android:layout_width="match_parent" | ||||
| 	android:layout_height="match_parent"> | ||||
| 	android:layout_height="match_parent" | ||||
| 	android:padding="16dp"> | ||||
|  | ||||
| 	<android.widget.Toolbar | ||||
| 		android:layout_width="match_parent" | ||||
| 		android:layout_height="wrap_content" | ||||
| 		android:id="@+id/toolbar"/> | ||||
|  | ||||
| 	<ScrollView | ||||
| 	<LinearLayout | ||||
| 		android:layout_width="match_parent" | ||||
| 		android:layout_height="0dp" | ||||
| 		android:layout_weight="1.0"> | ||||
| 		android:layout_weight="1" | ||||
| 		android:orientation="vertical" | ||||
| 		android:gravity="center_vertical" | ||||
| 		android:spacing="12dp"> | ||||
|  | ||||
| 		<LinearLayout | ||||
| 			xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
| 		<Button | ||||
| 			android:layout_width="match_parent" | ||||
| 			android:layout_height="wrap_content" | ||||
| 			android:orientation="vertical" | ||||
| 			android:gravity="center"> | ||||
| 			android:text="应用崩溃测试" | ||||
| 			android:textSize="16sp" | ||||
| 			android:textColor="@android:color/white" | ||||
| 			android:background="#81C7F5" | ||||
| 			android:paddingVertical="12dp" | ||||
| 			android:layout_marginHorizontal="24dp" | ||||
| 			android:onClick="onCrashTest"/> | ||||
|  | ||||
| 			<LinearLayout | ||||
| 				android:orientation="vertical" | ||||
| 				android:layout_width="match_parent" | ||||
| 				android:layout_height="0dp" | ||||
| 				android:layout_weight="1.0" | ||||
| 				android:gravity="center_horizontal"> | ||||
| 		<Button | ||||
| 			android:layout_width="match_parent" | ||||
| 			android:layout_height="wrap_content" | ||||
| 			android:text="应用日志测试" | ||||
| 			android:textSize="16sp" | ||||
| 			android:textColor="@android:color/white" | ||||
| 			android:background="#81C7F5" | ||||
| 			android:paddingVertical="12dp" | ||||
| 			android:layout_marginHorizontal="24dp" | ||||
| 			android:onClick="onLogTest"/> | ||||
|  | ||||
| 				<TextView | ||||
| 					android:layout_width="wrap_content" | ||||
| 					android:layout_height="wrap_content" | ||||
| 					android:text="安卓R对应的是Android 11,其API级别是30。以下是Android 11中一些重要的API相关特性: | ||||
| 	</LinearLayout> | ||||
|  | ||||
|                     \n- 隐私保护方面:引入单次授权,让用户可以选择授予应用对位置信息、麦克风和摄像头的临时访问权限。还增加了数据访问审核功能,能让开发者深入了解应用在何处访问私密数据。 | ||||
|                      | ||||
|                     \n- 系统功能方面:提供了ControlsProviderService API,用于向连接的外部设备提供控件,这些控件显示于Android电源菜单中的设备控件下。媒体控件也得到更新,显示于快捷设置旁,来自多个应用的会话排列在一个可滑动的轮播界面中。 | ||||
|                      | ||||
|                     \n- 硬件支持方面:提供了一些API以支持瀑布屏,通过将窗口布局属性  layoutInDisplayCutoutMode  设为  LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS ,可允许窗口延伸到屏幕各个边缘上的刘海和瀑布区域。对于合页式屏幕配置的设备,提供了具有  TYPE_HINGE_ANGLE  的新传感器以及新的  SensorEvent ,用于监控合页角度。 | ||||
|                      | ||||
|                     \n- 安全方面:对生物识别身份验证机制进行了更新,引入了  BiometricManager.Authenticators  接口,定义了  BIOMETRIC_STRONG 、 BIOMETRIC_WEAK 、 DEVICE_CREDENTIAL  等身份验证类型。还在  BiometricPrompt  类中提供了对“每次使用时进行身份验证”密钥的更多支持。 | ||||
|                      | ||||
|                     \n- 性能和质量方面:支持无线调试,通过Android调试桥(adb)从工作站以无线方式部署和调试应用,避免了常见的USB连接问题。"/> | ||||
|                  | ||||
|                      | ||||
| 				<HorizontalScrollView | ||||
| 					android:layout_width="match_parent" | ||||
| 					android:layout_height="wrap_content"> | ||||
|  | ||||
| 					<LinearLayout | ||||
| 						android:orientation="horizontal" | ||||
| 						android:layout_height="wrap_content" | ||||
| 						android:gravity="right|center_vertical" | ||||
| 						android:layout_width="wrap_content"> | ||||
|  | ||||
| 						<CheckBox | ||||
| 							android:layout_width="wrap_content" | ||||
| 							android:layout_height="wrap_content" | ||||
| 							android:text="Debug Mode" | ||||
| 							android:layout_weight="1.0" | ||||
| 							android:onClick="onSwitchDebugMode" | ||||
| 							android:id="@+id/activitymainCheckBox1"/> | ||||
|  | ||||
| 						<Button | ||||
| 							android:layout_width="wrap_content" | ||||
| 							android:layout_height="wrap_content" | ||||
| 							android:text="Test Application CrashReport" | ||||
| 							android:textAllCaps="false" | ||||
| 							android:onClick="onTestApplicationCrashReport"/> | ||||
|  | ||||
| 						<Button | ||||
| 							android:layout_width="wrap_content" | ||||
| 							android:layout_height="wrap_content" | ||||
| 							android:text="PreviewGlobalCrashActivity" | ||||
| 							android:textAllCaps="false" | ||||
| 							android:onClick="onPreviewGlobalCrashActivity"/> | ||||
|  | ||||
| 					</LinearLayout> | ||||
|  | ||||
| 				</HorizontalScrollView> | ||||
|  | ||||
| 				<ScrollView | ||||
| 					android:layout_width="match_parent" | ||||
| 					android:layout_height="400dp"> | ||||
|  | ||||
| 					<LinearLayout | ||||
| 						android:orientation="vertical" | ||||
| 						android:layout_width="match_parent" | ||||
| 						android:layout_height="wrap_content" | ||||
| 						android:gravity="right"> | ||||
|  | ||||
| 						<LinearLayout | ||||
| 							android:orientation="horizontal" | ||||
| 							android:layout_width="wrap_content" | ||||
| 							android:layout_height="wrap_content"> | ||||
|  | ||||
| 							<Button | ||||
| 								android:layout_width="wrap_content" | ||||
| 								android:layout_height="wrap_content" | ||||
| 								android:text="StartCenter" | ||||
| 								android:textAllCaps="false" | ||||
| 								android:onClick="onStartCenter"/> | ||||
|  | ||||
| 							<Button | ||||
| 								android:layout_width="wrap_content" | ||||
| 								android:layout_height="wrap_content" | ||||
| 								android:text="StopCenter" | ||||
| 								android:textAllCaps="false" | ||||
| 								android:onClick="onStopCenter"/> | ||||
|  | ||||
| 						</LinearLayout> | ||||
|  | ||||
| 						<HorizontalScrollView | ||||
| 							android:layout_width="match_parent" | ||||
| 							android:layout_height="wrap_content"> | ||||
|  | ||||
| 							<LinearLayout | ||||
| 								android:orientation="horizontal" | ||||
| 								android:layout_width="wrap_content" | ||||
| 								android:layout_height="wrap_content"> | ||||
|  | ||||
| 								<Button | ||||
| 									android:layout_width="wrap_content" | ||||
| 									android:layout_height="wrap_content" | ||||
| 									android:text="SartTestDemoService" | ||||
| 									android:textAllCaps="false" | ||||
| 									android:onClick="onSartTestDemoService"/> | ||||
|  | ||||
| 								<Button | ||||
| 									android:layout_width="wrap_content" | ||||
| 									android:layout_height="wrap_content" | ||||
| 									android:text="StopTestDemoService" | ||||
| 									android:textAllCaps="false" | ||||
| 									android:onClick="onStopTestDemoService"/> | ||||
|  | ||||
| 								<Button | ||||
| 									android:layout_width="wrap_content" | ||||
| 									android:layout_height="wrap_content" | ||||
| 									android:text="StopTestDemoServiceNoSettings" | ||||
| 									android:textAllCaps="false" | ||||
| 									android:onClick="onStopTestDemoServiceNoSettings"/> | ||||
|  | ||||
| 							</LinearLayout> | ||||
|  | ||||
| 						</HorizontalScrollView> | ||||
|  | ||||
| 						<HorizontalScrollView | ||||
| 							android:layout_width="match_parent" | ||||
| 							android:layout_height="wrap_content"> | ||||
|  | ||||
| 							<LinearLayout | ||||
| 								android:orientation="horizontal" | ||||
| 								android:layout_width="wrap_content" | ||||
| 								android:layout_height="wrap_content"> | ||||
|  | ||||
| 								<Button | ||||
| 									android:layout_width="wrap_content" | ||||
| 									android:layout_height="wrap_content" | ||||
| 									android:text="SartTestDemoBindService" | ||||
| 									android:textAllCaps="false" | ||||
| 									android:onClick="onSartTestDemoBindService"/> | ||||
|  | ||||
| 								<Button | ||||
| 									android:layout_width="wrap_content" | ||||
| 									android:layout_height="wrap_content" | ||||
| 									android:text="StopTestDemoBindService" | ||||
| 									android:textAllCaps="false" | ||||
| 									android:onClick="onStopTestDemoBindService"/> | ||||
|  | ||||
| 								<Button | ||||
| 									android:layout_width="wrap_content" | ||||
| 									android:layout_height="wrap_content" | ||||
| 									android:text="StopTestDemoBindServiceNoSettings" | ||||
| 									android:textAllCaps="false" | ||||
| 									android:onClick="onStopTestDemoBindServiceNoSettings"/> | ||||
|  | ||||
| 							</LinearLayout> | ||||
|  | ||||
| 						</HorizontalScrollView> | ||||
|  | ||||
| 						<Button | ||||
| 							android:layout_width="wrap_content" | ||||
| 							android:layout_height="wrap_content" | ||||
| 							android:text="TestStopMainServiceWithoutSettingEnable" | ||||
| 							android:textAllCaps="false" | ||||
| 							android:onClick="onTestStopMainServiceWithoutSettingEnable"/> | ||||
|  | ||||
| 						<Button | ||||
| 							android:layout_width="wrap_content" | ||||
| 							android:layout_height="wrap_content" | ||||
| 							android:text="TestUseComponentStartService" | ||||
| 							android:textAllCaps="false" | ||||
| 							android:onClick="onTestUseComponentStartService"/> | ||||
|  | ||||
| 						<Button | ||||
| 							android:layout_width="wrap_content" | ||||
| 							android:layout_height="wrap_content" | ||||
| 							android:text="TestDemoServiceSOS" | ||||
| 							android:textAllCaps="false" | ||||
| 							android:onClick="onTestDemoServiceSOS"/> | ||||
|  | ||||
| 						<Button | ||||
| 							android:layout_width="wrap_content" | ||||
| 							android:layout_height="wrap_content" | ||||
| 							android:text="TestOpenNewActivity" | ||||
| 							android:textAllCaps="false" | ||||
| 							android:onClick="onTestOpenNewActivity"/> | ||||
|  | ||||
| 					</LinearLayout> | ||||
|  | ||||
| 				</ScrollView> | ||||
|  | ||||
| 			</LinearLayout> | ||||
|  | ||||
| 		</LinearLayout> | ||||
|  | ||||
| 	</ScrollView> | ||||
| </LinearLayout> | ||||
|  | ||||
|   | ||||
| @@ -1,43 +0,0 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <LinearLayout | ||||
| 	xmlns:android="http://schemas.android.com/apk/res/android" | ||||
| 	xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
| 	android:orientation="vertical" | ||||
| 	android:layout_width="match_parent" | ||||
| 	android:layout_height="match_parent"> | ||||
|  | ||||
|     <android.widget.Toolbar | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
| 		android:id="@+id/toolbar"/> | ||||
|      | ||||
| 	<TextView | ||||
| 		android:layout_width="wrap_content" | ||||
| 		android:layout_height="wrap_content" | ||||
| 		android:text="NewActivity"/> | ||||
|  | ||||
|     <Button | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:text="CloseThisActivity" | ||||
|         android:textAllCaps="false" | ||||
|         android:onClick="onCloseThisActivity"/> | ||||
|  | ||||
|     <Button | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:text="CloseAllActivity" | ||||
|         android:textAllCaps="false" | ||||
|         android:onClick="onCloseAllActivity"/> | ||||
|  | ||||
|     <Button | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:text="New2Activity" | ||||
|         android:textAllCaps="false" | ||||
|         android:onClick="onNew2Activity"/> | ||||
|  | ||||
|      | ||||
|      | ||||
| </LinearLayout> | ||||
|  | ||||
| @@ -1,43 +0,0 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <LinearLayout | ||||
| 	xmlns:android="http://schemas.android.com/apk/res/android" | ||||
| 	xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
| 	android:orientation="vertical" | ||||
| 	android:layout_width="match_parent" | ||||
| 	android:layout_height="match_parent"> | ||||
|  | ||||
|     <android.widget.Toolbar | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
| 		android:id="@+id/toolbar"/> | ||||
|  | ||||
| 	<TextView | ||||
| 		android:layout_width="wrap_content" | ||||
| 		android:layout_height="wrap_content" | ||||
| 		android:text="New2Activity"/> | ||||
|  | ||||
| 	<Button | ||||
| 		android:layout_width="wrap_content" | ||||
| 		android:layout_height="wrap_content" | ||||
| 		android:text="CloseThisActivity" | ||||
| 		android:textAllCaps="false" | ||||
| 		android:onClick="onCloseThisActivity"/> | ||||
|  | ||||
| 	<Button | ||||
| 		android:layout_width="wrap_content" | ||||
| 		android:layout_height="wrap_content" | ||||
| 		android:text="CloseAllActivity" | ||||
| 		android:textAllCaps="false" | ||||
| 		android:onClick="onCloseAllActivity"/> | ||||
|  | ||||
| 	<Button | ||||
| 		android:layout_width="wrap_content" | ||||
| 		android:layout_height="wrap_content" | ||||
| 		android:text="NewActivity" | ||||
| 		android:textAllCaps="false" | ||||
| 		android:onClick="onNewActivity"/> | ||||
|  | ||||
| 	 | ||||
|  | ||||
| </LinearLayout> | ||||
|  | ||||
| @@ -1,51 +0,0 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <LinearLayout | ||||
| 	xmlns:android="http://schemas.android.com/apk/res/android" | ||||
| 	xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
| 	android:layout_width="match_parent" | ||||
| 	android:layout_height="match_parent" | ||||
| 	android:orientation="vertical" | ||||
| 	android:background="#FFFFFFFF"> | ||||
|  | ||||
| 	<LinearLayout | ||||
| 		android:orientation="horizontal" | ||||
| 		android:layout_width="match_parent" | ||||
| 		android:layout_height="wrap_content" | ||||
| 		android:gravity="right|center_vertical"> | ||||
|  | ||||
| 		<TextView | ||||
| 			android:layout_width="0dp" | ||||
| 			android:layout_height="wrap_content" | ||||
| 			android:id="@+id/tv_title" | ||||
| 			android:layout_weight="1.0" | ||||
| 			android:text="WinBoLLNews" | ||||
| 			android:textStyle="bold" | ||||
| 			android:textSize="16sp"/> | ||||
|  | ||||
| 		<Button | ||||
| 			android:layout_width="48dp" | ||||
| 			android:layout_height="48dp" | ||||
| 			android:text="⇦" | ||||
| 			android:id="@+id/widget_button_pre"/> | ||||
|  | ||||
| 		<Button | ||||
| 			android:layout_width="48dp" | ||||
| 			android:layout_height="48dp" | ||||
| 			android:text="⇨" | ||||
| 			android:id="@+id/widget_button_next"/> | ||||
|  | ||||
| 	</LinearLayout> | ||||
|  | ||||
|     <TextView | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:id="@+id/tv_msg"/> | ||||
|      | ||||
| 	<TextView | ||||
| 		android:layout_width="match_parent" | ||||
| 		android:layout_height="0dp" | ||||
| 		android:id="@+id/tv_news" | ||||
| 		android:layout_weight="1.0"/> | ||||
|  | ||||
| </LinearLayout> | ||||
|  | ||||
| @@ -3,10 +3,6 @@ | ||||
| 	xmlns:app="http://schemas.android.com/apk/res-auto"> | ||||
|     <item | ||||
|         android:id="@+id/item_home" | ||||
|         android:title="HOME" | ||||
|         android:title="WinBoLL Home" | ||||
|         android:icon="@drawable/ic_winboll"/> | ||||
|     <item | ||||
|         android:id="@+id/item_log" | ||||
|         android:title="LOG" | ||||
|         android:icon="@drawable/ic_winboll_log"/> | ||||
| </menu> | ||||
|   | ||||
| @@ -1,7 +0,0 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:minWidth="200dp" | ||||
|     android:minHeight="100dp" | ||||
|     android:updatePeriodMillis="1000" | ||||
|     android:initialLayout="@layout/widget_news"> | ||||
| </appwidget-provider> | ||||
| @@ -1,8 +1,8 @@ | ||||
| #Created by .winboll/winboll_app_build.gradle | ||||
| #Sun Jun 01 16:01:35 HKT 2025 | ||||
| stageCount=3 | ||||
| #Mon Sep 01 07:56:33 HKT 2025 | ||||
| stageCount=7 | ||||
| libraryProject=libapputils | ||||
| baseVersion=15.8 | ||||
| publishVersion=15.8.2 | ||||
| publishVersion=15.8.6 | ||||
| buildCount=0 | ||||
| baseBetaVersion=15.8.3 | ||||
| baseBetaVersion=15.8.7 | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.apputils; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2024/12/08 15:10:51 | ||||
|  * @Describe 全局应用类 | ||||
|  */ | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.apputils; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/01/03 11:02:49 | ||||
|  * @Describe 一个可以浏览随 APP 附带的 Html 文档的窗口 | ||||
|  */ | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.apputils; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/01/18 10:32:21 | ||||
|  * @Describe 二维码扫码解码窗口 | ||||
|  */ | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.apputils; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/03/23 16:14:45 | ||||
|  */ | ||||
| import android.app.Activity; | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.apputils; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/05/13 11:15 | ||||
|  * @Describe WinBoLLActivity | ||||
|  */ | ||||
|   | ||||
							
								
								
									
										35
									
								
								autoinstaller/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								autoinstaller/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| # AutoInstaller | ||||
|  | ||||
| #### 介绍 | ||||
| APK 文件监控,监控指定文件路径的 APK 文件。 | ||||
| 在 APK 文件改变后自动调用系统安装命令安装,或者调用[应用信息查看器]打开。 | ||||
|  | ||||
| #### 软件架构 | ||||
| 适配安卓应用 [AIDE Pro] 的 Gradle 编译结构。 | ||||
| 也适配安卓应用 [AndroidIDE] 的 Gradle 编译结构。 | ||||
|  | ||||
|  | ||||
| #### Gradle 编译说明 | ||||
| 调试版编译命令 :gradle assembleBetaDebug | ||||
| 阶段版编译命令 :bash .winboll/bashPublishAPKAddTag.sh autoinstaller | ||||
|  | ||||
| #### 使用说明 | ||||
|  | ||||
| #### 参与贡献 | ||||
|  | ||||
| 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,8 +1,8 @@ | ||||
| #Created by .winboll/winboll_app_build.gradle | ||||
| #Sun May 04 05:32:00 GMT 2025 | ||||
| stageCount=1 | ||||
| #Tue Jun 24 09:54:47 HKT 2025 | ||||
| stageCount=3 | ||||
| libraryProject= | ||||
| baseVersion=15.2 | ||||
| publishVersion=15.2.0 | ||||
| buildCount=74 | ||||
| baseBetaVersion=15.2.1 | ||||
| publishVersion=15.2.2 | ||||
| buildCount=0 | ||||
| baseBetaVersion=15.2.3 | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.autoinstaller; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2024/04/28 02:39:58 | ||||
|  * @Describe 全局应用类 | ||||
|  */ | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.autoinstaller; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/04/15 09:24:46 | ||||
|  * @Describe 磁贴工具服务类 | ||||
|  */ | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.autoinstaller.models; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/04/02 20:50:29 | ||||
|  * @Describe 监控的 APK 安装文件对应的应用信息数据模型 | ||||
|  */ | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.autoinstaller.models; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2024/05/27 17:36:01 | ||||
|  * @Describe 应用配置数据类 | ||||
|  */ | ||||
|   | ||||
| @@ -5,7 +5,7 @@ import cc.winboll.studio.libappbase.BaseBean; | ||||
| import java.io.IOException; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/04/15 09:27:39 | ||||
|  * @Describe MainServiceBean | ||||
|  */ | ||||
|   | ||||
| @@ -9,7 +9,7 @@ import cc.winboll.studio.autoinstaller.services.MainService; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2024/06/06 13:19:38 | ||||
|  * @Describe 应用消息接收类 | ||||
|  */ | ||||
|   | ||||
| @@ -10,7 +10,7 @@ import cc.winboll.studio.autoinstaller.models.AppConfigs; | ||||
| import cc.winboll.studio.autoinstaller.utils.ServiceUtil; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2024/05/27 20:16:00 | ||||
|  * @Describe MainService 守护进程服务 | ||||
|  */ | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.autoinstaller.utils; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2024/05/27 17:56:31 | ||||
|  * @Describe 文件管理类 | ||||
|  */ | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.autoinstaller.utils; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2024/12/11 06:28:50 | ||||
|  * @Describe 一个获取安卓APK安装文件的应用包名的函数 | ||||
|  */ | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.autoinstaller.utils; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2024/05/27 20:20:03 | ||||
|  * @Describe 服务类工具 | ||||
|  */ | ||||
|   | ||||
| @@ -3,7 +3,7 @@ | ||||
| https://github.com/aJIEw/PhoneCallApp.git | ||||
|  | ||||
| #### 介绍 | ||||
| 通讯录与拨号 | ||||
| 这是可以根据正则表达式匹配拦截骚扰电话的手机拨号应用。 | ||||
|  | ||||
| #### 软件架构 | ||||
| 适配安卓应用 [AIDE Pro] 的 Gradle 编译结构。 | ||||
|   | ||||
| @@ -45,9 +45,9 @@ android { | ||||
|  | ||||
| dependencies { | ||||
|     api fileTree(dir: 'libs', include: ['*.jar']) | ||||
|     api 'cc.winboll.studio:libaes:15.8.0' | ||||
|     api 'cc.winboll.studio:libapputils:15.8.1' | ||||
|     api 'cc.winboll.studio:libappbase:15.8.1' | ||||
|     api 'cc.winboll.studio:libaes:15.9.3' | ||||
|     api 'cc.winboll.studio:libapputils:15.8.5' | ||||
|     api 'cc.winboll.studio:libappbase:15.9.5' | ||||
|      | ||||
|     // 权限请求框架:https://github.com/getActivity/XXPermissions | ||||
|     api 'com.github.getActivity:XXPermissions:18.63' | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| #Created by .winboll/winboll_app_build.gradle | ||||
| #Tue May 20 13:02:18 HKT 2025 | ||||
| stageCount=3 | ||||
| #Sat Sep 27 15:23:51 HKT 2025 | ||||
| stageCount=18 | ||||
| libraryProject= | ||||
| baseVersion=15.3 | ||||
| publishVersion=15.3.2 | ||||
| publishVersion=15.3.17 | ||||
| buildCount=0 | ||||
| baseBetaVersion=15.3.3 | ||||
| baseBetaVersion=15.3.18 | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.contacts; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2024/12/08 15:10:51 | ||||
|  * @Describe 全局应用类 | ||||
|  */ | ||||
|   | ||||
| @@ -1,13 +1,20 @@ | ||||
| package cc.winboll.studio.contacts; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen&豆包大模型<zhangsken@qq.com> | ||||
|  * @Date 2025/08/30 14:32 | ||||
|  * @Describe 主窗口 | ||||
|  */ | ||||
| import android.Manifest; | ||||
| import android.app.Activity; | ||||
| import android.app.ActivityManager; | ||||
| import android.app.AlertDialog; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.content.pm.PackageManager; | ||||
| import android.os.Build; | ||||
| import android.os.Bundle; | ||||
| import android.os.Handler; | ||||
| import android.telecom.TelecomManager; | ||||
| import android.telephony.PhoneStateListener; | ||||
| import android.telephony.TelephonyManager; | ||||
| @@ -25,6 +32,8 @@ import androidx.fragment.app.Fragment; | ||||
| import androidx.fragment.app.FragmentManager; | ||||
| import androidx.fragment.app.FragmentPagerAdapter; | ||||
| import androidx.viewpager.widget.ViewPager; | ||||
| import // 新增:导入 AppGoToSettingsUtil 工具类(确保包路径与项目实际一致) | ||||
| cc.winboll.studio.contacts.utils.AppGoToSettingsUtil; | ||||
| import cc.winboll.studio.contacts.R; | ||||
| import cc.winboll.studio.contacts.activities.SettingsActivity; | ||||
| import cc.winboll.studio.contacts.beans.MainServiceBean; | ||||
| @@ -41,11 +50,10 @@ import java.util.List; | ||||
|  | ||||
| final public class MainActivity extends AppCompatActivity implements IWinBoLLActivity, ViewPager.OnPageChangeListener, View.OnClickListener { | ||||
|  | ||||
| 	public static final String TAG = "MainActivity"; | ||||
|  | ||||
|     public static final String TAG = "MainActivity"; | ||||
|     public static final int REQUEST_HOME_ACTIVITY = 0; | ||||
|     public static final int REQUEST_ABOUT_ACTIVITY = 1; | ||||
|  | ||||
|     public static final int REQUEST_APP_SETTINGS = 2; | ||||
|     public static final String ACTION_SOS = "cc.winboll.studio.libappbase.WinBoLL.ACTION_SOS"; | ||||
|  | ||||
|     static MainActivity _MainActivity; | ||||
| @@ -55,13 +63,10 @@ final public class MainActivity extends AppCompatActivity implements IWinBoLLAct | ||||
|     MainServiceBean mMainServiceBean; | ||||
|     private TabLayout tabLayout; | ||||
|     private ViewPager viewPager; | ||||
|     private List<View> views; //用来存放放进ViewPager里面的布局 | ||||
|     //实例化存储imageView(导航原点)的集合 | ||||
|     private List<View> views;  | ||||
|     ImageView[] imageViews; | ||||
|     //MyPagerAdapter adapter;//适配器 | ||||
|     MyPagerAdapter pagerAdapter; | ||||
|     LinearLayout linearLayout;//下标所在在LinearLayout布局里 | ||||
|     int currentPoint = 0;//当前被选中中页面的下标 | ||||
|     LinearLayout linearLayout; | ||||
|     int currentPoint = 0; | ||||
|  | ||||
|     private TelephonyManager telephonyManager; | ||||
|     private MyPhoneStateListener phoneStateListener; | ||||
| @@ -69,30 +74,13 @@ final public class MainActivity extends AppCompatActivity implements IWinBoLLAct | ||||
|     List<String> tabTitleList; | ||||
|  | ||||
|     private static final int DIALER_REQUEST_CODE = 1; | ||||
|  | ||||
| //    @Override | ||||
| //    public Activity getActivity() { | ||||
| //        return this; | ||||
| //    } | ||||
|  | ||||
| //    @Override | ||||
| //    public APPInfo getAppInfo() { | ||||
| //        String szBranchName = "contacts"; | ||||
| // | ||||
| //        APPInfo appInfo = AboutActivityFactory.buildDefaultAPPInfo(); | ||||
| //        appInfo.setAppName("Contacts"); | ||||
| //        appInfo.setAppIcon(cc.winboll.studio.libapputils.R.drawable.ic_winboll); | ||||
| //        appInfo.setAppDescription("Contacts Description"); | ||||
| //        appInfo.setAppGitName("APP"); | ||||
| //        appInfo.setAppGitOwner("Studio"); | ||||
| //        appInfo.setAppGitAPPBranch(szBranchName); | ||||
| //        appInfo.setAppGitAPPSubProjectFolder(szBranchName); | ||||
| //        appInfo.setAppHomePage("https://www.winboll.cc/studio/details.php?app=Contacts"); | ||||
| //        appInfo.setAppAPKName("Contacts"); | ||||
| //        appInfo.setAppAPKFolderName("Contacts"); | ||||
| //        return appInfo; | ||||
| //        return null; | ||||
| //    } | ||||
|     private static final int REQUEST_REQUIRED_PERMISSIONS = 1002; | ||||
|     // 关键修改1:新增 READ_CALL_LOG 权限到必需权限列表(解决通话记录读取崩溃) | ||||
|     private String[] REQUIRED_PERMISSIONS = new String[]{ | ||||
| 		Manifest.permission.READ_CONTACTS,  // 通讯录读取(原) | ||||
| 		Manifest.permission.CALL_PHONE,     // 电话拨号(原) | ||||
| 		Manifest.permission.READ_CALL_LOG   // 通话记录读取(新增,核心修复) | ||||
|     }; | ||||
|  | ||||
|  | ||||
|     @Override | ||||
| @@ -107,28 +95,98 @@ final public class MainActivity extends AppCompatActivity implements IWinBoLLAct | ||||
|  | ||||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|         // 接收并处理 Intent 数据,函数 Intent 处理接收就直接返回 | ||||
|         //if (prosessIntents(getIntent())) return; | ||||
|         // 以下正常创建主窗口 | ||||
|         super.onCreate(savedInstanceState); | ||||
|         _MainActivity = this; | ||||
|  | ||||
|         // 优先检查所有必需权限(含新增的 READ_CALL_LOG) | ||||
|         if (!checkAllRequiredPermissions()) { | ||||
|             requestAllRequiredPermissions(); | ||||
|         } else { | ||||
|             initUIAndLogic(savedInstanceState); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // 权限检查方法(无需修改,自动包含新增的 READ_CALL_LOG) | ||||
|     private boolean checkAllRequiredPermissions() { | ||||
|         for (String permission : REQUIRED_PERMISSIONS) { | ||||
|             if (ActivityCompat.checkSelfPermission(this, permission)  | ||||
| 				!= PackageManager.PERMISSION_GRANTED) { | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     // 权限申请方法(无需修改,自动申请新增的 READ_CALL_LOG) | ||||
|     private void requestAllRequiredPermissions() { | ||||
|         ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_REQUIRED_PERMISSIONS); | ||||
|     } | ||||
|  | ||||
|     // 权限结果回调(无需修改,确保所有权限(含 READ_CALL_LOG)都通过才加载UI) | ||||
|     @Override | ||||
|     public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { | ||||
|         super.onRequestPermissionsResult(requestCode, permissions, grantResults); | ||||
|  | ||||
|         if (requestCode == REQUEST_REQUIRED_PERMISSIONS) { | ||||
|             boolean allPermissionsGranted = true; | ||||
|             for (int result : grantResults) { | ||||
|                 if (result != PackageManager.PERMISSION_GRANTED) { | ||||
|                     allPermissionsGranted = false; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (allPermissionsGranted) { | ||||
|                 initUIAndLogic(null); | ||||
|             } else { | ||||
|                 // 关键修改2:更新提示文案,告知用户新增的“通话记录权限” | ||||
|                 showPermissionDeniedDialogAndExit(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // 核心修改:新增“设置权限”按钮,点击调用 AppGoToSettingsUtil 跳转设置页 | ||||
|     private void showPermissionDeniedDialogAndExit() { | ||||
|         new AlertDialog.Builder(this) | ||||
| 			.setTitle("权限不足,无法使用") | ||||
| 			// 文案修改:明确新增“通话记录读取”权限 | ||||
| 			.setMessage("应用需要「通讯录读取」、「电话」和「通话记录读取」权限才能正常运行,请授予权限后重新打开应用。") | ||||
| 			.setCancelable(false) | ||||
| 			// 新增:左侧“设置权限”按钮(先添加的按钮在左侧) | ||||
| 			.setNegativeButton("设置权限", new AlertDialog.OnClickListener() { | ||||
| 				@Override | ||||
| 				public void onClick(android.content.DialogInterface dialog, int which) { | ||||
| 					dialog.dismiss(); | ||||
| 					// 调用工具类跳转应用设置页(按需求实现) | ||||
| 					AppGoToSettingsUtil appGoToSettingsUtil = new AppGoToSettingsUtil(); | ||||
| 					appGoToSettingsUtil.GoToSetting(MainActivity.this); | ||||
| 				} | ||||
| 			}) | ||||
| 			// 原有:右侧“确定退出”按钮(后添加的按钮在右侧) | ||||
| 			.setPositiveButton("确定退出", new AlertDialog.OnClickListener() { | ||||
| 				@Override | ||||
| 				public void onClick(android.content.DialogInterface dialog, int which) { | ||||
| 					dialog.dismiss(); | ||||
| 					finishAndRemoveTask(); | ||||
| 				} | ||||
| 			}) | ||||
| 			.show(); | ||||
|     } | ||||
|  | ||||
|     // 初始化UI和逻辑(无需修改,权限通过后才加载 CallLogFragment) | ||||
|     private void initUIAndLogic(Bundle savedInstanceState) { | ||||
|         setContentView(R.layout.activity_main); | ||||
|  | ||||
|         // 初始化工具栏 | ||||
|         mToolbar = findViewById(R.id.activitymainToolbar1); | ||||
|         mToolbar = (Toolbar) findViewById(R.id.activitymainToolbar1); | ||||
|         setSupportActionBar(mToolbar); | ||||
| //        if (isEnableDisplayHomeAsUp()) { | ||||
| //            // 显示后退按钮 | ||||
| //            getSupportActionBar().setDisplayHomeAsUpEnabled(true); | ||||
| //        } | ||||
|         getSupportActionBar().setSubtitle(TAG); | ||||
|  | ||||
|         tabLayout = findViewById(R.id.tabLayout); | ||||
|         viewPager = findViewById(R.id.viewPager); | ||||
|         tabLayout = (TabLayout) findViewById(R.id.tabLayout); | ||||
|         viewPager = (ViewPager) findViewById(R.id.viewPager); | ||||
|  | ||||
|         // 创建Fragment列表和标题列表 | ||||
|         fragmentList = new ArrayList<>(); | ||||
|         tabTitleList = new ArrayList<>(); | ||||
|         fragmentList = new ArrayList<Fragment>(); | ||||
|         tabTitleList = new ArrayList<String>(); | ||||
|         // CallLogFragment 仅在权限通过后才实例化(避免提前触发读取) | ||||
|         fragmentList.add(CallLogFragment.newInstance(0)); | ||||
|         fragmentList.add(ContactsFragment.newInstance(1)); | ||||
|         fragmentList.add(LogFragment.newInstance(2)); | ||||
| @@ -136,62 +194,34 @@ final public class MainActivity extends AppCompatActivity implements IWinBoLLAct | ||||
|         tabTitleList.add("联系人"); | ||||
|         tabTitleList.add("应用日志"); | ||||
|  | ||||
|         // 设置ViewPager的适配器 | ||||
|         MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager(), fragmentList, tabTitleList); | ||||
|         viewPager.setAdapter(adapter); | ||||
|  | ||||
|         // 关联TabLayout和ViewPager | ||||
|         viewPager.setOffscreenPageLimit(0); // 关闭预加载,避免提前初始化 CallLogFragment | ||||
|         tabLayout.setupWithViewPager(viewPager); | ||||
|  | ||||
|  | ||||
|  | ||||
| //        initData(); | ||||
| //        initView(); | ||||
| //        //initPoint();//调用初始化导航原点的方法 | ||||
| //        viewPager.addOnPageChangeListener(this);//滑动事件 | ||||
|  | ||||
|         //ViewPager viewPager = findViewById(R.id.activitymainViewPager1); | ||||
|         //MyPagerAdapter pagerAdapter = new MyPagerAdapter(getSupportFragmentManager()); | ||||
|         //viewPager.setAdapter(pagerAdapter); | ||||
|         //TabLayout tabLayout = findViewById(R.id.activitymainTabLayout1); | ||||
|         //tabLayout.setupWithViewPager(viewPager); | ||||
|  | ||||
| //        mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class); | ||||
| //        if (mMainServiceBean == null) { | ||||
| //            mMainServiceBean = new MainServiceBean(); | ||||
| //        } | ||||
| //        cbMainService = findViewById(R.id.activitymainCheckBox1); | ||||
| //        cbMainService.setChecked(mMainServiceBean.isEnable()); | ||||
| //        cbMainService.setOnClickListener(new View.OnClickListener(){ | ||||
| //                @Override | ||||
| //                public void onClick(View view) { | ||||
| //                    if (cbMainService.isChecked()) { | ||||
| //                        MainService.startMainService(MainActivity.this); | ||||
| //                    } else { | ||||
| //                        MainService.stopMainService(MainActivity.this); | ||||
| //                    } | ||||
| //                } | ||||
| //            }); | ||||
|  | ||||
|         // 原有服务启动、电话监听等逻辑... | ||||
|         MainServiceBean mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class); | ||||
|         if (mMainServiceBean == null) { | ||||
|             mMainServiceBean = new MainServiceBean(); | ||||
|             MainServiceBean.saveBean(this, mMainServiceBean); | ||||
|         } | ||||
|         if (mMainServiceBean.isEnable()) { | ||||
|             MainService.startMainService(this); | ||||
|             new Handler().postDelayed(new Runnable() { | ||||
| 					@Override | ||||
| 					public void run() { | ||||
| 						MainService.startMainService(MainActivity.this); | ||||
| 					} | ||||
| 				}, 1000); | ||||
|         } | ||||
|  | ||||
|         // 初始化TelephonyManager和PhoneStateListener | ||||
|         telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE); | ||||
|         phoneStateListener = new MyPhoneStateListener(); | ||||
|         telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     // ViewPager的适配器 | ||||
|     // 以下为原有代码(无需修改) | ||||
|     private class MyPagerAdapter extends FragmentPagerAdapter { | ||||
|  | ||||
|         private List<Fragment> fragmentList; | ||||
|         private List<String> tabTitleList; | ||||
|  | ||||
| @@ -221,96 +251,24 @@ final public class MainActivity extends AppCompatActivity implements IWinBoLLAct | ||||
|         Intent intent = new Intent(Intent.ACTION_DIAL); | ||||
|         intent.setData(android.net.Uri.parse("tel:" + phoneNumber)); | ||||
|         if (ActivityCompat.checkSelfPermission(_MainActivity, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) { | ||||
|             Toast.makeText(_MainActivity, "拨号权限不足", Toast.LENGTH_SHORT).show(); | ||||
|             return; | ||||
|         } | ||||
|         _MainActivity.startActivity(intent); | ||||
|     } | ||||
|  | ||||
|     //初始化view,即显示的图片 | ||||
| //    void initView() { | ||||
| //        viewPager = findViewById(R.id.activitymainViewPager1); | ||||
| //        pagerAdapter = new MyPagerAdapter(getSupportFragmentManager()); | ||||
| //        viewPager.setAdapter(pagerAdapter); | ||||
| //        //adapter = new MyPagerAdapter(views); | ||||
| //        //viewPager = findViewById(R.id.activitymainViewPager1); | ||||
| //        //viewPager.setAdapter(adapter); | ||||
| //        //linearLayout = findViewById(R.id.activitymainLinearLayout1); | ||||
| //        //initPoint();//初始化页面下方的点 | ||||
| //        viewPager.setOnPageChangeListener(this); | ||||
| // | ||||
| //    } | ||||
|  | ||||
|     //初始化所要显示的布局 | ||||
| //    void initData() { | ||||
| //        LayoutInflater inflater = LayoutInflater.from(getActivity()); | ||||
| //        View view1 = inflater.inflate(R.layout.fragment_call_log, viewPager, false); | ||||
| //        View view2 = inflater.inflate(R.layout.fragment_contacts, viewPager, false); | ||||
| //        View view3 = inflater.inflate(R.layout.fragment_log, viewPager, false); | ||||
| // | ||||
| //        views = new ArrayList<>(); | ||||
| //        views.add(view1); | ||||
| //        views.add(view2); | ||||
| //        views.add(view3); | ||||
| //    } | ||||
|  | ||||
| //    void initPoint() { | ||||
| //        imageViews = new ImageView[5];//实例化5个图片 | ||||
| //        for (int i = 0; i < linearLayout.getChildCount(); i++) { | ||||
| //            imageViews[i] = (ImageView) linearLayout.getChildAt(i); | ||||
| //            imageViews[i].setImageResource(R.drawable.ic_launcher); | ||||
| //            imageViews[i].setOnClickListener(this);//点击导航点,即可跳转 | ||||
| //            imageViews[i].setTag(i);//重复利用实例化的对象 | ||||
| //        } | ||||
| //        currentPoint = 0;//默认第一个坐标 | ||||
| //        imageViews[currentPoint].setImageResource(R.drawable.ic_launcher); | ||||
| //    } | ||||
|  | ||||
|     //OnPageChangeListener接口要实现的三个方法 | ||||
|     /*    onPageScrollStateChanged(int state) | ||||
|      此方法是在状态改变的时候调用,其中state这个参数有三种状态: | ||||
|      SCROLL_STATE_DRAGGING(1)表示用户手指“按在屏幕上并且开始拖动”的状态 | ||||
|      (手指按下但是还没有拖动的时候还不是这个状态,只有按下并且手指开始拖动后log才打出。) | ||||
|      SCROLL_STATE_IDLE(0)滑动动画做完的状态。 | ||||
|      SCROLL_STATE_SETTLING(2)在“手指离开屏幕”的状态。*/ | ||||
|     @Override | ||||
|     public void onPageScrollStateChanged(int state) { | ||||
|  | ||||
|     } | ||||
|     /*    onPageScrolled(int position, float positionOffset, int positionOffsetPixels) | ||||
|      当页面在滑动的时候会调用此方法,在滑动被停止之前,此方法回一直得到调用。其中三个参数的含义分别为: | ||||
|  | ||||
|      position :当前页面,即你点击滑动的页面(从A滑B,则是A页面的position。 | ||||
|      positionOffset:当前页面偏移的百分比 | ||||
|      positionOffsetPixels:当前页面偏移的像素位置*/ | ||||
|     public void onPageScrollStateChanged(int state) {} | ||||
|     @Override | ||||
|     public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { | ||||
|  | ||||
|     } | ||||
|     /*    onPageSelected(int position) | ||||
|      此方法是页面滑动完后得到调用,position是你当前选中的页面的Position(位置编号) | ||||
|      (从A滑动到B,就是B的position)*/ | ||||
|     public void onPageSelected(int position) { | ||||
|  | ||||
| //        ImageView preView = imageViews[currentPoint]; | ||||
| //        preView.setImageResource(R.drawable.ic_launcher); | ||||
| //        ImageView currView = imageViews[position]; | ||||
| //        currView.setImageResource(R.drawable.ic_launcher); | ||||
| //        currentPoint = position; | ||||
|     } | ||||
|  | ||||
|     //小圆点点击事件 | ||||
|     public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {} | ||||
|     @Override | ||||
|     public void onClick(View v) { | ||||
|         // TODO Auto-generated method stub | ||||
|         //通过getTag(),可以判断是哪个控件 | ||||
| //        int i = (Integer) v.getTag(); | ||||
| //        viewPager.setCurrentItem(i);//直接跳转到某一个页面的情况 | ||||
|     } | ||||
|     public void onPageSelected(int position) {} | ||||
|     @Override | ||||
|     public void onClick(View v) {} | ||||
|  | ||||
|     @Override | ||||
|     protected void onPostCreate(Bundle savedInstanceState) { | ||||
|         super.onPostCreate(savedInstanceState); | ||||
|         //setSubTitle(""); | ||||
|     } | ||||
|  | ||||
|     private class MyPhoneStateListener extends PhoneStateListener { | ||||
| @@ -336,109 +294,28 @@ final public class MainActivity extends AppCompatActivity implements IWinBoLLAct | ||||
|         LogUtils.d(TAG, "onDestroy() SOS"); | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|     // | ||||
|     // 处理传入的 Intent 数据 | ||||
|     // | ||||
| //    boolean prosessIntents(Intent intent) { | ||||
| //        if (intent == null  | ||||
| //            || intent.getAction() == null | ||||
| //            || intent.getAction().equals("")) | ||||
| //            return false; | ||||
| // | ||||
| //        if (intent.getAction().equals(StringToQrCodeView.ACTION_UNITTEST_QRCODE)) { | ||||
| //            try { | ||||
| //                WinBoLLActivity clazzActivity = UnitTestActivity.class.newInstance(); | ||||
| //                String tag = clazzActivity.getTag(); | ||||
| //                LogUtils.d(TAG, "String tag = clazzActivity.getTag(); tag " + tag); | ||||
| //                Intent subIntent = new Intent(this, UnitTestActivity.class); | ||||
| //                subIntent.setAction(intent.getAction()); | ||||
| //                File file = new File(getCacheDir(), UUID.randomUUID().toString()); | ||||
| //                //取出文件uri | ||||
| //                Uri uri = intent.getData(); | ||||
| //                if (uri == null) { | ||||
| //                    uri = intent.getParcelableExtra(Intent.EXTRA_STREAM); | ||||
| //                } | ||||
| //                //获取文件真实地址 | ||||
| //                String szSrcPath = UriUtils.getFileFromUri(getApplication(), uri); | ||||
| //                if (TextUtils.isEmpty(szSrcPath)) { | ||||
| //                    return false; | ||||
| //                } | ||||
| // | ||||
| //                Files.copy(Paths.get(szSrcPath), Paths.get(file.getPath())); | ||||
| //                //startWinBoLLActivity(subIntent, tag); | ||||
| //                WinBoLLActivityManager.getInstance(this).startWinBoLLActivity(this, subIntent, UnitTestActivity.class); | ||||
| //            } catch (IllegalAccessException | InstantiationException | IOException e) { | ||||
| //                LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
| //                // 函数处理异常返回失败 | ||||
| //                return false; | ||||
| //            } | ||||
| //        } else { | ||||
| //            LogUtils.d(TAG, "prosessIntents|" + intent.getAction() + "|yet"); | ||||
| //            return false; | ||||
| //        } | ||||
| //        return true; | ||||
| //    } | ||||
|  | ||||
| //    @Override | ||||
| //    public String getTag() { | ||||
| //        return TAG; | ||||
| //    } | ||||
|  | ||||
| //    @Override | ||||
| //    public void onBackPressed() { | ||||
| //        exit(); | ||||
| //    } | ||||
| // | ||||
| //    void exit() { | ||||
| //        YesNoAlertDialog.OnDialogResultListener listener = new YesNoAlertDialog.OnDialogResultListener(){ | ||||
| // | ||||
| //            @Override | ||||
| //            public void onYes() { | ||||
| //                WinBoLLActivityManager.getInstance(getApplicationContext()).finishAll(); | ||||
| //            } | ||||
| // | ||||
| //            @Override | ||||
| //            public void onNo() { | ||||
| //            } | ||||
| //        }; | ||||
| //        YesNoAlertDialog.show(this, "[ " + getString(R.string.app_name) + " ]", "Exit(Yes/No).\nIs close all activity?", listener); | ||||
| //    } | ||||
|  | ||||
|     @Override | ||||
|     public boolean onCreateOptionsMenu(Menu menu) { | ||||
|         getMenuInflater().inflate(R.menu.toolbar_main, menu); | ||||
|         return super.onCreateOptionsMenu(menu); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     @Override | ||||
|     public boolean onOptionsItemSelected(MenuItem item) { | ||||
|         if (item.getItemId() == R.id.item_settings) { | ||||
|             Intent intent = new Intent(this, SettingsActivity.class); | ||||
|             startActivity(intent); | ||||
|             //WinBoLLActivityManager.getInstance(this).startWinBoLLActivity(this, CallActivity.class); | ||||
|         } | ||||
| //        } else  | ||||
| //        if (item.getItemId() == R.id.item_exit) { | ||||
| //            exit(); | ||||
| //            return true; | ||||
| //        } | ||||
|         return super.onOptionsItemSelected(item); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     @Override | ||||
|     protected void onResume() { | ||||
|         super.onResume(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Android M 及以上检查是否是系统默认电话应用 | ||||
|      */ | ||||
|     public boolean isDefaultPhoneCallApp() { | ||||
|         if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { | ||||
|         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { | ||||
|             TelecomManager manger = (TelecomManager) getSystemService(TELECOM_SERVICE); | ||||
|             if (manger != null && manger.getDefaultDialerPackage() != null) { | ||||
|                 return manger.getDefaultDialerPackage().equals(getPackageName()); | ||||
| @@ -452,35 +329,24 @@ final public class MainActivity extends AppCompatActivity implements IWinBoLLAct | ||||
|         if (manager == null) return false; | ||||
|  | ||||
|         for (ActivityManager.RunningServiceInfo service : manager.getRunningServices( | ||||
|             Integer.MAX_VALUE)) { | ||||
| 			Integer.MAX_VALUE)) { | ||||
|             if (serviceClass.getName().equals(service.service.getClassName())) { | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onActivityResult(int requestCode, int resultCode, Intent data) { | ||||
| //        switch (resultCode) { | ||||
| //            case REQUEST_HOME_ACTIVITY : { | ||||
| //                    LogUtils.d(TAG, "REQUEST_HOME_ACTIVITY"); | ||||
| //                    break; | ||||
| //                } | ||||
| //            case REQUEST_ABOUT_ACTIVITY : { | ||||
| //                    LogUtils.d(TAG, "REQUEST_ABOUT_ACTIVITY"); | ||||
| //                    break; | ||||
| //                } | ||||
| //            default : { | ||||
| //                    super.onActivityResult(requestCode, resultCode, data); | ||||
| //                } | ||||
| //        } | ||||
|         if (requestCode == DIALER_REQUEST_CODE) { | ||||
|             if (resultCode == Activity.RESULT_OK) { | ||||
|                 Toast.makeText(MainActivity.this, getString(R.string.app_name) + " 已成为默认电话应用", | ||||
|                                Toast.LENGTH_SHORT).show(); | ||||
| 							   Toast.LENGTH_SHORT).show(); | ||||
|             } | ||||
|         } | ||||
|         } else if (requestCode == REQUEST_APP_SETTINGS) { | ||||
| 			recreate(); | ||||
| 		} | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.contacts.activities; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/03/31 15:15:54 | ||||
|  * @Describe 应用介绍窗口 | ||||
|  */ | ||||
| @@ -79,12 +79,12 @@ public class AboutActivity extends AppCompatActivity implements IWinBoLLActivity | ||||
|         APPInfo appInfo = new APPInfo(); | ||||
|         appInfo.setAppName("Contacts"); | ||||
|         appInfo.setAppIcon(cc.winboll.studio.libaes.R.drawable.ic_winboll); | ||||
|         appInfo.setAppDescription("通讯录与拨号"); | ||||
|         appInfo.setAppGitName("APP"); | ||||
|         appInfo.setAppDescription("这是可以根据正则表达式匹配拦截骚扰电话的手机拨号应用。"); | ||||
|         appInfo.setAppGitName("APPBase"); | ||||
|         appInfo.setAppGitOwner("Studio"); | ||||
|         appInfo.setAppGitAPPBranch(szBranchName); | ||||
|         appInfo.setAppGitAPPSubProjectFolder(szBranchName); | ||||
|         appInfo.setAppHomePage("https://www.winboll.cc/studio/details.php?app=Contacts"); | ||||
|         appInfo.setAppHomePage("https://discuz.winboll.cc/forum.php?mod=viewthread&tid=4&extra=page%3D1"); | ||||
|         appInfo.setAppAPKName("Contacts"); | ||||
|         appInfo.setAppAPKFolderName("Contacts"); | ||||
|         return new AboutView(mContext, appInfo); | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.contacts.activities; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/02/20 17:15:46 | ||||
|  * @Describe 拨号窗口 | ||||
|  */ | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.contacts.activities; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/02/20 20:18:26 | ||||
|  */ | ||||
| import android.content.Intent; | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.contacts.activities; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/02/21 05:37:42 | ||||
|  */ | ||||
| import android.content.Context; | ||||
| @@ -198,6 +198,9 @@ public class SettingsActivity extends AppCompatActivity implements IWinBoLLActiv | ||||
|             settingsModel.setDunTotalCount(Integer.parseInt(etDunTotalCount.getText().toString())); | ||||
|             settingsModel.setDunResumeSecondCount(Integer.parseInt(etDunResumeSecondCount.getText().toString())); | ||||
|             settingsModel.setDunResumeCount(Integer.parseInt(etDunResumeCount.getText().toString())); | ||||
| 			 | ||||
| 			// 应用效果提示 | ||||
| 			ToastUtils.show((settingsModel.getDunTotalCount() == 1)?"电话骚扰防御力几乎为0。":String.format("以下设置将在连拨%d次后接通电话。", settingsModel.getDunTotalCount())); | ||||
|         } | ||||
|         settingsModel.setIsEnableDun(isEnableDun); | ||||
|         Rules.getInstance(this).saveDun(); | ||||
| @@ -207,6 +210,7 @@ public class SettingsActivity extends AppCompatActivity implements IWinBoLLActiv | ||||
|         etDunTotalCount.setText(Integer.toString(settingsModel.getDunTotalCount())); | ||||
|         etDunResumeSecondCount.setText(Integer.toString(settingsModel.getDunResumeSecondCount())); | ||||
|         etDunResumeCount.setText(Integer.toString(settingsModel.getDunResumeCount())); | ||||
| 		 | ||||
|     } | ||||
|  | ||||
|     void updateStreamVolumeTextView() { | ||||
| @@ -243,6 +247,9 @@ public class SettingsActivity extends AppCompatActivity implements IWinBoLLActiv | ||||
|         Rules.getInstance(this).resetDefaultBoBullToonURL(); | ||||
|         EditText etBoBullToonURL = findViewById(R.id.bobulltoonurl_et); | ||||
|         etBoBullToonURL.setText(Rules.getInstance(this).getBoBullToonURL()); | ||||
| 		 | ||||
| 		final TomCat tomCat = TomCat.getInstance(this); | ||||
| 		tomCat.cleanBoBullToon(); | ||||
|     } | ||||
|  | ||||
|     public void onDownloadBoBullToon(View view) { | ||||
| @@ -330,4 +337,8 @@ public class SettingsActivity extends AppCompatActivity implements IWinBoLLActiv | ||||
|     public void onAbout(View view) { | ||||
|         App.getWinBoLLActivityManager().startWinBoLLActivity(this, AboutActivity.class); | ||||
|     } | ||||
| 	 | ||||
| 	public void onLogView(View view) { | ||||
|         App.getWinBoLLActivityManager().startLogActivity(this); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -11,7 +11,7 @@ import cc.winboll.studio.libappbase.LogUtils; | ||||
| import cc.winboll.studio.libappbase.LogView; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/03/02 16:07:04 | ||||
|  */ | ||||
| public class UnitTestActivity extends Activity { | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.contacts.activities; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/03/31 15:16:45 | ||||
|  * @Describe 应用窗口基类 | ||||
|  */ | ||||
|   | ||||
| @@ -1,17 +1,22 @@ | ||||
| package cc.winboll.studio.contacts.adapters; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/02/26 13:09:32 | ||||
|  * @Describe CallLogAdapter | ||||
|  */ | ||||
| import android.content.ClipData; | ||||
| import android.content.ClipboardManager; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.MenuItem; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.widget.Button; | ||||
| import android.widget.PopupMenu; | ||||
| import android.widget.TextView; | ||||
| import android.widget.Toast; | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.recyclerview.widget.RecyclerView; | ||||
| import cc.winboll.studio.contacts.R; | ||||
| @@ -35,6 +40,10 @@ public class CallLogAdapter extends RecyclerView.Adapter<CallLogAdapter.CallLogV | ||||
|         this.mContactUtils = ContactUtils.getInstance(mContext); | ||||
|         this.callLogList = callLogList; | ||||
|     } | ||||
| 	 | ||||
| 	public void relaodContacts() { | ||||
| 		this.mContactUtils.relaodContacts(); | ||||
| 	} | ||||
|  | ||||
|     @NonNull | ||||
|     @Override | ||||
| @@ -47,6 +56,38 @@ public class CallLogAdapter extends RecyclerView.Adapter<CallLogAdapter.CallLogV | ||||
|     public void onBindViewHolder(@NonNull CallLogViewHolder holder, int position) { | ||||
|         final CallLogModel callLog = callLogList.get(position); | ||||
|         holder.phoneNumber.setText(callLog.getPhoneNumber() + "☎" + mContactUtils.getContactsName(callLog.getPhoneNumber())); | ||||
| 		holder.phoneNumber.setOnLongClickListener(new View.OnLongClickListener() { | ||||
| 				@Override | ||||
| 				public boolean onLongClick(View p1) { | ||||
| 					// 弹出复制菜单 | ||||
| 					PopupMenu menu = new PopupMenu(mContext, holder.phoneNumber); | ||||
| 					//加载菜单资源 | ||||
| 					menu.getMenuInflater().inflate(R.menu.toolbar_calllog_phonenumber, menu.getMenu()); | ||||
| 					//设置点击事件的响应 | ||||
| 					menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { | ||||
| 							@Override | ||||
| 							public boolean onMenuItemClick(MenuItem menuItem) { | ||||
| 								int nItemId = menuItem.getItemId(); | ||||
| 								if (nItemId == R.id.item_calllog_phonenumber_copy) { | ||||
| 									// Gets a handle to the clipboard service. | ||||
| 									ClipboardManager clipboard = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE); | ||||
| 									// Creates a new text clip to put on the clipboard | ||||
| 									ClipData clip = ClipData.newPlainText("simple text", callLog.getPhoneNumber()); | ||||
| 									// Set the clipboard's primary clip. | ||||
| 									clipboard.setPrimaryClip(clip); | ||||
| 									Toast.makeText(mContext, "Copy to clipboard.", Toast.LENGTH_SHORT).show(); | ||||
| 								} | ||||
|  | ||||
| 								return true; | ||||
| 							} | ||||
| 						}); | ||||
| 					//一定要调用show()来显示弹出式菜单 | ||||
| 					menu.show(); | ||||
|  | ||||
| 					return true; | ||||
| 				} | ||||
| 			}); | ||||
| 		 | ||||
|         holder.callStatus.setText(callLog.getCallStatus()); | ||||
|         SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); | ||||
|         holder.callDate.setText(dateFormat.format(callLog.getCallDate())); | ||||
|   | ||||
| @@ -1,23 +1,29 @@ | ||||
| package cc.winboll.studio.contacts.adapters; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/02/26 13:35:44 | ||||
|  * @Describe ContactAdapter | ||||
|  */ | ||||
| import android.content.ClipData; | ||||
| import android.content.ClipboardManager; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.MenuItem; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.widget.Button; | ||||
| import android.widget.LinearLayout; | ||||
| import android.widget.PopupMenu; | ||||
| import android.widget.TextView; | ||||
| import android.widget.Toast; | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.recyclerview.widget.RecyclerView; | ||||
| import cc.winboll.studio.contacts.R; | ||||
| import cc.winboll.studio.contacts.beans.ContactModel; | ||||
| import cc.winboll.studio.libaes.views.AOHPCTCSeekBar; | ||||
| import com.hjq.toast.ToastUtils; | ||||
| import java.util.List; | ||||
| import cc.winboll.studio.libaes.views.AOHPCTCSeekBar; | ||||
|  | ||||
| public class ContactAdapter extends RecyclerView.Adapter<ContactAdapter.ContactViewHolder> { | ||||
|  | ||||
| @@ -26,8 +32,10 @@ public class ContactAdapter extends RecyclerView.Adapter<ContactAdapter.ContactV | ||||
|     private static final int REQUEST_CALL_PHONE = 1; | ||||
|  | ||||
|     private List<ContactModel> contactList; | ||||
| 	Context mContext; | ||||
|  | ||||
|     public ContactAdapter(List<ContactModel> contactList) { | ||||
|     public ContactAdapter(Context context, List<ContactModel> contactList) { | ||||
| 		mContext = context; | ||||
|         this.contactList = contactList; | ||||
|     } | ||||
|  | ||||
| @@ -41,6 +49,37 @@ public class ContactAdapter extends RecyclerView.Adapter<ContactAdapter.ContactV | ||||
|     @Override | ||||
|     public void onBindViewHolder(@NonNull ContactViewHolder holder, int position) { | ||||
|         final ContactModel contact = contactList.get(position); | ||||
| 		holder.llPhoneNumberMain.setOnLongClickListener(new View.OnLongClickListener() { | ||||
| 				@Override | ||||
| 				public boolean onLongClick(View p1) { | ||||
| 					// 弹出复制菜单 | ||||
| 					PopupMenu menu = new PopupMenu(mContext, holder.llPhoneNumberMain); | ||||
| 					//加载菜单资源 | ||||
| 					menu.getMenuInflater().inflate(R.menu.toolbar_contact_phonenumber, menu.getMenu()); | ||||
| 					//设置点击事件的响应 | ||||
| 					menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { | ||||
| 							@Override | ||||
| 							public boolean onMenuItemClick(MenuItem menuItem) { | ||||
| 								int nItemId = menuItem.getItemId(); | ||||
| 								if (nItemId == R.id.item_contact_phonenumber_copy) { | ||||
| 									// Gets a handle to the clipboard service. | ||||
| 									ClipboardManager clipboard = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE); | ||||
| 									// Creates a new text clip to put on the clipboard | ||||
| 									ClipData clip = ClipData.newPlainText("simple text", contact.getNumber()); | ||||
| 									// Set the clipboard's primary clip. | ||||
| 									clipboard.setPrimaryClip(clip); | ||||
| 									Toast.makeText(mContext, "Copy to clipboard.", Toast.LENGTH_SHORT).show(); | ||||
| 								} | ||||
|  | ||||
| 								return true; | ||||
| 							} | ||||
| 						}); | ||||
| 					//一定要调用show()来显示弹出式菜单 | ||||
| 					menu.show(); | ||||
|  | ||||
| 					return true; | ||||
| 				} | ||||
| 			}); | ||||
|         holder.contactName.setText(contact.getName()); | ||||
|         holder.contactNumber.setText(contact.getNumber()); | ||||
|  | ||||
| @@ -69,12 +108,14 @@ public class ContactAdapter extends RecyclerView.Adapter<ContactAdapter.ContactV | ||||
|     } | ||||
|  | ||||
|     public class ContactViewHolder extends RecyclerView.ViewHolder { | ||||
| 		LinearLayout llPhoneNumberMain; | ||||
|         TextView contactName; | ||||
|         TextView contactNumber; | ||||
|         AOHPCTCSeekBar dialAOHPCTCSeekBar; | ||||
|          | ||||
|         public ContactViewHolder(@NonNull View itemView) { | ||||
|             super(itemView); | ||||
| 			llPhoneNumberMain = itemView.findViewById(R.id.itemcontactLinearLayout1); | ||||
|             contactName = itemView.findViewById(R.id.contact_name); | ||||
|             contactNumber = itemView.findViewById(R.id.contact_number); | ||||
|             dialAOHPCTCSeekBar = itemView.findViewById(R.id.aohpctcseekbar_dial); | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.contacts.adapters; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/02/20 14:20:38 | ||||
|  * @Describe ImagePagerAdapter | ||||
|  */ | ||||
|   | ||||
| @@ -1,12 +1,13 @@ | ||||
| package cc.winboll.studio.contacts.adapters; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/03/02 17:27:41 | ||||
|  * @Describe PhoneConnectRuleAdapter | ||||
|  */ | ||||
| import android.content.Context; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.MotionEvent; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.widget.Button; | ||||
| @@ -20,6 +21,7 @@ import cc.winboll.studio.contacts.R; | ||||
| import cc.winboll.studio.contacts.beans.PhoneConnectRuleModel; | ||||
| import cc.winboll.studio.contacts.dun.Rules; | ||||
| import cc.winboll.studio.contacts.views.LeftScrollView; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| import cc.winboll.studio.libappbase.dialogs.YesNoAlertDialog; | ||||
| import com.hjq.toast.ToastUtils; | ||||
| import java.util.ArrayList; | ||||
| @@ -60,6 +62,10 @@ public class PhoneConnectRuleAdapter extends RecyclerView.Adapter<RecyclerView.V | ||||
|             final SimpleViewHolder simpleViewHolder = (SimpleViewHolder) holder; | ||||
|             String szView = model.getRuleText().trim().equals("") ?"[NULL]": model.getRuleText(); | ||||
|             simpleViewHolder.tvRuleText.setText(szView); | ||||
|             simpleViewHolder.checkBoxAllow.setChecked(model.isAllowConnection()); | ||||
| 			simpleViewHolder.checkBoxAllow.setEnabled(false); | ||||
|             simpleViewHolder.checkBoxEnable.setChecked(model.isEnable()); | ||||
| 			simpleViewHolder.checkBoxEnable.setEnabled(false); | ||||
|             simpleViewHolder.scrollView.setOnActionListener(new LeftScrollView.OnActionListener(){ | ||||
|  | ||||
|                     @Override | ||||
| @@ -215,16 +221,22 @@ public class PhoneConnectRuleAdapter extends RecyclerView.Adapter<RecyclerView.V | ||||
|  | ||||
|         private final LeftScrollView scrollView; | ||||
|         private final TextView tvRuleText; | ||||
| 		CheckBox checkBoxAllow; | ||||
|         CheckBox checkBoxEnable; | ||||
|  | ||||
|  | ||||
|         public SimpleViewHolder(@NonNull ViewGroup parent, @NonNull View itemView) { | ||||
|             super(itemView); | ||||
|             scrollView = itemView.findViewById(R.id.scrollView); | ||||
|             //tvRuleText = itemView.findViewById(R.id.ruletext_tv); | ||||
|             tvRuleText = new TextView(itemView.getContext()); | ||||
| 			LayoutInflater inflater = LayoutInflater.from(itemView.getContext()); | ||||
| 		    View viewContent = inflater.inflate(R.layout.view_phone_connect_rule_simple_content, parent, false); | ||||
|             tvRuleText = viewContent.findViewById(R.id.ruletext_tv); | ||||
|             checkBoxAllow = viewContent.findViewById(R.id.checkbox_allow); | ||||
|             checkBoxEnable = viewContent.findViewById(R.id.checkbox_enable); | ||||
|             //tvRuleText = new TextView(itemView.getContext()); | ||||
|             scrollView.setContentWidth(parent.getWidth()); | ||||
|             //scrollView.setContentWidth(600); | ||||
|             scrollView.addContentLayout(tvRuleText); | ||||
|             scrollView.addContentLayout(viewContent); | ||||
|         } | ||||
|  | ||||
|     } | ||||
| @@ -243,5 +255,9 @@ public class PhoneConnectRuleAdapter extends RecyclerView.Adapter<RecyclerView.V | ||||
|             buttonConfirm = itemView.findViewById(R.id.button_confirm); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| 	private void setCheckBoxTouchListener(CheckBox checkBox) { | ||||
|  | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.contacts.beans; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/02/26 13:10:57 | ||||
|  * @Describe CallLogModel | ||||
|  */ | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| package cc.winboll.studio.contacts.beans; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Date 2025/02/26 13:37:00 | ||||
|  * @Describe ContactModel | ||||
|  * @Author ZhanGSKen&豆包大模型<zhangsken@qq.com> | ||||
|  * @Date 2025/08/30 14:32 | ||||
|  * @Describe 联系人信息数据模型 | ||||
|  */ | ||||
| import net.sourceforge.pinyin4j.PinyinHelper; | ||||
| import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType; | ||||
| @@ -18,13 +18,18 @@ public class ContactModel { | ||||
|     private String name; | ||||
|     private String number; | ||||
|     private String pinyin; | ||||
|     // 新增:存储姓名的拼音首字母(如"啊牛"→"an") | ||||
|     private String pinyinFirstLetter; | ||||
|  | ||||
|     public ContactModel(String name, String number) { | ||||
|         this.name = name; | ||||
|         this.number = number.replaceAll("\\s", ""); | ||||
|         this.pinyin = convertToPinyin(name); | ||||
|         // 初始化时生成拼音首字母 | ||||
|         this.pinyinFirstLetter = convertToPinyinFirstLetter(name); | ||||
|     } | ||||
|  | ||||
|     // 原方法:转换为全拼(如"啊牛"→"aniu") | ||||
|     private String convertToPinyin(String chinese) { | ||||
|         HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat(); | ||||
|         format.setCaseType(HanyuPinyinCaseType.LOWERCASE); | ||||
| @@ -33,22 +38,55 @@ public class ContactModel { | ||||
|         StringBuilder pinyin = new StringBuilder(); | ||||
|         for (int i = 0; i < chinese.length(); i++) { | ||||
|             char ch = chinese.charAt(i); | ||||
|             if (Character.toString(ch).matches("[\\u4e00-\\u9fa5]")) { | ||||
|             if (Character.toString(ch).matches("[\\u4e00-\\u9fa5]")) { // 仅处理汉字 | ||||
|                 try { | ||||
|                     String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(ch, format); | ||||
|                     if (pinyinArray != null) { | ||||
|                         pinyin.append(pinyinArray[0]); | ||||
|                     if (pinyinArray != null && pinyinArray.length > 0) { | ||||
|                         pinyin.append(pinyinArray[0]); // 取第一个拼音(多音字默认首选项) | ||||
|                     } | ||||
|                 } catch (BadHanyuPinyinOutputFormatCombination e) { | ||||
|                     e.printStackTrace(); | ||||
|                 } | ||||
|             } else { | ||||
|                 pinyin.append(ch); | ||||
|                 pinyin.append(ch); // 非汉字直接拼接(如字母、数字、符号) | ||||
|             } | ||||
|         } | ||||
|         return pinyin.toString(); | ||||
|     } | ||||
|  | ||||
|     // 新增:转换为拼音首字母(如"啊牛"→"an") | ||||
|     private String convertToPinyinFirstLetter(String chinese) { | ||||
|         HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat(); | ||||
|         format.setCaseType(HanyuPinyinCaseType.LOWERCASE); | ||||
|         format.setToneType(HanyuPinyinToneType.WITHOUT_TONE); | ||||
|  | ||||
|         StringBuilder firstLetters = new StringBuilder(); | ||||
|         for (int i = 0; i < chinese.length(); i++) { | ||||
|             char ch = chinese.charAt(i); | ||||
|             if (Character.toString(ch).matches("[\\u4e00-\\u9fa5]")) { // 仅处理汉字 | ||||
|                 try { | ||||
|                     String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(ch, format); | ||||
|                     if (pinyinArray != null && pinyinArray.length > 0) { | ||||
|                         // 取拼音的第一个字母(如"a"、"niu"→"a"、"n") | ||||
|                         firstLetters.append(pinyinArray[0].charAt(0)); | ||||
|                     } | ||||
|                 } catch (BadHanyuPinyinOutputFormatCombination e) { | ||||
|                     e.printStackTrace(); | ||||
|                 } | ||||
|             } else { | ||||
|                 // 非汉字可根据需求处理:此处保留原字符(如"李3"→"l3","张A"→"za") | ||||
|                 firstLetters.append(ch); | ||||
|             } | ||||
|         } | ||||
|         return firstLetters.toString(); | ||||
|     } | ||||
|  | ||||
|     // 新增:获取拼音首字母 | ||||
|     public String getPinyinFirstLetter() { | ||||
|         return pinyinFirstLetter; | ||||
|     } | ||||
|  | ||||
|     // 原有getter方法 | ||||
|     public String getName() { | ||||
|         return name; | ||||
|     } | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.contacts.beans; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/02/13 07:06:13 | ||||
|  */ | ||||
| import android.util.JsonReader; | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.contacts.beans; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/02/21 09:52:10 | ||||
|  * @Describe 电话黑名单规则 | ||||
|  */ | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.contacts.beans; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/02/24 18:47:11 | ||||
|  * @Describe 手机铃声设置参数类 | ||||
|  */ | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.contacts.beans; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/03/02 19:51:40 | ||||
|  * @Describe SettingsModel | ||||
|  */ | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.contacts.bobulltoon; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/03/02 13:47:48 | ||||
|  * @Describe 汤姆猫管家 :使用 BoBullToon 项目,对通讯地址进行筛选判断的好朋友。 | ||||
|  */ | ||||
| @@ -11,6 +11,7 @@ import cc.winboll.studio.contacts.dun.Rules; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| import com.hjq.toast.ToastUtils; | ||||
| import java.io.File; | ||||
| import java.io.FileFilter; | ||||
| import java.io.FileOutputStream; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| @@ -44,7 +45,7 @@ public class TomCat { | ||||
|         } | ||||
|         return _TomCat; | ||||
|     } | ||||
|      | ||||
|  | ||||
|     public String getDefaultBobulltoonUrl() { | ||||
|         return mContext.getString(R.string.default_bobulltoon_url); | ||||
|     } | ||||
| @@ -123,7 +124,7 @@ public class TomCat { | ||||
|             } | ||||
|  | ||||
|             // 更新新文件 | ||||
|             if(downloadAndExtractZip(zipUrl, destinationFolder)) { | ||||
|             if (downloadAndExtractZip(zipUrl, destinationFolder)) { | ||||
|                 LogUtils.d(TAG, "ZIP 文件下载并解压成功。"); | ||||
|                 return true; | ||||
|             } | ||||
| @@ -154,12 +155,81 @@ public class TomCat { | ||||
|     File getWorkingFolder() { | ||||
|         return mContext.getExternalFilesDir(TAG); | ||||
|     } | ||||
| 	 | ||||
| 	public File getBoBullToonDataFolder() { | ||||
| 		File fCheckRoot = getWorkingFolder(); | ||||
| 		if (fCheckRoot == null || !fCheckRoot.exists()) { | ||||
| 			return fCheckRoot; | ||||
| 		} | ||||
|  | ||||
| 		// 递归查找符合条件的文件夹 | ||||
| 		File targetFolder = findTargetFolder(fCheckRoot); | ||||
| 		return targetFolder != null ? targetFolder : fCheckRoot; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 递归查找同时包含LICENSE和README.md文件的文件夹 | ||||
| 	 */ | ||||
| 	private File findTargetFolder(File currentFolder) { | ||||
| 		// 检查当前文件夹是否符合条件 | ||||
| 		if (hasRequiredFiles(currentFolder)) { | ||||
| 			return currentFolder; | ||||
| 		} | ||||
|  | ||||
| 		// 查找子文件夹(Java 7不支持方法引用,用匿名内部类过滤) | ||||
| 		File[] subFolders = currentFolder.listFiles(new FileFilter() { | ||||
| 				@Override | ||||
| 				public boolean accept(File file) { | ||||
| 					return file.isDirectory(); // 仅保留子文件夹 | ||||
| 				} | ||||
| 			}); | ||||
|  | ||||
| 		if (subFolders != null) { | ||||
| 			for (File subFolder : subFolders) { | ||||
| 				File result = findTargetFolder(subFolder); | ||||
| 				if (result != null) { | ||||
| 					return result; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		return null; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 检查文件夹中是否同时存在LICENSE和README.md文件 | ||||
| 	 */ | ||||
| 	private boolean hasRequiredFiles(File folder) { | ||||
| 		if (folder == null || !folder.isDirectory()) { | ||||
| 			return false; | ||||
| 		} | ||||
|  | ||||
| 		// 检查两个文件是否同时存在且均为文件(非文件夹) | ||||
| 		File licenseFile = new File(folder, "LICENSE"); | ||||
| 		File readmeFile = new File(folder, "README.md"); | ||||
|  | ||||
| 		return licenseFile.exists() && licenseFile.isFile() | ||||
| 			&& readmeFile.exists() && readmeFile.isFile(); | ||||
| 	} | ||||
|  | ||||
| 	public void cleanBoBullToon() { | ||||
| 		String destinationFolder = getWorkingFolder().getPath(); // 替换为实际的目标文件夹路径 | ||||
| 		// 删除旧文件 | ||||
| 		File fOldFolder = new File(destinationFolder); | ||||
| 		if (fOldFolder.exists()) { | ||||
| 			deleteFolderRecursive(fOldFolder); | ||||
| 			fOldFolder.mkdirs(); | ||||
| 		} | ||||
|  | ||||
| 		ToastUtils.show("已清空 BoBullToon 数据!"); | ||||
| 		LogUtils.d(TAG, "已清空 BoBullToon 数据"); | ||||
| 	} | ||||
|  | ||||
|     public boolean loadPhoneBoBullToon() { | ||||
|         listPhoneBoBullToon.clear(); | ||||
|         File fBoBullToon = new File(getWorkingFolder(), "bobulltoon"); | ||||
|         File fBoBullToon = getBoBullToonDataFolder(); | ||||
|         if (fBoBullToon.exists()) { | ||||
|             LogUtils.d(TAG, String.format("getWorkingFolder() %s", getWorkingFolder())); | ||||
|             LogUtils.d(TAG, String.format("getBoBullToonDataFolder() %s", getWorkingFolder())); | ||||
|             for (File userFolder : fBoBullToon.listFiles()) { | ||||
|                 if (userFolder.isDirectory()) { | ||||
|                     for (File recordFile : userFolder.listFiles()) { | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.contacts.dun; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/02/21 06:15:10 | ||||
|  * @Describe 云盾防御规则 | ||||
|  */ | ||||
| @@ -145,6 +145,14 @@ public class Rules { | ||||
|             LogUtils.d(TAG, String.format("isDefend == %s\nisConnect == %s", isDefend, isConnect)); | ||||
|         } | ||||
|  | ||||
|         // 检验拨不通号码群 | ||||
|         if (!isDefend && MainService.isPhoneInBoBullToon(phoneNumber)) { | ||||
|             LogUtils.d(TAG, String.format("PhoneNumber %s\n Is In BoBullToon", phoneNumber)); | ||||
|             isDefend = true; | ||||
|             isConnect = false; | ||||
|             LogUtils.d(TAG, String.format("isDefend == %s\nisConnect == %s", isDefend, isConnect)); | ||||
|         } | ||||
|  | ||||
|         // 查询通讯录是否有该联系人 | ||||
|         boolean isPhoneInContacts = ContactUtils.getInstance(mContext).isPhoneInContacts(mContext, phoneNumber); | ||||
|         if (!isDefend) { | ||||
| @@ -158,14 +166,6 @@ public class Rules { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // 检验拨不通号码群 | ||||
|         if (!isDefend && MainService.isPhoneInBoBullToon(phoneNumber)) { | ||||
|             LogUtils.d(TAG, String.format("PhoneNumber %s\n Is In BoBullToon", phoneNumber)); | ||||
|             isDefend = true; | ||||
|             isConnect = false; | ||||
|             LogUtils.d(TAG, String.format("isDefend == %s\nisConnect == %s", isDefend, isConnect)); | ||||
|         } | ||||
|  | ||||
|         // 正则匹配规则名单校验 | ||||
|         if (!isDefend) { | ||||
|             for (int i = 0; i < _PhoneConnectRuleModelList.size(); i++) { | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.contacts.fragments; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/02/20 12:57:00 | ||||
|  * @Describe 拨号 | ||||
|  */ | ||||
| @@ -161,4 +161,12 @@ public class CallLogFragment extends Fragment { | ||||
|             _CallLogFragment.triggerUpdate(); | ||||
|         } | ||||
|     } | ||||
| 	 | ||||
| 	@Override | ||||
| 	public void onResume() { | ||||
| 		super.onResume(); | ||||
| 		//ToastUtils.show("onResume"); | ||||
| 		callLogAdapter.relaodContacts(); | ||||
| 		readCallLog();  // 窗口回显时更新通话记录 | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -1,15 +1,18 @@ | ||||
| package cc.winboll.studio.contacts.fragments; | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Date 2025/02/20 12:57:50 | ||||
|  * @Describe 联系人 | ||||
|  * @Author ZhanGSKen&豆包大模型<zhangsken@qq.com> | ||||
|  * @Date 2025/08/30 14:32 | ||||
|  * @Describe 联系人视图 | ||||
|  */ | ||||
| import android.Manifest; | ||||
| import android.content.Intent; | ||||
| import android.content.pm.PackageManager; | ||||
| import android.database.Cursor; | ||||
| import android.os.Bundle; | ||||
| import android.os.Handler; | ||||
| import android.os.Looper; | ||||
| import android.provider.ContactsContract; | ||||
| import android.text.Editable; | ||||
| import android.text.TextWatcher; | ||||
| @@ -27,24 +30,39 @@ import androidx.recyclerview.widget.RecyclerView; | ||||
| import cc.winboll.studio.contacts.R; | ||||
| import cc.winboll.studio.contacts.adapters.ContactAdapter; | ||||
| import cc.winboll.studio.contacts.beans.ContactModel; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| import com.hjq.toast.ToastUtils; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
|  | ||||
| import java.util.concurrent.ExecutorService; | ||||
| import java.util.concurrent.Executors; | ||||
|  | ||||
| public class ContactsFragment extends Fragment { | ||||
|  | ||||
|     public static final String TAG = "ContactsFragment"; | ||||
|  | ||||
|     private static final String ARG_PAGE = "ARG_PAGE"; | ||||
|     private int mPage; | ||||
|  | ||||
|     private static final int REQUEST_READ_CONTACTS = 1; | ||||
|  | ||||
|     private int mPage; | ||||
|     private RecyclerView recyclerView; | ||||
|     private ContactAdapter contactAdapter; | ||||
|     private List<ContactModel> contactList = new ArrayList<>(); | ||||
|     private List<ContactModel> originalContactList = new ArrayList<>(); | ||||
|     private EditText searchEditText; | ||||
|     private Button btnDial; | ||||
|     private boolean isViewInitialized = false; // 标记视图是否已初始化 | ||||
|  | ||||
|     // 静态缓存:全局复用联系人数据 | ||||
|     private static List<ContactModel> sCachedOriginalList = new ArrayList<ContactModel>(); | ||||
|     private static List<ContactModel> sCachedFilteredList = new ArrayList<ContactModel>(); | ||||
|  | ||||
|     // 当前页面数据容器 | ||||
|     private List<ContactModel> contactList = new ArrayList<ContactModel>(); | ||||
|     private List<ContactModel> originalContactList = new ArrayList<ContactModel>(); | ||||
|  | ||||
|     // 异步工具 | ||||
|     private final ExecutorService executor = Executors.newSingleThreadExecutor(); | ||||
|     private final Handler mainHandler = new Handler(Looper.getMainLooper()); | ||||
|     private boolean isDataLoaded = false; | ||||
|  | ||||
|  | ||||
|     public static ContactsFragment newInstance(int page) { | ||||
|         Bundle args = new Bundle(); | ||||
| @@ -65,103 +83,272 @@ public class ContactsFragment extends Fragment { | ||||
|     @Nullable | ||||
|     @Override | ||||
|     public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { | ||||
|         return inflater.inflate(R.layout.fragment_contacts, container, false); | ||||
|         // 加载布局(已移除进度条相关代码) | ||||
|         View view = inflater.inflate(R.layout.fragment_contacts, container, false); | ||||
|         return view; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { | ||||
|         super.onViewCreated(view, savedInstanceState); | ||||
|         recyclerView = view.findViewById(R.id.contacts_recycler_view); | ||||
|         // 初始化RecyclerView | ||||
|         recyclerView = (RecyclerView) view.findViewById(R.id.contacts_recycler_view); | ||||
|         recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); | ||||
|         contactAdapter = new ContactAdapter(contactList); | ||||
|         contactList = new ArrayList<ContactModel>(); | ||||
|         contactAdapter = new ContactAdapter(getContext(), contactList); | ||||
|         recyclerView.setAdapter(contactAdapter); | ||||
|         // 初始隐藏列表,数据加载后显示 | ||||
|         recyclerView.setVisibility(View.GONE); | ||||
|  | ||||
|         searchEditText = view.findViewById(R.id.search_edit_text); | ||||
|         searchEditText.addTextChangedListener(new TextWatcher() { | ||||
|                 @Override | ||||
|                 public void beforeTextChanged(CharSequence s, int start, int count, int after) { | ||||
|                 } | ||||
|         // 绑定搜索框和拨号按钮 | ||||
|         searchEditText = (EditText) view.findViewById(R.id.search_edit_text); | ||||
|         btnDial = (Button) view.findViewById(R.id.btn_dial); | ||||
|         // 初始隐藏搜索相关控件,延迟到首次可见时显示 | ||||
|         searchEditText.setVisibility(View.GONE); | ||||
|         btnDial.setVisibility(View.GONE); | ||||
|     } | ||||
|  | ||||
|                 @Override | ||||
|                 public void onTextChanged(CharSequence s, int start, int before, int count) { | ||||
|                     filterContacts(s.toString()); | ||||
|                 } | ||||
|     // 首次可见时初始化资源 | ||||
|     @Override | ||||
|     public void onResume() { | ||||
|         super.onResume(); | ||||
|         if (!isViewInitialized) { | ||||
|             initSearchAndDial(); // 初始化搜索和拨号功能 | ||||
|             checkContactPermission(); // 检查权限并加载数据 | ||||
|             isViewInitialized = true; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|                 @Override | ||||
|                 public void afterTextChanged(Editable s) { | ||||
|                 } | ||||
|             }); | ||||
|     // 初始化搜索框和拨号按钮 | ||||
|     private void initSearchAndDial() { | ||||
|         // 显示搜索相关控件 | ||||
|         searchEditText.setVisibility(View.VISIBLE); | ||||
|         btnDial.setVisibility(View.VISIBLE); | ||||
|  | ||||
|         // 搜索框防抖监听 | ||||
|         searchEditText.addTextChangedListener(new DebounceTextWatcher(300) { | ||||
| 				@Override | ||||
| 				public void onDebounceTextChanged(String query) { | ||||
| 					filterContacts(query); | ||||
| 				} | ||||
| 			}); | ||||
|  | ||||
|         // 拨号按钮点击事件 | ||||
|         btnDial.setOnClickListener(new View.OnClickListener() { | ||||
| 				@Override | ||||
| 				public void onClick(View v) { | ||||
| 					String phoneNumber = searchEditText.getText().toString().replaceAll("\\s", ""); | ||||
| 					if (phoneNumber.isEmpty()) { | ||||
| 						ToastUtils.show("请输入号码"); | ||||
| 						return; | ||||
| 					} | ||||
| 					Intent intent = new Intent(Intent.ACTION_CALL); | ||||
| 					intent.setData(android.net.Uri.parse("tel:" + phoneNumber)); | ||||
| 					intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||||
| 					startActivity(intent); | ||||
| 				} | ||||
| 			}); | ||||
|     } | ||||
|  | ||||
|     // 权限检查 | ||||
|     private void checkContactPermission() { | ||||
|         if (ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { | ||||
|             ActivityCompat.requestPermissions(requireActivity(), new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_READ_CONTACTS); | ||||
|         } else { | ||||
|             readContacts(); | ||||
|             loadContacts(); | ||||
|         } | ||||
|  | ||||
|         Button btnDial = view.findViewById(R.id.btn_dial); | ||||
|         btnDial.setOnClickListener(new View.OnClickListener(){ | ||||
|                 @Override | ||||
|                 public void onClick(View p1) { | ||||
|  | ||||
|                     String phoneNumber = searchEditText.getText().toString().replaceAll("\\s", ""); | ||||
|                     //phoneNumber = "+8616769764848"; | ||||
|                     ToastUtils.show(phoneNumber); | ||||
|                     Intent intent = new Intent(Intent.ACTION_CALL); | ||||
|                     intent.setData(android.net.Uri.parse("tel:" + phoneNumber)); | ||||
|                     // 添加 FLAG_ACTIVITY_NEW_TASK 标志 | ||||
|                     intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||||
|                     startActivity(intent); | ||||
|                 } | ||||
|             }); | ||||
|     } | ||||
|  | ||||
|     // 加载联系人(延迟到首次可见时) | ||||
|     private void loadContacts() { | ||||
|         // 若有缓存,直接复用 | ||||
|         if (!sCachedOriginalList.isEmpty() && !sCachedFilteredList.isEmpty()) { | ||||
|             originalContactList.clear(); | ||||
|             originalContactList.addAll(sCachedOriginalList); | ||||
|             contactList.clear(); | ||||
|             contactList.addAll(sCachedFilteredList); | ||||
|             contactAdapter.notifyDataSetChanged(); | ||||
|             recyclerView.setVisibility(View.VISIBLE); // 显示列表 | ||||
|             isDataLoaded = true; | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // 无缓存时异步加载 | ||||
|         if (!isDataLoaded) { | ||||
|             recyclerView.setVisibility(View.GONE); // 加载中隐藏列表 | ||||
|  | ||||
|             executor.execute(new Runnable() { | ||||
| 					@Override | ||||
| 					public void run() { | ||||
| 						// 子线程读取联系人 | ||||
| 						final List<ContactModel> tempList = readContactsInBackground(); | ||||
|  | ||||
| 						// 主线程更新UI | ||||
| 						mainHandler.post(new Runnable() { | ||||
| 								@Override | ||||
| 								public void run() { | ||||
| 									// 更新缓存 | ||||
| 									sCachedOriginalList.clear(); | ||||
| 									sCachedOriginalList.addAll(tempList); | ||||
| 									sCachedFilteredList.clear(); | ||||
| 									sCachedFilteredList.addAll(tempList); | ||||
|  | ||||
| 									// 更新当前列表 | ||||
| 									originalContactList.clear(); | ||||
| 									originalContactList.addAll(sCachedOriginalList); | ||||
| 									contactList.clear(); | ||||
| 									contactList.addAll(sCachedFilteredList); | ||||
| 									contactAdapter.notifyDataSetChanged(); | ||||
| 									LogUtils.d(TAG, String.format("联系人加载完成,共%d条数据", contactList.size())); | ||||
|  | ||||
| 									// 数据加载后显示列表 | ||||
| 									recyclerView.setVisibility(View.VISIBLE); | ||||
| 									isDataLoaded = true; | ||||
| 								} | ||||
| 							}); | ||||
| 					} | ||||
| 				}); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // 子线程读取联系人 | ||||
|     private List<ContactModel> readContactsInBackground() { | ||||
|         List<ContactModel> tempList = new ArrayList<ContactModel>(); | ||||
|         Cursor cursor = null; | ||||
|         try { | ||||
|             // 查询联系人姓名和号码 | ||||
|             cursor = requireContext().getContentResolver().query( | ||||
|                 ContactsContract.CommonDataKinds.Phone.CONTENT_URI, | ||||
|                 new String[]{ | ||||
|                     ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, | ||||
|                     ContactsContract.CommonDataKinds.Phone.NUMBER | ||||
|                 }, | ||||
|                 null, | ||||
|                 null, | ||||
|                 ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC" | ||||
|             ); | ||||
|  | ||||
|             if (cursor != null && cursor.moveToFirst()) { | ||||
|                 int nameIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME); | ||||
|                 int numberIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER); | ||||
|  | ||||
|                 do { | ||||
|                     String name = cursor.getString(nameIndex); | ||||
|                     String number = cursor.getString(numberIndex).replaceAll("\\s", ""); // 去除空格 | ||||
|                     tempList.add(new ContactModel(name, number)); | ||||
|                 } while (cursor.moveToNext()); | ||||
|             } | ||||
|         } catch (Exception e) { | ||||
|             LogUtils.d(TAG, "读取联系人失败:" + e); | ||||
|         } finally { | ||||
|             if (cursor != null) { | ||||
|                 cursor.close(); // 关闭游标,避免内存泄漏 | ||||
|             } | ||||
|         } | ||||
|         return tempList; | ||||
|     } | ||||
|  | ||||
|     // 过滤联系人 | ||||
|     private void filterContacts(String query) { | ||||
|         contactList.clear(); | ||||
|         if (query.isEmpty()) { | ||||
|             contactList.addAll(originalContactList); | ||||
|             sCachedFilteredList.clear(); | ||||
|             sCachedFilteredList.addAll(originalContactList); | ||||
|         } else { | ||||
|             String lowerQuery = query.toLowerCase(); | ||||
|             for (ContactModel contact : originalContactList) { | ||||
|                 // 匹配姓名、全拼、简拼、号码 | ||||
|                 boolean matchName = contact.getName().toLowerCase().contains(lowerQuery); | ||||
|                 boolean matchPinyin = contact.getPinyin().toLowerCase().contains(lowerQuery); | ||||
|                 boolean matchFirstLetter = contact.getPinyinFirstLetter().toLowerCase().contains(lowerQuery); | ||||
|                 boolean matchNumber = contact.getNumber().contains(lowerQuery); | ||||
|  | ||||
|                 if (matchName || matchPinyin || matchFirstLetter || matchNumber) { | ||||
|                     contactList.add(contact); | ||||
|                 } | ||||
|             } | ||||
|             sCachedFilteredList.clear(); | ||||
|             sCachedFilteredList.addAll(contactList); | ||||
|         } | ||||
|         contactAdapter.notifyDataSetChanged(); | ||||
|         // 过滤后确保列表可见 | ||||
|         recyclerView.setVisibility(View.VISIBLE); | ||||
|     } | ||||
|  | ||||
|     // 权限回调 | ||||
|     @Override | ||||
|     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { | ||||
|         super.onRequestPermissionsResult(requestCode, permissions, grantResults); | ||||
|         if (requestCode == REQUEST_READ_CONTACTS) { | ||||
|             if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { | ||||
|                 readContacts(); | ||||
|                 loadContacts(); // 授权后加载联系人 | ||||
|             } else { | ||||
|                 ToastUtils.show("请授予联系人权限以查看联系人列表"); | ||||
|                 recyclerView.setVisibility(View.VISIBLE); // 显示空列表 | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void readContacts() { | ||||
|         contactList.clear(); | ||||
|         originalContactList.clear(); | ||||
|         Cursor cursor = requireContext().getContentResolver().query( | ||||
|             ContactsContract.CommonDataKinds.Phone.CONTENT_URI, | ||||
|             null, | ||||
|             null, | ||||
|             null, | ||||
|             ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC"); | ||||
|     // 防抖TextWatcher(Java 7实现) | ||||
|     public abstract static class DebounceTextWatcher implements TextWatcher { | ||||
|         private final long debounceDelay; | ||||
|         private Handler handler = new Handler(Looper.getMainLooper()); | ||||
|         private Runnable pendingRunnable; | ||||
|  | ||||
|         if (cursor != null) { | ||||
|             while (cursor.moveToNext()) { | ||||
|                 String name = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)); | ||||
|                 String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)); | ||||
|                 ContactModel contact = new ContactModel(name, number); | ||||
|                 contactList.add(contact); | ||||
|                 originalContactList.add(contact); | ||||
|             } | ||||
|             cursor.close(); | ||||
|             contactAdapter.notifyDataSetChanged(); | ||||
|         public DebounceTextWatcher(long debounceDelay) { | ||||
|             this.debounceDelay = debounceDelay; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void filterContacts(String query) { | ||||
|         contactList.clear(); | ||||
|         if (query.isEmpty()) { | ||||
|             contactList.addAll(originalContactList); | ||||
|         } else { | ||||
|             for (ContactModel contact : originalContactList) { | ||||
|                 if (contact.getName().toLowerCase().contains(query.toLowerCase()) || | ||||
|                     contact.getPinyin().toLowerCase().contains(query.toLowerCase()) || | ||||
|                     contact.getNumber().toLowerCase().contains(query.toLowerCase())) { | ||||
|                     contactList.add(contact); | ||||
|         @Override | ||||
|         public void beforeTextChanged(CharSequence s, int start, int count, int after) { | ||||
|             // 无需处理 | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void onTextChanged(final CharSequence s, int start, int before, int count) { | ||||
|             // 移除之前的延迟任务 | ||||
|             if (pendingRunnable != null) { | ||||
|                 handler.removeCallbacks(pendingRunnable); | ||||
|             } | ||||
|             // 延迟执行过滤 | ||||
|             pendingRunnable = new Runnable() { | ||||
|                 @Override | ||||
|                 public void run() { | ||||
|                     onDebounceTextChanged(s.toString()); | ||||
|                 } | ||||
|             } | ||||
|             }; | ||||
|             handler.postDelayed(pendingRunnable, debounceDelay); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void afterTextChanged(Editable s) { | ||||
|             // 无需处理 | ||||
|         } | ||||
|  | ||||
|         // 抽象方法:防抖后的回调 | ||||
|         public abstract void onDebounceTextChanged(String query); | ||||
|     } | ||||
|  | ||||
|     // 资源释放 | ||||
|     @Override | ||||
|     public void onDestroy() { | ||||
|         super.onDestroy(); | ||||
|         executor.shutdown(); // 关闭线程池 | ||||
|         mainHandler.removeCallbacksAndMessages(null); // 清除未执行任务 | ||||
|     } | ||||
|  | ||||
|     // Fragment隐藏/显示时的处理 | ||||
|     @Override | ||||
|     public void onHiddenChanged(boolean hidden) { | ||||
|         super.onHiddenChanged(hidden); | ||||
|         if (!hidden && isDataLoaded) { | ||||
|             // 复用缓存数据并显示列表 | ||||
|             contactList.clear(); | ||||
|             contactList.addAll(sCachedFilteredList); | ||||
|             contactAdapter.notifyDataSetChanged(); | ||||
|             recyclerView.setVisibility(View.VISIBLE); | ||||
|         } | ||||
|         contactAdapter.notifyDataSetChanged(); | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.contacts.fragments; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/02/20 12:58:15 | ||||
|  * @Describe 应用日志 | ||||
|  */ | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.contacts.handlers; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/02/14 03:51:40 | ||||
|  */ | ||||
| import android.os.Handler; | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.contacts.receivers; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/02/13 06:58:04 | ||||
|  * @Describe 主要广播接收器 | ||||
|  */ | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.contacts.services; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/02/14 03:38:31 | ||||
|  * @Describe 守护进程服务 | ||||
|  */ | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.contacts.services; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/02/13 06:56:41 | ||||
|  * @Describe 拨号主服务 | ||||
|  * 参考: | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.contacts.threads; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/02/14 03:46:44 | ||||
|  */ | ||||
| import android.content.Context; | ||||
|   | ||||
| @@ -0,0 +1,270 @@ | ||||
| package cc.winboll.studio.contacts.utils; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen&豆包大模型<zhangsken@qq.com> | ||||
|  * @Date 2025/09/27 14:27 | ||||
|  * @Describe 调用应用属性设置页工具类 | ||||
|  *  来源:https://blog.csdn.net/zhuhai__yizhi/article/details/78737593 | ||||
|  *  Created by zyy on 2018/3/12. | ||||
|  *  直接跳转到权限后返回,可以监控权限授权情况,但是,跳转到应用详情页,无法监测权限情况 | ||||
|  *  是否要加以区分,若是应用详情页,则跳转回来后,onRestart检测所求权限,如果授权,则收回提示,如果没授权,则继续提示 | ||||
|  */ | ||||
| import android.app.Activity; | ||||
| import android.content.ComponentName; | ||||
| import android.content.Intent; | ||||
| import android.net.Uri; | ||||
| import android.os.Build; | ||||
| import android.provider.Settings; | ||||
| import cc.winboll.studio.contacts.MainActivity; | ||||
|  | ||||
| public class AppGoToSettingsUtil { | ||||
|  | ||||
|     public static final String TAG = "AppGoToSettingsUtil"; | ||||
|  | ||||
|     public static final int ACTIVITY_RESULT_APP_SETTINGS = MainActivity.REQUEST_APP_SETTINGS; | ||||
|  | ||||
|     /** | ||||
|      * Build.MANUFACTURER判断各大手机厂商品牌 | ||||
|      */ | ||||
|     private static final String MANUFACTURER_HUAWEI = "Huawei";//华为 | ||||
|     private static final String MANUFACTURER_MEIZU = "Meizu";//魅族 | ||||
|     private static final String MANUFACTURER_XIAOMI = "Xiaomi";//小米 | ||||
|     private static final String MANUFACTURER_SONY = "Sony";//索尼 | ||||
|     private static final String MANUFACTURER_OPPO = "OPPO"; | ||||
|     private static final String MANUFACTURER_LG = "LG"; | ||||
|     private static final String MANUFACTURER_VIVO = "vivo"; | ||||
|     private static final String MANUFACTURER_SAMSUNG = "samsung";//三星 | ||||
|     private static final String MANUFACTURER_LETV = "Letv";//乐视 | ||||
|     private static final String MANUFACTURER_ZTE = "ZTE";//中兴 | ||||
|     private static final String MANUFACTURER_YULONG = "YuLong";//酷派 | ||||
|     private static final String MANUFACTURER_LENOVO = "LENOVO";//联想 | ||||
|  | ||||
|     public static boolean isAppSettingOpen=false; | ||||
|     /** | ||||
|      * 跳转到相应品牌手机系统权限设置页,如果跳转不成功,则跳转到应用详情页 | ||||
|      * 这里需要改造成返回true或者false,应用详情页:true,应用权限页:false | ||||
|      * @param activity | ||||
|      */ | ||||
|     public static void GoToSetting(Activity activity) { | ||||
|         switch (Build.MANUFACTURER) { | ||||
|             case MANUFACTURER_HUAWEI://华为 | ||||
|                 Huawei(activity); | ||||
|                 break; | ||||
|             case MANUFACTURER_MEIZU://魅族 | ||||
|                 Meizu(activity); | ||||
|                 break; | ||||
|             case MANUFACTURER_XIAOMI://小米 | ||||
|                 Xiaomi(activity); | ||||
|                 break; | ||||
|             case MANUFACTURER_SONY://索尼 | ||||
|                 Sony(activity); | ||||
|                 break; | ||||
|             case MANUFACTURER_OPPO://oppo | ||||
|                 OPPO(activity); | ||||
|                 break; | ||||
|             case MANUFACTURER_LG://lg | ||||
|                 LG(activity); | ||||
|                 break; | ||||
|             case MANUFACTURER_LETV://乐视 | ||||
|                 Letv(activity); | ||||
|                 break; | ||||
|             default://其他 | ||||
|                 try {//防止应用详情页也找不到,捕获异常后跳转到设置,这里跳转最好是两级,太多用户也会觉得麻烦,还不如不跳 | ||||
|                     openAppDetailSetting(activity); | ||||
|                     //activity.startActivityForResult(getAppDetailSettingIntent(activity), PERMISSION_SETTING_FOR_RESULT); | ||||
|                 } catch (Exception e) { | ||||
|                     SystemConfig(activity); | ||||
|                 } | ||||
|                 break; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 华为跳转权限设置页 | ||||
|      * @param activity | ||||
|      */ | ||||
|     public static void Huawei(Activity activity) { | ||||
|         try { | ||||
|             Intent intent = new Intent(); | ||||
|             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||||
|             intent.putExtra("packageName", activity.getPackageName()); | ||||
|             ComponentName comp = new ComponentName("com.huawei.systemmanager", "com.huawei.permissionmanager.ui.MainActivity"); | ||||
|             intent.setComponent(comp); | ||||
|             activity.startActivityForResult(intent, ACTIVITY_RESULT_APP_SETTINGS); | ||||
|             isAppSettingOpen = false; | ||||
|         } catch (Exception e) { | ||||
|             openAppDetailSetting(activity); | ||||
|             //activity.startActivityForResult(getAppDetailSettingIntent(activity), PERMISSION_SETTING_FOR_RESULT); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 魅族跳转权限设置页,测试时,点击无反应,具体原因不明 | ||||
|      * @param activity | ||||
|      */ | ||||
|     public static void Meizu(Activity activity) { | ||||
|         try { | ||||
|             Intent intent = new Intent("com.meizu.safe.security.SHOW_APPSEC"); | ||||
|             intent.addCategory(Intent.CATEGORY_DEFAULT); | ||||
|             intent.putExtra("packageName", activity.getPackageName()); | ||||
|             activity.startActivity(intent); | ||||
|             isAppSettingOpen = false; | ||||
|         } catch (Exception e) { | ||||
|             openAppDetailSetting(activity); | ||||
|             //activity.startActivityForResult(getAppDetailSettingIntent(activity), PERMISSION_SETTING_FOR_RESULT); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 小米,功能正常 | ||||
|      * @param activity | ||||
|      */ | ||||
|     public static void Xiaomi(Activity activity) { | ||||
|         try { //MIUI 8 9 | ||||
|             Intent localIntent = new Intent("miui.intent.action.APP_PERM_EDITOR"); | ||||
|             localIntent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.PermissionsEditorActivity"); | ||||
|             localIntent.putExtra("extra_pkgname", activity.getPackageName()); | ||||
|             activity.startActivityForResult(localIntent, ACTIVITY_RESULT_APP_SETTINGS); | ||||
|             isAppSettingOpen = false; | ||||
|             //activity.startActivity(localIntent); | ||||
|         } catch (Exception e) { | ||||
|             try { //MIUI 5/6/7 | ||||
|                 Intent localIntent = new Intent("miui.intent.action.APP_PERM_EDITOR"); | ||||
|                 localIntent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.AppPermissionsEditorActivity"); | ||||
|                 localIntent.putExtra("extra_pkgname", activity.getPackageName()); | ||||
|                 activity.startActivityForResult(localIntent, ACTIVITY_RESULT_APP_SETTINGS); | ||||
|                 isAppSettingOpen = false; | ||||
|                 //activity.startActivity(localIntent); | ||||
|             } catch (Exception e1) { //否则跳转到应用详情 | ||||
|                 openAppDetailSetting(activity); | ||||
|                 //activity.startActivityForResult(getAppDetailSettingIntent(activity), PERMISSION_SETTING_FOR_RESULT); | ||||
|                 //这里有个问题,进入活动后需要再跳一级活动,就检测不到返回结果 | ||||
|                 //activity.startActivity(getAppDetailSettingIntent()); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 索尼,6.0以上的手机非常少,基本没看见 | ||||
|      * @param activity | ||||
|      */ | ||||
|     public static void Sony(Activity activity) { | ||||
|         try { | ||||
|             Intent intent = new Intent(); | ||||
|             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||||
|             intent.putExtra("packageName", activity.getPackageName()); | ||||
|             ComponentName comp = new ComponentName("com.sonymobile.cta", "com.sonymobile.cta.SomcCTAMainActivity"); | ||||
|             intent.setComponent(comp); | ||||
|             activity.startActivity(intent); | ||||
|             isAppSettingOpen = false; | ||||
|         } catch (Exception e) { | ||||
|             openAppDetailSetting(activity); | ||||
|             //activity.startActivityForResult(getAppDetailSettingIntent(activity), PERMISSION_SETTING_FOR_RESULT); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * OPPO | ||||
|      * @param activity | ||||
|      */ | ||||
|     public static void OPPO(Activity activity) { | ||||
|         try { | ||||
|             Intent intent = new Intent(); | ||||
|             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||||
|             intent.putExtra("packageName", activity.getPackageName()); | ||||
|             ComponentName comp = new ComponentName("com.color.safecenter", "com.color.safecenter.permission.PermissionManagerActivity"); | ||||
|             intent.setComponent(comp); | ||||
|             activity.startActivity(intent); | ||||
|             isAppSettingOpen = false; | ||||
|         } catch (Exception e) { | ||||
|             openAppDetailSetting(activity); | ||||
|             //activity.startActivityForResult(getAppDetailSettingIntent(activity), PERMISSION_SETTING_FOR_RESULT); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * LG经过测试,正常使用 | ||||
|      * @param activity | ||||
|      */ | ||||
|     public static void LG(Activity activity) { | ||||
|         try { | ||||
|             Intent intent = new Intent("android.intent.action.MAIN"); | ||||
|             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||||
|             intent.putExtra("packageName", activity.getPackageName()); | ||||
|             ComponentName comp = new ComponentName("com.android.settings", "com.android.settings.Settings$AccessLockSummaryActivity"); | ||||
|             intent.setComponent(comp); | ||||
|             activity.startActivity(intent); | ||||
|             isAppSettingOpen = false; | ||||
|         } catch (Exception e) { | ||||
|             openAppDetailSetting(activity); | ||||
|             //activity.startActivityForResult(getAppDetailSettingIntent(activity), PERMISSION_SETTING_FOR_RESULT); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 乐视6.0以上很少,基本都可以忽略了,现在乐视手机不多 | ||||
|      * @param activity | ||||
|      */ | ||||
|     public static void Letv(Activity activity) { | ||||
|         try { | ||||
|             Intent intent = new Intent(); | ||||
|             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||||
|             intent.putExtra("packageName", activity.getPackageName()); | ||||
|             ComponentName comp = new ComponentName("com.letv.android.letvsafe", "com.letv.android.letvsafe.PermissionAndApps"); | ||||
|             intent.setComponent(comp); | ||||
|             activity.startActivity(intent); | ||||
|             isAppSettingOpen = false; | ||||
|         } catch (Exception e) { | ||||
|             openAppDetailSetting(activity); | ||||
|             //activity.startActivityForResult(getAppDetailSettingIntent(activity), PERMISSION_SETTING_FOR_RESULT); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 只能打开到自带安全软件 | ||||
|      * @param activity | ||||
|      */ | ||||
|     public static void _360(Activity activity) { | ||||
|         try { | ||||
|             Intent intent = new Intent("android.intent.action.MAIN"); | ||||
|             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||||
|             intent.putExtra("packageName", activity.getPackageName()); | ||||
|             ComponentName comp = new ComponentName("com.qihoo360.mobilesafe", "com.qihoo360.mobilesafe.ui.index.AppEnterActivity"); | ||||
|             intent.setComponent(comp); | ||||
|             activity.startActivity(intent); | ||||
|         } catch (Exception e) { | ||||
|             openAppDetailSetting(activity); | ||||
|             //activity.startActivityForResult(getAppDetailSettingIntent(activity), PERMISSION_SETTING_FOR_RESULT); | ||||
|         } | ||||
|     } | ||||
|     /** | ||||
|      * 系统设置界面 | ||||
|      * @param activity | ||||
|      */ | ||||
|     public static void SystemConfig(Activity activity) { | ||||
|         Intent intent = new Intent(Settings.ACTION_SETTINGS); | ||||
|         activity.startActivity(intent); | ||||
|     } | ||||
|     /** | ||||
|      * 获取应用详情页面 | ||||
|      * @return | ||||
|      */ | ||||
|     private static Intent getAppDetailSettingIntent(Activity activity) { | ||||
|         Intent localIntent = new Intent(); | ||||
|         localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||||
|         //if (Build.VERSION.SDK_INT >= 9) { | ||||
|         localIntent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS"); | ||||
|         localIntent.setData(Uri.fromParts("package", activity.getPackageName(), null)); | ||||
|         /*} else if (Build.VERSION.SDK_INT <= 8) { | ||||
|          localIntent.setAction(Intent.ACTION_VIEW); | ||||
|          localIntent.setClassName("com.android.settings", "com.android.settings.InstalledAppDetails"); | ||||
|          localIntent.putExtra("com.android.settings.ApplicationPkgName", activity.getPackageName()); | ||||
|          }*/ | ||||
|         return localIntent; | ||||
|     } | ||||
|  | ||||
|     public static void openAppDetailSetting(Activity activity) { | ||||
|         activity.startActivityForResult(getAppDetailSettingIntent(activity), ACTIVITY_RESULT_APP_SETTINGS); | ||||
|         isAppSettingOpen = true; | ||||
|     } | ||||
| } | ||||
| @@ -1,9 +1,9 @@ | ||||
| package cc.winboll.studio.contacts.utils; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Date 2025/03/06 21:08:16 | ||||
|  * @Describe ContactUtils | ||||
|  * @Author ZhanGSKen&豆包大模型<zhangsken@qq.com> | ||||
|  * @Date 2025/08/30 14:32 | ||||
|  * @Describe 联系人工具集 | ||||
|  */ | ||||
| import android.content.ContentResolver; | ||||
| import android.content.Context; | ||||
|   | ||||
| @@ -3,7 +3,7 @@ import android.widget.EditText; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/04/13 00:59:13 | ||||
|  * @Describe Int类型数字输入框工具集 | ||||
|  */ | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.contacts.utils; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/04/13 01:16:28 | ||||
|  * @Describe Int数字操作工具集 | ||||
|  */ | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cc.winboll.studio.contacts.utils; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@188.com> | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/02/26 15:21:48 | ||||
|  * @Describe PhoneUtils | ||||
|  */ | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user