拷贝APP_Bck20250119项目源码,移除libjc/jcc/libs/android-29.jar文件。
This commit is contained in:
		
							
								
								
									
										1
									
								
								winboll-shared/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								winboll-shared/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| /build | ||||
							
								
								
									
										46
									
								
								winboll-shared/build.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								winboll-shared/build.gradle
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| apply plugin: 'com.android.library' | ||||
| apply plugin: 'maven-publish' | ||||
| apply from: '../.winboll/winboll_lib_build.gradle' | ||||
| apply from: '../.winboll/winboll_lint_build.gradle' | ||||
|  | ||||
| android { | ||||
|     namespace 'cc.winboll.studio' | ||||
|      | ||||
|     compileSdkVersion 32 | ||||
|     buildToolsVersion "33.0.3" | ||||
|  | ||||
|     defaultConfig { | ||||
|         minSdkVersion 21 | ||||
|         targetSdkVersion 30 | ||||
|     } | ||||
|     buildTypes { | ||||
|         release { | ||||
|             minifyEnabled false | ||||
|             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | ||||
|         } | ||||
|     } | ||||
|     compileOptions { | ||||
|         sourceCompatibility JavaVersion.VERSION_11 | ||||
|         targetCompatibility JavaVersion.VERSION_11 | ||||
|     } | ||||
| } | ||||
|  | ||||
| dependencies { | ||||
|     // https://mvnrepository.com/artifact/com.google.zxing/core | ||||
|     api 'com.google.zxing:core:3.4.1' | ||||
|     // https://mvnrepository.com/artifact/com.google.zxing/javase | ||||
|     api 'com.google.zxing:javase:3.4.1' | ||||
|  | ||||
|  | ||||
|     api 'io.github.medyo:android-about-page:2.0.0' | ||||
|     api 'com.github.getActivity:ToastUtils:10.5' | ||||
|     api 'com.jcraft:jsch:0.1.55' | ||||
|     api 'org.jsoup:jsoup:1.13.1' | ||||
|     api 'com.squareup.okhttp3:okhttp:4.4.1' | ||||
|      | ||||
|     api 'androidx.appcompat:appcompat:1.0.0' | ||||
|     api 'androidx.fragment:fragment:1.0.0' | ||||
|     api 'com.google.android.material:material:1.0.0' | ||||
|      | ||||
|     api fileTree(dir: 'libs', include: ['*.jar']) | ||||
| } | ||||
							
								
								
									
										8
									
								
								winboll-shared/build.properties
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								winboll-shared/build.properties
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| #Created by .winboll/winboll_app_build.gradle | ||||
| #Fri Nov 29 11:55:20 GMT 2024 | ||||
| stageCount=0 | ||||
| libraryProject=winboll-shared | ||||
| baseVersion=1.8 | ||||
| publishVersion=1.8.16 | ||||
| buildCount=0 | ||||
| baseBetaVersion=1.8.17 | ||||
							
								
								
									
										17
									
								
								winboll-shared/proguard-rules.pro
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								winboll-shared/proguard-rules.pro
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| # Add project specific ProGuard rules here. | ||||
| # By default, the flags in this file are appended to flags specified | ||||
| # in C:/tools/adt-bundle-windows-x86_64-20131030/sdk/tools/proguard/proguard-android.txt | ||||
| # You can edit the include path and order by changing the proguardFiles | ||||
| # directive in build.gradle. | ||||
| # | ||||
| # For more details, see | ||||
| #   http://developer.android.com/guide/developing/tools/proguard.html | ||||
|  | ||||
| # Add any project specific keep options here: | ||||
|  | ||||
| # If your project uses WebView with JS, uncomment the following | ||||
| # and specify the fully qualified class name to the JavaScript interface | ||||
| # class: | ||||
| #-keepclassmembers class fqcn.of.javascript.interface.for.webview { | ||||
| #   public *; | ||||
| #} | ||||
							
								
								
									
										51
									
								
								winboll-shared/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								winboll-shared/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | ||||
| <?xml version='1.0' encoding='utf-8'?> | ||||
| <manifest | ||||
|     xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     package="cc.winboll.studio"> | ||||
|  | ||||
|     <!-- 此应用可显示在其他应用上方 --> | ||||
|     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> | ||||
|  | ||||
|     <!-- 对正在运行的应用重新排序 --> | ||||
|     <uses-permission android:name="android.permission.REORDER_TASKS"/> | ||||
|  | ||||
|     <!-- MOVE_TASK_TO_FRONT --> | ||||
|     <uses-permission android:name="android.permission.MOVE_TASK_TO_FRONT"/> | ||||
|  | ||||
|     <!-- 检索正在运行的应用 --> | ||||
|     <uses-permission android:name="android.permission.GET_TASKS"/> | ||||
|  | ||||
|     <application android:networkSecurityConfig="@xml/network_security_config"> | ||||
|  | ||||
|         <activity | ||||
|             android:name=".shared.app.CrashHandler$CrashActiviy" | ||||
|             android:label="CrashActiviy" | ||||
|             android:launchMode="standard"/> | ||||
|  | ||||
|         <activity | ||||
|             android:name=".shared.log.LogActivity" | ||||
|             android:label="LogActivity" | ||||
|             android:launchMode="standard"/> | ||||
|  | ||||
|         <activity | ||||
|             android:name=".activities.AboutActivity" | ||||
|             android:label="AboutActivity"/> | ||||
|  | ||||
|         <activity android:name=".activities.HelpActivity" | ||||
|             android:label="HelpActivity"/> | ||||
|  | ||||
|         <service android:name="cc.winboll.studio.shared.service.WinBollClientService"/> | ||||
|  | ||||
|         <service android:name="cc.winboll.studio.shared.service.AssistantService"/> | ||||
|  | ||||
|         <service android:name="cc.winboll.studio.shared.service.WinBollMail"/> | ||||
|  | ||||
|         <activity android:name="cc.winboll.studio.unittest.UnitTestActivity"/> | ||||
|  | ||||
|         <activity android:name="cc.winboll.studio.shared.activities.AboutActivity"/> | ||||
|  | ||||
|         <activity android:name="cc.winboll.studio.shared.activities.HelpActivity"/> | ||||
|  | ||||
|     </application> | ||||
|  | ||||
| </manifest> | ||||
							
								
								
									
										241
									
								
								winboll-shared/src/main/assets/winboll/studio/html/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										241
									
								
								winboll-shared/src/main/assets/winboll/studio/html/index.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,241 @@ | ||||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
|   <!-- | ||||
|     JavaScript实现在HTML中的粒子文字特效 | ||||
|     来源:https://blog.csdn.net/m0_46700215/article/details/126963561?sharetype=blog&shareId=126963561&sharerefer=APP&sharesource=weixin_38986226 | ||||
|     --> | ||||
|   <head> | ||||
|     <meta charset="UTF-8"> | ||||
|     <meta name="viewport" content="width=device - width, initial - scale = 1.0"> | ||||
|     <title>WinBoll</title> | ||||
|   </head> | ||||
|   <body> | ||||
|     <canvas id="ChangeText">WinBoll Studio</canvas> | ||||
|  | ||||
| <script type="text/javascript"> | ||||
|     const COLOR = "#39BC54"; // 设定粒子特效颜色 | ||||
|     let MESSAGE = document.getElementById("ChangeText").textContent; // 根据标签的ID获取待处理的文字内容 | ||||
|  | ||||
|     let FONT_SIZE = (window.innerWidth * 0.08); // 字体大小 | ||||
|     let AMOUNT = 6000; // 设定粒子数量 | ||||
|     let SIZE = 2; // 粒子大小 | ||||
|     let INITIAL_DISPLACEMENT = 500; // 最初位移量 | ||||
|     const INITIAL_VELOCITY = 7.5; // 最初速度 | ||||
|     const VELOCITY_RETENTION = 0.95; // 速度保持 | ||||
|     let SETTLE_SPEED = 1; // 稳定速度 | ||||
|     const FLEE_SPEED = 2; // 逃逸速度 | ||||
|     const FLEE_DISTANCE = 50; // 逃逸距离 | ||||
|     let FLEE = true; // 逃逸模式 | ||||
|     let SCATTER_VELOCITY = 3; // 散射速度 | ||||
|     const SCATTER = true; // 散射模式 | ||||
|  | ||||
|     // 若处于移动设备展示 | ||||
|     if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) { | ||||
|         // Mobile | ||||
|         MESSAGE = document.getElementById("ChangeText").textContent; // 通过标签ID获取文本内容 | ||||
|  | ||||
|         FONT_SIZE = 50;  // 字体大小减小 | ||||
|         AMOUNT = 2000; // 粒子数量减少 | ||||
|         SIZE = 1; | ||||
|         INITIAL_DISPLACEMENT = 100; // 最初位移量减少 | ||||
|         SETTLE_SPEED = 1; // 最初速度减少 | ||||
|         FLEE = false; // 关闭逃逸模式 | ||||
|         SCATTER_VELOCITY = 2; // 散射速度 | ||||
|     } | ||||
|  | ||||
|     const canvas = document.getElementById("ChangeText"); | ||||
|     const ctx = canvas.getContext("2d"); // 创建画布 | ||||
|  | ||||
|     let POINTS = []; | ||||
|     const MOUSE = { | ||||
|         x: 0, | ||||
|         y: 0 | ||||
|     }; | ||||
|  | ||||
|     function Point(x, y, r, g, b, a) { | ||||
|         const angle = Math.random() * 6.28; | ||||
|         this.x = canvas.width / 2 - x + (Math.random() - 0.5) * INITIAL_DISPLACEMENT; | ||||
|         this.y = canvas.height / 2 - y + (Math.random() - 0.5) * INITIAL_DISPLACEMENT; | ||||
|         this.velx = INITIAL_VELOCITY * Math.cos(angle); | ||||
|         this.vely = INITIAL_VELOCITY * Math.sin(angle); | ||||
|         this.target_x = canvas.width / 2 - x; | ||||
|         this.target_y = canvas.height / 2 - y; | ||||
|         this.r = r; | ||||
|         this.g = g; | ||||
|         this.b = b; | ||||
|         this.a = a; | ||||
|  | ||||
|         this.getX = function () { | ||||
|             return this.x; | ||||
|         } | ||||
|  | ||||
|         this.getY = function () { | ||||
|             return this.y; | ||||
|         } | ||||
|         this.fleeFrom = function () { | ||||
|             this.velx -= ((MOUSE.x - this.x) * FLEE_SPEED / 10); | ||||
|             this.vely -= ((MOUSE.y - this.y) * FLEE_SPEED / 10); | ||||
|         } | ||||
|  | ||||
|         this.settleTo = function () { | ||||
|             this.velx += ((this.target_x - this.x) * SETTLE_SPEED / 100); | ||||
|             this.vely += ((this.target_y - this.y) * SETTLE_SPEED / 100); | ||||
|             this.velx -= this.velx * (1 - VELOCITY_RETENTION); | ||||
|             this.vely -= this.vely * (1 - VELOCITY_RETENTION); | ||||
|         } | ||||
|  | ||||
|         this.scatter = function () { | ||||
|             const unit = this.unitVecToMouse(); | ||||
|             const vel = SCATTER_VELOCITY * 10 * (0.5 + Math.random() / 2); | ||||
|             this.velx = -unit.x * vel; | ||||
|             this.vely = -unit.y * vel; | ||||
|         } | ||||
|  | ||||
|         this.move = function () { | ||||
|             if (this.distanceToMouse() <= FLEE_DISTANCE) { | ||||
|                 this.fleeFrom(); | ||||
|             } else { | ||||
|                 this.settleTo(); | ||||
|             } | ||||
|  | ||||
|             if (this.x + this.velx < 0 || this.x + this.velx >= canvas.width) { | ||||
|                 this.velx *= -1; | ||||
|             } | ||||
|             if (this.y + this.vely < 0 || this.y + this.vely >= canvas.height) { | ||||
|                 this.vely *= -1; | ||||
|             } | ||||
|  | ||||
|             this.x += this.velx; | ||||
|             this.y += this.vely; | ||||
|         } | ||||
|         this.distanceToMouse = function () { | ||||
|             return this.distanceTo(MOUSE.x, MOUSE.y); | ||||
|         } | ||||
|  | ||||
|         this.distanceTo = function (x, y) { | ||||
|             return Math.sqrt((x - this.x) * (x - this.x) + (y - this.y) * (y - this.y)); | ||||
|         } | ||||
|         this.unitVecToMouse = function () { | ||||
|             return this.unitVecTo(MOUSE.x, MOUSE.y); | ||||
|         } | ||||
|  | ||||
|         this.unitVecTo = function (x, y) { | ||||
|             const dx = x - this.x; | ||||
|             const dy = y - this.y; | ||||
|             return { | ||||
|                 x: dx / Math.sqrt(dx * dx + dy * dy), | ||||
|                 y: dy / Math.sqrt(dx * dx + dy * dy) | ||||
|             }; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     window.addEventListener("resize", function () { | ||||
|         resizeCanvas() | ||||
|         adjustText() | ||||
|     }); | ||||
|  | ||||
|     if (FLEE) { | ||||
|         window.addEventListener("mousemove", function (event) { | ||||
|             MOUSE.x = event.clientX; | ||||
|             MOUSE.y = event.clientY; | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     if (SCATTER) { | ||||
|         window.addEventListener("click", function (event) { | ||||
|             MOUSE.x = event.clientX; | ||||
|             MOUSE.y = event.clientY; | ||||
|             for (let i = 0; i < POINTS.length; i++) { | ||||
|                 POINTS[i].scatter(); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     function resizeCanvas() { | ||||
|         canvas.width = window.innerWidth; | ||||
|         canvas.height = window.innerHeight; | ||||
|     } | ||||
|  | ||||
|     function adjustText() { | ||||
|         ctx.fillStyle = COLOR; | ||||
|         ctx.textBaseline = "middle"; | ||||
|         ctx.textAlign = "center"; | ||||
|         ctx.font = FONT_SIZE + "px Arial"; | ||||
|         ctx.fillText(MESSAGE, canvas.width / 2, canvas.height / 2); | ||||
|         const textWidth = ctx.measureText(MESSAGE).width; | ||||
|         if (textWidth === 0) { | ||||
|             return; | ||||
|         } | ||||
|         const minX = canvas.width / 2 - textWidth / 2; | ||||
|         const minY = canvas.height / 2 - FONT_SIZE / 2; | ||||
|         const data = ctx.getImageData(minX, minY, textWidth, FONT_SIZE).data; | ||||
|         let isBlank = true; | ||||
|         for (let i = 0; i < data.length; i++) { | ||||
|             if (data[i] !== 0) { | ||||
|                 isBlank = false; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (!isBlank) { | ||||
|             let count = 0; | ||||
|             let curr = 0; | ||||
|             let num = 0; | ||||
|             let x = 0; | ||||
|             let y = 0; | ||||
|             const w = Math.floor(textWidth); | ||||
|             POINTS = []; | ||||
|             while (count < AMOUNT) { | ||||
|                 while (curr === 0) { | ||||
|                     num = Math.floor(Math.random() * data.length); | ||||
|                     curr = data[num]; | ||||
|                 } | ||||
|                 num = Math.floor(num / 4); | ||||
|                 x = w / 2 - num % w; | ||||
|                 y = FONT_SIZE / 2 - Math.floor(num / w); | ||||
|                 POINTS.push(new Point(x, y, data[num * 4], data[num * 4 + 1], data[num * 4 + 2], data[num * 4 + 3])); | ||||
|                 curr = 0; | ||||
|                 count++; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function init() { | ||||
|         resizeCanvas() | ||||
|         adjustText() | ||||
|         window.requestAnimationFrame(animate); | ||||
|     } | ||||
|  | ||||
|     function animate() { | ||||
|         update(); | ||||
|         draw(); | ||||
|     } | ||||
|  | ||||
|     function update() { | ||||
|         let point; | ||||
|         for (let i = 0; i < POINTS.length; i++) { | ||||
|             point = POINTS[i]; | ||||
|             point.move(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function draw() { | ||||
|         ctx.clearRect(0, 0, canvas.width, canvas.height); | ||||
|  | ||||
|         let point; | ||||
|         for (let i = 0; i < POINTS.length; i++) { | ||||
|             point = POINTS[i]; | ||||
|             ctx.fillStyle = "rgba(" + point.r + "," + point.g + "," + point.b + "," + point.a + ")"; | ||||
|             ctx.beginPath(); | ||||
|             ctx.arc(point.getX(), point.getY(), SIZE, 0, 2 * Math.PI); | ||||
|             ctx.fill(); | ||||
|         } | ||||
|  | ||||
|         window.requestAnimationFrame(animate); | ||||
|     } | ||||
|  | ||||
|     init(); | ||||
| </script> | ||||
|   </body> | ||||
| </html> | ||||
|  | ||||
| @@ -0,0 +1,13 @@ | ||||
| package cc.winboll.studio.intent; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/12/24 18:51:20 | ||||
|  */ | ||||
| public class action { | ||||
|      | ||||
|     static final String _PRE = cc.winboll.studio.intent.action.class.getName(); | ||||
|      | ||||
|     public static final String DEBUGVIEW = _PRE + ".DEBUGVIEW"; | ||||
|      | ||||
| } | ||||
| @@ -0,0 +1,87 @@ | ||||
| package cc.winboll.studio.shared.activities; | ||||
|  | ||||
| import android.content.Intent; | ||||
| import android.os.Bundle; | ||||
| import android.view.Menu; | ||||
| import android.view.MenuItem; | ||||
| import androidx.appcompat.widget.Toolbar; | ||||
| import cc.winboll.studio.R; | ||||
| import cc.winboll.studio.shared.app.WinBollActivity; | ||||
| import cc.winboll.studio.shared.app.WinBollActivityManager; | ||||
| import cc.winboll.studio.shared.log.LogUtils; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/07/14 13:20:33 | ||||
|  * @Describe AboutFragment Test | ||||
|  */ | ||||
| final public class AboutActivity extends WinBollActivity { | ||||
|  | ||||
|     public static final String TAG = "AboutActivity"; | ||||
|  | ||||
|  | ||||
|     @Override | ||||
|     public String getTag() { | ||||
|         return TAG; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected boolean isEnableDisplayHomeAsUp() { | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         setContentView(R.layout.activity_about); | ||||
|  | ||||
|         /*AboutView aboutView = findViewById(R.id.activityaboutAboutView1); | ||||
|          aboutView.setOnRequestDevUserInfoAutofillListener(new AboutView.OnRequestDevUserInfoAutofillListener(){ | ||||
|  | ||||
|          @Override | ||||
|          public void requestAutofill(EditText etDevUserName, EditText etDevUserPassword) { | ||||
|          AutofillManager autofillManager = (AutofillManager) getSystemService(AutofillManager.class); | ||||
|          if (autofillManager!= null) { | ||||
|          //ToastUtils.show("0"); | ||||
|          autofillManager.requestAutofill(findViewById(R.id.usernameEditText)); | ||||
|          autofillManager.requestAutofill(findViewById(R.id.passwordEditText)); | ||||
|          } | ||||
|          } | ||||
|          });*/ | ||||
|  | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onPostCreate(Bundle savedInstanceState) { | ||||
|         super.onPostCreate(savedInstanceState); | ||||
|         setSubTitle(TAG); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected boolean isAddWinBollToolBar() { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected Toolbar initToolBar() { | ||||
|         return findViewById(R.id.activityaboutToolbar1); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean onCreateOptionsMenu(Menu menu) { | ||||
|         getMenuInflater().inflate(R.menu.toolbar_winboll_shared_about, menu); | ||||
|         return super.onCreateOptionsMenu(menu); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     @Override | ||||
|     public boolean onOptionsItemSelected(MenuItem item) { | ||||
|         if (item.getItemId() == R.id.item_help) { | ||||
|             WinBollActivityManager.getInstance(this).startWinBollActivity(this, HelpActivity.class); | ||||
|         } | ||||
| //        else if (item.getItemId() == android.R.id.home) { | ||||
| //            WinBollActivityManager.getInstance(this).finish(this); | ||||
| //        } | ||||
|         return super.onOptionsItemSelected(item); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,139 @@ | ||||
| package cc.winboll.studio.shared.activities; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2025/01/03 11:02:49 | ||||
|  * @Describe HelpActivity | ||||
|  */ | ||||
| import android.content.Context; | ||||
| import android.net.Uri; | ||||
| import android.os.Bundle; | ||||
| import android.view.Menu; | ||||
| import android.view.MenuItem; | ||||
| import androidx.appcompat.widget.Toolbar; | ||||
| import androidx.core.content.FileProvider; | ||||
| import cc.winboll.studio.R; | ||||
| import cc.winboll.studio.shared.app.WinBollActivity; | ||||
| import cc.winboll.studio.shared.app.WinBollActivityManager; | ||||
| import cc.winboll.studio.shared.log.LogUtils; | ||||
| import cc.winboll.studio.shared.util.FileUtils; | ||||
| import cc.winboll.studio.shared.view.SimpleWebView; | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
|  | ||||
| public class HelpActivity extends WinBollActivity { | ||||
|  | ||||
|     public static final String TAG = "HelpActivity"; | ||||
|  | ||||
|     String mszHelpIndexFilePath = ""; | ||||
|     Uri mszHelpIndexFileUri; | ||||
|     Context mContext; | ||||
|  | ||||
|     @Override | ||||
|     public String getTag() { | ||||
|         return TAG; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected boolean isEnableDisplayHomeAsUp() { | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     @Override | ||||
|     protected boolean isAddWinBollToolBar() { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected Toolbar initToolBar() { | ||||
|         return findViewById(R.id.activityhelpToolbar1); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean onCreateOptionsMenu(Menu menu) { | ||||
|         return super.onCreateOptionsMenu(menu); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean onOptionsItemSelected(MenuItem item) { | ||||
| //        if (item.getItemId() == android.R.id.home) { | ||||
| //            WinBollActivityManager.getInstance(this).finish(this); | ||||
| //        } | ||||
|         return super.onOptionsItemSelected(item); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         setContentView(R.layout.activity_help); | ||||
|  | ||||
|         // 与其他应用分享 html 帮助 | ||||
|         //FileUtils.shareHtmlFile(this, mszHelpIndexFilePath); | ||||
|  | ||||
|         // 直接读取 assets 文件显示 WebView | ||||
|         // | ||||
|         initWebViewFromAssets(); | ||||
|          | ||||
|         // 复制 assets 文件到数据目录 files 后, | ||||
|         // 显示 WebView | ||||
|         // | ||||
| //        if (App.isDebug()) { | ||||
| //            initAssetsToSD(); | ||||
| //        } | ||||
| //        MyWebView myWebView = findViewById(R.id.activityhelpMyWebView1); | ||||
| //        myWebView.getSettings().setSupportZoom(true); | ||||
| // | ||||
| //        // 使用file协议加载本地HTML文件 | ||||
| //        //Test OK //String url = "content://cc.winboll.studio.app.beta.fileprovider/files_path/winboll/studio/html/index.html"; | ||||
| //        //myWebView.loadUrl(url); | ||||
| // | ||||
| //        myWebView.loadUrl(mszHelpIndexFileUri.toString()); | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     void initWebViewFromAssets() { | ||||
|         try { | ||||
|             SimpleWebView webView = findViewById(R.id.activityhelpSimpleWebView1); | ||||
|             webView.getSettings().setSupportZoom(true); | ||||
|             // 读取assets文件夹下的index.html文件 | ||||
|             InputStream inputStream = getAssets().open("winboll/studio/html/index.html"); | ||||
|             int size = inputStream.available(); | ||||
|             byte[] buffer = new byte[size]; | ||||
|             inputStream.read(buffer); | ||||
|             inputStream.close(); | ||||
|             String html = new String(buffer); | ||||
|             webView.loadDataWithBaseURL(null, html, "text/html", "UTF-8", null); | ||||
|         } catch (IOException e) { | ||||
|             LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void initAssetsToSD() { | ||||
|         String szHtmlFileName = "index.html"; | ||||
|         String szAssetsFilePath = "winboll/studio/html/" + szHtmlFileName; | ||||
|         StringBuilder sbDst = new StringBuilder(); | ||||
|         sbDst.append(getFilesDir()); | ||||
|         sbDst.append(File.separator); | ||||
|         sbDst.append("winboll"); | ||||
|         sbDst.append(File.separator); | ||||
|         sbDst.append("studio"); | ||||
|         sbDst.append(File.separator); | ||||
|         sbDst.append("html"); | ||||
|         sbDst.append(File.separator); | ||||
|         File fDstFolder = new File(sbDst.toString()); | ||||
|         if (!fDstFolder.exists()) { | ||||
|             fDstFolder.mkdirs(); | ||||
|         } | ||||
|         File fDst = new File(fDstFolder, szHtmlFileName); | ||||
|  | ||||
|         mszHelpIndexFilePath = fDst.getPath(); | ||||
|         mszHelpIndexFileUri = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", fDst); | ||||
|  | ||||
|         FileUtils.copyAssetsToSD(this, szAssetsFilePath, mszHelpIndexFilePath); | ||||
|  | ||||
|         // 设置只读权限 | ||||
|         //fDst.setReadOnly(); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,240 @@ | ||||
| package cc.winboll.studio.shared.ads; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/12/09 15:37:35 | ||||
|  * @Describe WinBoll 应用推广视图 | ||||
|  */ | ||||
| import android.content.Context; | ||||
| import android.util.AttributeSet; | ||||
| import android.view.View; | ||||
| import android.webkit.WebView; | ||||
| import android.webkit.WebViewClient; | ||||
| import android.widget.RelativeLayout; | ||||
| import com.hjq.toast.ToastUtils; | ||||
|  | ||||
| public class ADsView extends RelativeLayout { | ||||
|  | ||||
|     public static final String TAG = "ADsView"; | ||||
|  | ||||
|     public volatile boolean mIsHandling; | ||||
|     public volatile boolean mIsAddNewLog; | ||||
|  | ||||
|     Context mContext; | ||||
|     WebView mWebView; | ||||
|     //ScrollView mScrollView; | ||||
|     //TextView mTextView; | ||||
|     //CheckBox mSelectableCheckBox; | ||||
|     //LogViewThread mLogViewThread; | ||||
|     //LogViewHandler mLogViewHandler; | ||||
|     //Spinner mLogLevelSpinner; | ||||
|     //ArrayAdapter<CharSequence> mLogLevelSpinnerAdapter; | ||||
|  | ||||
|     public ADsView(Context context) { | ||||
|         super(context); | ||||
|         initView(context); | ||||
|     } | ||||
|  | ||||
|     public ADsView(Context context, AttributeSet attrs) { | ||||
|         super(context, attrs); | ||||
|         initView(context); | ||||
|     } | ||||
|  | ||||
|     public ADsView(Context context, AttributeSet attrs, int defStyleAttr) { | ||||
|         super(context, attrs, defStyleAttr); | ||||
|         initView(context); | ||||
|     } | ||||
|  | ||||
|     public ADsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { | ||||
|         super(context, attrs, defStyleAttr, defStyleRes); | ||||
|         initView(context); | ||||
|     } | ||||
|  | ||||
| //    public void start() { | ||||
| //        mLogViewThread = new LogViewThread(LogView.this); | ||||
| //        mLogViewThread.start(); | ||||
| //        // 显示日志 | ||||
| //        showAndScrollLogView(); | ||||
| //    } | ||||
| // | ||||
| //    public void scrollLogUp() { | ||||
| //        mScrollView.post(new Runnable() { | ||||
| //                @Override | ||||
| //                public void run() { | ||||
| //                    mScrollView.fullScroll(ScrollView.FOCUS_DOWN); | ||||
| //                    // 日志显示结束 | ||||
| //                    mLogViewHandler.setIsHandling(false); | ||||
| //                    // 检查是否添加了新日志 | ||||
| //                    if (mLogViewHandler.isAddNewLog()) { | ||||
| //                        // 有新日志添加,先更改新日志标志 | ||||
| //                        mLogViewHandler.setIsAddNewLog(false); | ||||
| //                        // 再次发送显示日志的显示 | ||||
| //                        Message message = mLogViewHandler.obtainMessage(LogViewHandler.MSG_LOGVIEW_UPDATE); | ||||
| //                        mLogViewHandler.sendMessage(message); | ||||
| //                    } | ||||
| //                } | ||||
| //            }); | ||||
| //    } | ||||
|  | ||||
|     void initView(Context context) { | ||||
|         mContext = context; | ||||
|  | ||||
| //        mLogViewHandler = new LogViewHandler(); | ||||
|         // 加载视图布局 | ||||
|         View viewMain = inflate(mContext, cc.winboll.studio.R.layout.view_ads, null); | ||||
|         mWebView = viewMain.findViewById(cc.winboll.studio.R.id.viewadsWebView1); | ||||
|         mWebView.setWebViewClient(new WebViewClient() { | ||||
|                 @Override | ||||
|                 public boolean shouldOverrideUrlLoading(WebView view, String url) { | ||||
|                     view.loadUrl(url); | ||||
|                     ToastUtils.show(url); | ||||
|                     return true; | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
| //        // 初始化日志子控件视图 | ||||
| //        // | ||||
| //        mScrollView = findViewById(cc.winboll.studio.R.id.viewlogScrollViewLog); | ||||
| //        mTextView = findViewById(cc.winboll.studio.R.id.viewlogTextViewLog); | ||||
| //        // 获取Log Level spinner实例 | ||||
| //        mLogLevelSpinner = findViewById(cc.winboll.studio.R.id.viewlogSpinner1); | ||||
| // | ||||
| //        (findViewById(cc.winboll.studio.R.id.viewlogButtonClean)).setOnClickListener(new View.OnClickListener(){ | ||||
| // | ||||
| //                @Override | ||||
| //                public void onClick(View v) { | ||||
| //                    LogUtils.cleanLog(); | ||||
| //                    LogUtils.d(TAG, "Log is cleaned."); | ||||
| //                } | ||||
| //            }); | ||||
| //        (findViewById(cc.winboll.studio.R.id.viewlogButtonCopy)).setOnClickListener(new View.OnClickListener(){ | ||||
| // | ||||
| //                @Override | ||||
| //                public void onClick(View v) { | ||||
| // | ||||
| //                    ClipboardManager cm = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE); | ||||
| //                    cm.setPrimaryClip(ClipData.newPlainText(mContext.getPackageName(), LogUtils.loadLog())); | ||||
| //                    LogUtils.d(TAG, "Log is copied."); | ||||
| //                } | ||||
| //            }); | ||||
| //        mSelectableCheckBox = findViewById(cc.winboll.studio.R.id.viewlogCheckBoxSelectable); | ||||
| //        mSelectableCheckBox.setOnClickListener(new View.OnClickListener(){ | ||||
| //                @Override | ||||
| //                public void onClick(View v) { | ||||
| //                    if (mSelectableCheckBox.isChecked()) { | ||||
| //                        setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); | ||||
| //                    } else { | ||||
| //                        setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); | ||||
| //                    } | ||||
| //                } | ||||
| //            }); | ||||
| // | ||||
| //        // 设置日志级别列表 | ||||
| //        ArrayList<String> adapterItems = new ArrayList<>(); | ||||
| //        for (LogUtils.LOG_LEVEL e : LogUtils.LOG_LEVEL.values()) { | ||||
| //            adapterItems.add(e.name()); | ||||
| //        } | ||||
| //        // 假设你有一个字符串数组作为选项列表 | ||||
| //        //String[] options = {"Option 1", "Option 2", "Option 3"}; | ||||
| //        // 创建一个ArrayAdapter来绑定数据到spinner | ||||
| //        mLogLevelSpinnerAdapter = ArrayAdapter.createFromResource( | ||||
| //            context, cc.winboll.studio.R.array.enum_loglevel_array, android.R.layout.simple_spinner_item); | ||||
| //        mLogLevelSpinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); | ||||
| // | ||||
| //        // 设置适配器并将它应用到spinner上 | ||||
| //        mLogLevelSpinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); // 设置下拉视图样式 | ||||
| //        mLogLevelSpinner.setAdapter(mLogLevelSpinnerAdapter); | ||||
| //        // 为Spinner添加监听器 | ||||
| //        mLogLevelSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { | ||||
| //                @Override | ||||
| //                public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { | ||||
| //                    //String selectedOption = mLogLevelSpinnerAdapter.getItem(position); | ||||
| //                    // 处理选中的选项... | ||||
| //                    LogUtils.setLogLevel(LogUtils.LOG_LEVEL.values()[position]); | ||||
| //                } | ||||
| //                @Override | ||||
| //                public void onNothingSelected(AdapterView<?> parent) { | ||||
| //                    // 如果没有选择,则执行此操作... | ||||
| //                } | ||||
| //            }); | ||||
| //        // 获取默认值的索引 | ||||
| //        int defaultValueIndex = LogUtils.getLogLevel().ordinal(); | ||||
| // | ||||
| //        if (defaultValueIndex != -1) { | ||||
| //            // 如果找到了默认值,设置默认选项 | ||||
| //            mLogLevelSpinner.setSelection(defaultValueIndex); | ||||
| //        } | ||||
| // | ||||
| //        // 设置滚动时不聚焦日志 | ||||
| //        setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); | ||||
|     } | ||||
|      | ||||
|     public void loadUrl(String url) { | ||||
|         mWebView.loadUrl(url); | ||||
|         //webView.loadUrl("https://www.winboll.cc"); | ||||
|         //webView.loadUrl("https://ads.winboll.cc"); | ||||
|     } | ||||
|  | ||||
| //    public void updateLogView() { | ||||
| //        if (mLogViewHandler.isHandling() == true) { | ||||
| //            // 正在处理日志显示, | ||||
| //            // 就先设置一个新日志标志位 | ||||
| //            // 以便日志显示完后,再次显示新日志内容 | ||||
| //            mLogViewHandler.setIsAddNewLog(true); | ||||
| //        } else { | ||||
| //            //LogUtils.d(TAG, "LogListener showLog(String path)"); | ||||
| //            Message message = mLogViewHandler.obtainMessage(LogViewHandler.MSG_LOGVIEW_UPDATE); | ||||
| //            mLogViewHandler.sendMessage(message); | ||||
| //            mLogViewHandler.setIsAddNewLog(false); | ||||
| //        } | ||||
| //    } | ||||
| // | ||||
| //    void showAndScrollLogView() { | ||||
| //        mTextView.setText(LogUtils.loadLog()); | ||||
| //        scrollLogUp(); | ||||
| //    } | ||||
| // | ||||
| //    class LogViewHandler extends Handler { | ||||
| // | ||||
| //        final static int MSG_LOGVIEW_UPDATE = 0; | ||||
| //        volatile boolean isHandling; | ||||
| //        volatile boolean isAddNewLog; | ||||
| // | ||||
| //        public LogViewHandler() { | ||||
| //            setIsHandling(false); | ||||
| //            setIsAddNewLog(false); | ||||
| //        } | ||||
| // | ||||
| //        public void setIsHandling(boolean isHandling) { | ||||
| //            this.isHandling = isHandling; | ||||
| //        } | ||||
| // | ||||
| //        public boolean isHandling() { | ||||
| //            return isHandling; | ||||
| //        } | ||||
| // | ||||
| //        public void setIsAddNewLog(boolean isAddNewLog) { | ||||
| //            this.isAddNewLog = isAddNewLog; | ||||
| //        } | ||||
| // | ||||
| //        public boolean isAddNewLog() { | ||||
| //            return isAddNewLog; | ||||
| //        } | ||||
| // | ||||
| //        public void handleMessage(Message msg) { | ||||
| //            switch (msg.what) { | ||||
| //                case MSG_LOGVIEW_UPDATE:{ | ||||
| //                        if (isHandling() == false) { | ||||
| //                            setIsHandling(true); | ||||
| //                            showAndScrollLogView(); | ||||
| //                        } | ||||
| //                        break; | ||||
| //                    } | ||||
| //                default: | ||||
| //                    break; | ||||
| //            } | ||||
| //            super.handleMessage(msg); | ||||
| //        } | ||||
| //    } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,153 @@ | ||||
| package cc.winboll.studio.shared.app; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/08/12 14:45:35 | ||||
|  * @Describe 应用版本工具集 | ||||
|  */ | ||||
| import com.hjq.toast.ToastUtils; | ||||
| import java.util.regex.Matcher; | ||||
| import java.util.regex.Pattern; | ||||
|  | ||||
| public class AppVersionUtils { | ||||
|  | ||||
|     public static final String TAG = "AppVersionUtils"; | ||||
|  | ||||
|     // | ||||
|     // 检查新版本是否成立 | ||||
|     // szCurrentCode : 当前版本应用包名 | ||||
|     // szNextCode : 新版本应用包名 | ||||
|     // 返回 :情况1:当前版本是发布版 | ||||
|     //       返回 true (新版本 > 当前版本) | ||||
|     //       情况1:当前版本是Beta版 | ||||
|     //       true 新版本 == 当前版本 | ||||
|     // | ||||
|     public static boolean isHasNewVersion2(String szCurrentName, String szNextName) { | ||||
|         //szCurrentName = "AES_6.2.0-beta0_3234.apk"; | ||||
|         //szNextName = "AES_6.1.12.apk"; | ||||
|         //szCurrentName = "AES_6.2.0-beta0_3234.apk"; | ||||
|         //szNextName = "AES_6.2.0.apk"; | ||||
|         //szCurrentName = "AES_6.2.0-beta0_3234.apk"; | ||||
|         //szNextName = "AES_6.2.2.apk"; | ||||
|         //szCurrentName = "AES_6.2.0-beta0_3234.apk"; | ||||
|         //szNextName = "AES_6.2.0.apk"; | ||||
|         //szCurrentName = "AES_6.1.0.apk"; | ||||
|         //szNextName = "AES_6.2.0.apk"; | ||||
|         //LogUtils.d(TAG, "szCurrentName : " + szCurrentName); | ||||
|         //LogUtils.d(TAG, "szNextName : " + szNextName); | ||||
|  | ||||
|         //boolean isVersionNewer = false; | ||||
|         //if(szCurrentName.equals(szNextName)) { | ||||
|         //    isVersionNewer = false; | ||||
|         //} else { | ||||
|         //ToastUtils.show("szCurrent : " + szCurrent + "\nszNext : " + szNext); | ||||
|         //int nApk = szNextName.lastIndexOf(".apk"); | ||||
|         //ToastUtils.show("nApk : " + Integer.toString(nApk)); | ||||
|         //String szNextNoApkName = szNextName.substring(0, nApk); | ||||
|         //ToastUtils.show("szNextNoApkName : " + szNextNoApkName); | ||||
|         //String szCurrentNoApkName = szCurrentName.substring(0, szNextNoApkName.length()); | ||||
|         //ToastUtils.show("szCurrentNoApkName : " + szCurrentNoApkName); | ||||
|         //String str1 = "3.4.50"; | ||||
|         //String str2 = "3.3.60"; | ||||
|         //String str1 = getCodeInPackageName(szCurrentName); | ||||
|         //String str2 = getCodeInPackageName(szNextName); | ||||
|         //String str1 = getCodeInPackageName(szNextName); | ||||
|         //String str2 = getCodeInPackageName(szCurrentName); | ||||
|         //Boolean isVersionNewer2 = checkNewVersion(str1,str2); | ||||
|         //ToastUtils.show("isVersionNewer2 : " + Boolean.toString(isVersionNewer2)); | ||||
|         //ToastUtils.show(checkNewVersion(getCodeInPackageName(szCurrentName), getCodeInPackageName(szNextName))); | ||||
|         //return checkNewVersion(getCodeInPackageName(szCurrentName), getCodeInPackageName(szNextName)); | ||||
|         //} | ||||
|         //return isVersionNewer; | ||||
|         if (checkNewVersion(getCodeInPackageName(szCurrentName), getCodeInPackageName(szNextName))) { | ||||
|             // 比 AES_6.2.0.apk 版本大,如 AES_6.2.1.apk。 | ||||
|             // 比 AES_6.2.0-beta0_3234.apk 大,如 AES_6.2.1.apk。 | ||||
|             //LogUtils.d(TAG, "App newer stage version is released. Release name : " + szNextName); | ||||
|             return true; | ||||
|         }  | ||||
|         if (szCurrentName.matches(".*_\\d+\\.\\d+\\.\\d+-beta.*\\.apk")) { | ||||
|             String szCurrentReleasePackageName = getReleasePackageName(szCurrentName); | ||||
|             //LogUtils.d(TAG, "szCurrentReleasePackageName : " + szCurrentReleasePackageName); | ||||
|             if (szCurrentReleasePackageName.equals(szNextName)) { | ||||
|                 // 与 AES_6.2.0-beta0_3234.apk 版本相同,如 AES_6.2.0.apk。 | ||||
|                 //LogUtils.d(TAG, "App stage version is released. Release name : " + szNextName); | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|         //LogUtils.d(TAG, "App version is the newest. "); | ||||
|         return false; | ||||
|     } | ||||
|      | ||||
|     public static boolean isHasNewStageReleaseVersion(String szCurrentName, String szNextName) { | ||||
|         //szCurrentName = "AES_6.2.12.apk"; | ||||
|         //szNextName = "AES_6.3.12.apk"; | ||||
|         if (checkNewVersion(getCodeInPackageName(szCurrentName), getCodeInPackageName(szNextName))) { | ||||
|             // 比 AES_6.2.0.apk 版本大,如 AES_6.2.1.apk。 | ||||
|             //LogUtils.d(TAG, "App newer stage version is released. Release name : " + szNextName); | ||||
|             return true; | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     // 检查新版本是否成立 | ||||
|     // szCurrentCode : 当前版本 | ||||
|     // szNextCode : 新版本 | ||||
|     // 返回 :true 新版本 > 当前版本 | ||||
|     // | ||||
|     public static Boolean checkNewVersion(String szCurrentCode, String szNextCode) { | ||||
|         boolean isNew = false; | ||||
|         String[] appVersionCurrent = szCurrentCode.split("\\."); | ||||
|         String[] appVersionNext = szNextCode.split("\\."); | ||||
|         //根据位数最短的判断 | ||||
|         int lim = appVersionCurrent.length > appVersionNext.length ? appVersionNext.length : appVersionCurrent.length; | ||||
|         //根据位数循环判断各个版本 | ||||
|         for (int i = 0; i < lim; i++) { | ||||
|             if (Integer.parseInt(appVersionNext[i]) > Integer.parseInt(appVersionCurrent[i])) { | ||||
|                 isNew = true; | ||||
|                 return isNew; | ||||
|             } else if(Integer.parseInt(appVersionNext[i]) == Integer.parseInt(appVersionCurrent[i])) { | ||||
|                 continue ; | ||||
|             } else { | ||||
|                 isNew = false; | ||||
|                 return isNew; | ||||
|             } | ||||
|         } | ||||
|         return isNew; | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     // 截取应用包名称版本号信息 | ||||
|     // 如 :AppUtils_7.0.4-beta1_0120.apk 版本号为 7.0.4 | ||||
|     // 如 :AppUtils_7.0.4.apk 版本号为 7.0.4 | ||||
|     // | ||||
|     public static String getCodeInPackageName(String apkName) { | ||||
|         //String apkName = "AppUtils_7.0.0.apk"; | ||||
|         Pattern pattern = Pattern.compile("\\d+\\.\\d+\\.\\d+"); | ||||
|         Matcher matcher = pattern.matcher(apkName); | ||||
|         if (matcher.find()) { | ||||
|             String version = matcher.group(); | ||||
|             return version; | ||||
|             //System.out.println("Version number: " + version); // 输出:7.0.0 | ||||
|         } | ||||
|         return ""; | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     // 根据Beta版名称生成发布版应用包名称 | ||||
|     // 如 AppUtils_7.0.4-beta1_0120.apk | ||||
|     // 发布版名称就为AppUtils_7.0.4.apk | ||||
|     // | ||||
|     public static String getReleasePackageName(String szBetaPackageName) { | ||||
|         //String szBetaPackageName = "AppUtils_7.0.4-beta1_0120.apk"; | ||||
|         Pattern pattern = Pattern.compile(".*\\d+\\.\\d+\\.\\d+"); | ||||
|         Matcher matcher = pattern.matcher(szBetaPackageName); | ||||
|         if (matcher.find()) { | ||||
|             String szReleasePackageName = matcher.group(); | ||||
|             return szReleasePackageName + ".apk"; | ||||
|             //System.out.println("Version number: " + version); // 输出:7.0.0 | ||||
|         } | ||||
|         return ""; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,284 @@ | ||||
| package cc.winboll.studio.shared.app; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/08/23 15:40:30 | ||||
|  * @Describe Json Bean 基础类。 | ||||
|  */ | ||||
| import android.content.Context; | ||||
| import android.util.JsonReader; | ||||
| import android.util.JsonWriter; | ||||
| import cc.winboll.studio.shared.log.LogUtils; | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.io.StringReader; | ||||
| import java.io.StringWriter; | ||||
| import java.util.ArrayList; | ||||
|  | ||||
| public abstract class BaseBean<T extends BaseBean> { | ||||
|  | ||||
|     public static final String TAG = "BaseBean"; | ||||
|     static final String BEAN_NAME = "BeanName"; | ||||
|  | ||||
|     public BaseBean() {} | ||||
|  | ||||
|     public abstract String getName(); | ||||
|  | ||||
|     public String getBeanJsonFilePath(Context context) { | ||||
|  | ||||
|         return context.getExternalFilesDir(TAG) + "/" + getName() + ".json"; | ||||
|     } | ||||
|  | ||||
|     public String getBeanListJsonFilePath(Context context) { | ||||
|  | ||||
|         return context.getExternalFilesDir(TAG) + "/" + getName() + "_List.json"; | ||||
|     } | ||||
|  | ||||
|     public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { | ||||
|         jsonWriter.name(BEAN_NAME).value(getName()); | ||||
|     } | ||||
|  | ||||
|     public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     abstract public T readBeanFromJsonReader(JsonReader jsonReader) throws IOException; | ||||
|  | ||||
|     public static <T extends BaseBean> String checkIsTheSameBeanListAndFile(String szFilePath, Class<T> clazz) { | ||||
|         StringBuilder sbResult = new StringBuilder(); | ||||
|         String szErrorInfo = "Check Is The Same Bean List And File Error : "; | ||||
|  | ||||
|         try { | ||||
|             int nSameCount = 0; | ||||
|             int nBeanListCout = 0; | ||||
|  | ||||
|             T beanTemp = clazz.newInstance(); | ||||
|             String szBeanSimpleName = beanTemp.getName(); | ||||
|             String szListJson = FileUtils.readStringFromFile(szFilePath); | ||||
|             StringReader stringReader = new StringReader(szListJson); | ||||
|             JsonReader jsonReader = new JsonReader(stringReader); | ||||
|             jsonReader.beginArray(); | ||||
|             while (jsonReader.hasNext()) { | ||||
|                 nBeanListCout++; | ||||
|                 jsonReader.beginObject(); | ||||
|                 while (jsonReader.hasNext()) { | ||||
|                     String name = jsonReader.nextName(); | ||||
|                     if (name.equals(BEAN_NAME)) { | ||||
|                         if (szBeanSimpleName.equals(jsonReader.nextString())) { | ||||
|                             nSameCount++; | ||||
|                         } | ||||
|                     } else { | ||||
|                         jsonReader.skipValue(); | ||||
|                     } | ||||
|                 } | ||||
|                 jsonReader.endObject(); | ||||
|             } | ||||
|             jsonReader.endArray(); | ||||
|  | ||||
|             // 返回检查结果 | ||||
|             if (nSameCount == nBeanListCout) { | ||||
|                 // 检查一致直接返回空串 | ||||
|                 return ""; | ||||
|             } else { | ||||
|                 // 检查不一致返回对比信息 | ||||
|                 sbResult.append("Total : "); | ||||
|                 sbResult.append(nBeanListCout); | ||||
|                 sbResult.append(" Diff : "); | ||||
|                 sbResult.append(nBeanListCout - nSameCount); | ||||
|             } | ||||
|         } catch (InstantiationException e) { | ||||
|             sbResult.append(szErrorInfo); | ||||
|             sbResult.append(e); | ||||
|             LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); | ||||
|         } catch (IllegalAccessException e) { | ||||
|             sbResult.append(szErrorInfo); | ||||
|             sbResult.append(e); | ||||
|             LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); | ||||
|         } catch (IOException e) { | ||||
|             sbResult.append(szErrorInfo); | ||||
|             sbResult.append(e); | ||||
|             LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); | ||||
|         } | ||||
|         return sbResult.toString(); | ||||
|     } | ||||
|  | ||||
|     public static <T extends BaseBean> T parseStringToBean(String szBean, Class<T> clazz) throws IOException { | ||||
|         // 创建 JsonWriter 对象 | ||||
|         StringReader stringReader = new StringReader(szBean); | ||||
|         JsonReader jsonReader = new JsonReader(stringReader); | ||||
|         try { | ||||
|             T beanTemp = clazz.newInstance(); | ||||
|             return (T)beanTemp.readBeanFromJsonReader(jsonReader); | ||||
|         } catch (InstantiationException e) { | ||||
|             LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); | ||||
|         } catch (IllegalAccessException e) { | ||||
|             LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     public static <T extends BaseBean> boolean parseStringToBeanList(String szBeanList, ArrayList<T> beanList, Class<T> clazz) { | ||||
|         try { | ||||
|             beanList.clear(); | ||||
|             StringReader stringReader = new StringReader(szBeanList); | ||||
|             JsonReader jsonReader = new JsonReader(stringReader); | ||||
|             jsonReader.beginArray(); | ||||
|             while (jsonReader.hasNext()) { | ||||
|                 T beanTemp = clazz.newInstance(); | ||||
|                 T bean = (T)beanTemp.readBeanFromJsonReader(jsonReader); | ||||
|                 if (bean != null) { | ||||
|                     beanList.add(bean); | ||||
|                     //LogUtils.d(TAG, "beanList.add(bean)"); | ||||
|                 } | ||||
|             } | ||||
|             jsonReader.endArray(); | ||||
|             return true; | ||||
|             //LogUtils.d(TAG, "beanList.size() is " + Integer.toString(beanList.size())); | ||||
|         } catch (InstantiationException e) { | ||||
|             LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); | ||||
|         } catch (IllegalAccessException e) { | ||||
|             LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); | ||||
|         } catch (IOException e) { | ||||
|             LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String toString() { | ||||
|         // 创建 JsonWriter 对象 | ||||
|         StringWriter stringWriter = new StringWriter(); | ||||
|         JsonWriter jsonWriter = new JsonWriter(stringWriter); | ||||
|         jsonWriter.setIndent("  "); | ||||
|         try {// 开始 JSON 对象 | ||||
|             jsonWriter.beginObject(); | ||||
|             // 写入键值对 | ||||
|             writeThisToJsonWriter(jsonWriter); | ||||
|             // 结束 JSON 对象 | ||||
|             jsonWriter.endObject(); | ||||
|             return stringWriter.toString(); | ||||
|         } catch (IOException e) { | ||||
|             LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); | ||||
|         } | ||||
|         // 获取 JSON 字符串 | ||||
|         return ""; | ||||
|     } | ||||
|  | ||||
|     public static <T extends BaseBean> String toStringByBeanList(ArrayList<T> beanList) { | ||||
|         try { | ||||
|             StringWriter stringWriter = new StringWriter(); | ||||
|             JsonWriter jsonWriter = new JsonWriter(stringWriter); | ||||
|             jsonWriter.setIndent("  "); | ||||
|             jsonWriter.beginArray(); | ||||
|             for (int i = 0; i < beanList.size(); i++) { | ||||
|                 // 开始 JSON 对象 | ||||
|                 jsonWriter.beginObject(); | ||||
|                 // 写入键值对 | ||||
|                 beanList.get(i).writeThisToJsonWriter(jsonWriter); | ||||
|                 // 结束 JSON 对象 | ||||
|                 jsonWriter.endObject(); | ||||
|             } | ||||
|             jsonWriter.endArray(); | ||||
|             jsonWriter.close(); | ||||
|             return stringWriter.toString(); | ||||
|         } catch (IOException e) { | ||||
|             LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); | ||||
|         } | ||||
|         return ""; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     public static <T extends BaseBean> T loadBean(Context context, Class<T> clazz) { | ||||
|         try { | ||||
|             T beanTemp = clazz.newInstance(); | ||||
|             return loadBeanFromFile(beanTemp.getBeanJsonFilePath(context), clazz); | ||||
|         } catch (InstantiationException e) { | ||||
|             LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); | ||||
|         } catch (IllegalAccessException e) { | ||||
|             LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     public static <T extends BaseBean> T loadBeanFromFile(String szFilePath, Class<T> clazz) { | ||||
|         try { | ||||
|             try { | ||||
|                 File fTemp = new File(szFilePath); | ||||
|                 if (fTemp.exists()) { | ||||
|                     T beanTemp = clazz.newInstance();String szJson = FileUtils.readStringFromFile(szFilePath); | ||||
|                     return beanTemp.parseStringToBean(szJson, clazz); | ||||
|                 } | ||||
|             } catch (InstantiationException e) { | ||||
|                 LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); | ||||
|             } catch (IllegalAccessException e) { | ||||
|                 LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); | ||||
|             } | ||||
|  | ||||
|         } catch (IOException e) { | ||||
|             LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     public static <T extends BaseBean> boolean saveBean(Context context, T bean) { | ||||
|         return saveBeanToFile(bean.getBeanJsonFilePath(context), bean); | ||||
|     } | ||||
|  | ||||
|     public static <T extends BaseBean> boolean saveBeanToFile(String szFilePath, T bean) { | ||||
|         try { | ||||
|             String szJson = bean.toString(); | ||||
|             FileUtils.writeStringToFile(szFilePath, szJson); | ||||
|             return true; | ||||
|         } catch (IOException e) { | ||||
|             LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     public static <T extends BaseBean> boolean loadBeanList(Context context, ArrayList<T> beanListDst, Class<T> clazz) { | ||||
|         try { | ||||
|             T beanTemp = clazz.newInstance(); | ||||
|             return loadBeanListFromFile(beanTemp.getBeanListJsonFilePath(context), beanListDst, clazz); | ||||
|         } catch (InstantiationException e) {} catch (IllegalAccessException e) { | ||||
|             LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     public static <T extends BaseBean> boolean loadBeanListFromFile(String szFilePath, ArrayList<T> beanList, Class<T> clazz) { | ||||
|         try { | ||||
|             File fTemp = new File(szFilePath); | ||||
|             if (fTemp.exists()) { | ||||
|                 String szListJson = FileUtils.readStringFromFile(szFilePath); | ||||
|                 return parseStringToBeanList(szListJson, beanList, clazz); | ||||
|             } | ||||
|         } catch (IOException e) { | ||||
|             LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     public static <T extends BaseBean> boolean saveBeanList(Context context, ArrayList<T> beanList, Class<T> clazz) { | ||||
|         try { | ||||
|             T beanTemp = clazz.newInstance(); | ||||
|             return saveBeanListToFile(beanTemp.getBeanListJsonFilePath(context), beanList); | ||||
|         } catch (InstantiationException e) { | ||||
|             LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); | ||||
|         } catch (IllegalAccessException e) { | ||||
|             LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     public static <T extends BaseBean> boolean saveBeanListToFile(String szFilePath, ArrayList<T> beanList) { | ||||
|         try { | ||||
|             String szJson = toStringByBeanList(beanList); | ||||
|             FileUtils.writeStringToFile(szFilePath, szJson); | ||||
|             //LogUtils.d(TAG, "FileUtil.writeFile beanList.size() is " + Integer.toString(beanList.size())); | ||||
|             return true; | ||||
|         } catch (IOException e) { | ||||
|             LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,215 @@ | ||||
| package cc.winboll.studio.shared.app; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/08/12 13:22:12 | ||||
|  * @Describe 异常处理类 | ||||
|  */ | ||||
| import android.app.Activity; | ||||
| import android.app.Application; | ||||
| import android.content.ActivityNotFoundException; | ||||
| import android.content.ClipData; | ||||
| import android.content.ClipboardManager; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.content.pm.PackageInfo; | ||||
| import android.content.pm.PackageManager; | ||||
| import android.content.res.Resources; | ||||
| import android.graphics.Color; | ||||
| import android.os.Build; | ||||
| import android.os.Bundle; | ||||
| import android.text.TextUtils; | ||||
| import android.view.Menu; | ||||
| import android.view.MenuItem; | ||||
| import android.view.ViewGroup; | ||||
| import android.widget.HorizontalScrollView; | ||||
| import android.widget.ScrollView; | ||||
| import android.widget.TextView; | ||||
| import java.io.File; | ||||
| import java.io.FileOutputStream; | ||||
| import java.io.IOException; | ||||
| import java.io.PrintWriter; | ||||
| import java.io.StringWriter; | ||||
| import java.lang.Thread.UncaughtExceptionHandler; | ||||
| import java.text.SimpleDateFormat; | ||||
| import java.util.Date; | ||||
| import java.util.Locale; | ||||
|  | ||||
| public final class CrashHandler { | ||||
|  | ||||
|     public static final UncaughtExceptionHandler DEFAULT_UNCAUGHT_EXCEPTION_HANDLER = Thread.getDefaultUncaughtExceptionHandler(); | ||||
|  | ||||
|     public static void init(Application app) { | ||||
|         init(app, null); | ||||
|     } | ||||
|  | ||||
|     public static void init(final Application app, final String crashDir) { | ||||
|         Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler(){ | ||||
|  | ||||
|                 @Override | ||||
|                 public void uncaughtException(Thread thread, Throwable throwable) { | ||||
|                     try { | ||||
|                         tryUncaughtException(thread, throwable); | ||||
|                     } catch (Throwable e) { | ||||
|                         e.printStackTrace(); | ||||
|                         if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null) | ||||
|                             DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(thread, throwable); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 private void tryUncaughtException(Thread thread, Throwable throwable) { | ||||
|                     final String time = new SimpleDateFormat("yyyy_MM_dd-HH_mm_ss", Locale.getDefault()).format(new Date()); | ||||
|                     File crashFile = new File(TextUtils.isEmpty(crashDir) ? new File(app.getExternalFilesDir(null), "crash") | ||||
|                                               : new File(crashDir), "crash_" + time + ".txt"); | ||||
|  | ||||
|                     String versionName = "unknown"; | ||||
|                     long versionCode = 0; | ||||
|                     try {  | ||||
|                         PackageInfo packageInfo = app.getPackageManager().getPackageInfo(app.getPackageName(), 0); | ||||
|                         versionName = packageInfo.versionName; | ||||
|                         versionCode = Build.VERSION.SDK_INT >= 28 ? packageInfo.getLongVersionCode() | ||||
|                             : packageInfo.versionCode; | ||||
|                     } catch (PackageManager.NameNotFoundException ignored) {} | ||||
|  | ||||
|                     String fullStackTrace; { | ||||
|                         StringWriter sw = new StringWriter();  | ||||
|                         PrintWriter pw = new PrintWriter(sw); | ||||
|                         throwable.printStackTrace(pw); | ||||
|                         fullStackTrace = sw.toString(); | ||||
|                         pw.close(); | ||||
|                     } | ||||
|  | ||||
|                     StringBuilder sb = new StringBuilder(); | ||||
|                     sb.append("************* Crash Head ****************\n"); | ||||
|                     sb.append("Time Of Crash      : ").append(time).append("\n"); | ||||
|                     sb.append("Device Manufacturer: ").append(Build.MANUFACTURER).append("\n"); | ||||
|                     sb.append("Device Model       : ").append(Build.MODEL).append("\n"); | ||||
|                     sb.append("Android Version    : ").append(Build.VERSION.RELEASE).append("\n"); | ||||
|                     sb.append("Android SDK        : ").append(Build.VERSION.SDK_INT).append("\n"); | ||||
|                     sb.append("App VersionName    : ").append(versionName).append("\n"); | ||||
|                     sb.append("App VersionCode    : ").append(versionCode).append("\n"); | ||||
|                     sb.append("************* Crash Head ****************\n"); | ||||
|                     sb.append("\n").append(fullStackTrace); | ||||
|  | ||||
|                     String errorLog = sb.toString(); | ||||
|  | ||||
|                     try { | ||||
|                         writeFile(crashFile, errorLog); | ||||
|                     } catch (IOException ignored) {} | ||||
|  | ||||
|                     gotoCrashActiviy: { | ||||
|                         Intent intent = new Intent(app, CrashActiviy.class); | ||||
|                         intent.addFlags( | ||||
|                             Intent.FLAG_ACTIVITY_NEW_TASK | ||||
|                             | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK | ||||
|                         ); | ||||
|                         intent.putExtra(CrashActiviy.EXTRA_CRASH_INFO, errorLog); | ||||
|                         try { | ||||
|                             app.startActivity(intent); | ||||
|                             android.os.Process.killProcess(android.os.Process.myPid()); | ||||
|                             System.exit(0); | ||||
|                         } catch (ActivityNotFoundException e) { | ||||
|                             e.printStackTrace(); | ||||
|                             if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null) | ||||
|                                 DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(thread, throwable); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 private void writeFile(File file, String content) throws IOException { | ||||
|                     File parentFile = file.getParentFile(); | ||||
|                     if (parentFile != null && !parentFile.exists()) { | ||||
|                         parentFile.mkdirs(); | ||||
|                     } | ||||
|                     file.createNewFile(); | ||||
|                     FileOutputStream fos = new FileOutputStream(file); | ||||
|                     fos.write(content.getBytes()); | ||||
|                     try { | ||||
|                         fos.close(); | ||||
|                     } catch (IOException e) {} | ||||
|                 } | ||||
|  | ||||
|             }); | ||||
|     } | ||||
|  | ||||
|     public static final class CrashActiviy extends Activity implements MenuItem.OnMenuItemClickListener { | ||||
|  | ||||
|         private static final String EXTRA_CRASH_INFO = "crashInfo"; | ||||
|  | ||||
|         private static final int MENUITEM_COPY = 0; | ||||
|         private static final int MENUITEM_RESTART = 1; | ||||
|  | ||||
|         private String mLog; | ||||
|  | ||||
|         @Override | ||||
|         protected void onCreate(Bundle savedInstanceState) { | ||||
|             super.onCreate(savedInstanceState); | ||||
|             mLog = getIntent().getStringExtra(EXTRA_CRASH_INFO); | ||||
|             setTheme(android.R.style.Theme_DeviceDefault_Light_DarkActionBar); | ||||
|             setContentView: { | ||||
|                 ScrollView contentView = new ScrollView(this); | ||||
|                 contentView.setFillViewport(true); | ||||
|                 HorizontalScrollView hw = new HorizontalScrollView(this); | ||||
|                 hw.setBackgroundColor(Color.GRAY); | ||||
|                 TextView message = new TextView(this); { | ||||
|                     int padding = dp2px(16); | ||||
|                     message.setPadding(padding, padding, padding, padding); | ||||
|                     message.setText(mLog); | ||||
|                     message.setTextIsSelectable(true); | ||||
|                 } | ||||
|                 hw.addView(message); | ||||
|                 contentView.addView(hw, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); | ||||
|                 setContentView(contentView); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void onBackPressed() { | ||||
|             restart(); | ||||
|         } | ||||
|  | ||||
|         private void restart() { | ||||
|             PackageManager pm = getPackageManager(); | ||||
|             Intent intent = pm.getLaunchIntentForPackage(getPackageName()); | ||||
|             if (intent != null) { | ||||
|                 intent.addFlags( | ||||
|                     Intent.FLAG_ACTIVITY_NEW_TASK | ||||
|                     | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK | ||||
|                 ); | ||||
|                 startActivity(intent); | ||||
|             } | ||||
|             finish(); | ||||
|             android.os.Process.killProcess(android.os.Process.myPid()); | ||||
|             System.exit(0); | ||||
|         } | ||||
|  | ||||
|         private int dp2px(final float dpValue) { | ||||
|             final float scale = Resources.getSystem().getDisplayMetrics().density; | ||||
|             return (int) (dpValue * scale + 0.5f); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public boolean onMenuItemClick(MenuItem item) { | ||||
|             switch (item.getItemId()) { | ||||
|                 case MENUITEM_COPY:  | ||||
|                     ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); | ||||
|                     cm.setPrimaryClip(ClipData.newPlainText(getPackageName(), mLog)); | ||||
|                     break; | ||||
|                 case MENUITEM_RESTART:  | ||||
|                     restart(); | ||||
|                     break; | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public boolean onCreateOptionsMenu(Menu menu) { | ||||
|             menu.add(0, MENUITEM_COPY, 0, "Copy").setOnMenuItemClickListener(this) | ||||
|                 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); | ||||
|             menu.add(0, MENUITEM_RESTART, 0, "Restart").setOnMenuItemClickListener(this) | ||||
|                 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); | ||||
|             return true; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -0,0 +1,150 @@ | ||||
| package cc.winboll.studio.shared.app; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/08/12 13:46:32 | ||||
|  * @Describe 文件处理工具 | ||||
|  */ | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.content.res.AssetManager; | ||||
| import android.net.Uri; | ||||
| import androidx.core.content.FileProvider; | ||||
| import cc.winboll.studio.shared.log.LogUtils; | ||||
| import java.io.File; | ||||
| import java.io.FileInputStream; | ||||
| import java.io.FileOutputStream; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.io.InputStreamReader; | ||||
| import java.io.OutputStream; | ||||
| import java.io.OutputStreamWriter; | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.nio.file.Files; | ||||
| import java.nio.file.Path; | ||||
| import java.nio.file.Paths; | ||||
|  | ||||
| public class FileUtils { | ||||
|  | ||||
|     public static final String TAG = "FileUtils"; | ||||
|  | ||||
|     public static void shareJSONFile(Context context, String szShareFilePath) { | ||||
|         Uri uri; | ||||
|         File file = new File(szShareFilePath); | ||||
|         uri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", file); | ||||
|         /*if (Build.VERSION.SDK_INT >= 24) {//android 7.0以上 | ||||
|          uri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", file); | ||||
|          } else { | ||||
|          uri = Uri.fromFile(file); | ||||
|          }*/ | ||||
|         Intent shareIntent = new Intent();     | ||||
|         shareIntent.setAction(Intent.ACTION_SEND);     | ||||
|         shareIntent.putExtra(Intent.EXTRA_STREAM, uri);     | ||||
|         shareIntent.setType("application/json"); | ||||
|  | ||||
|         shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); | ||||
|         /*if (Build.VERSION.SDK_INT >= 24) { | ||||
|          shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); | ||||
|          }*/ | ||||
|  | ||||
|         // 设置分享的标题     | ||||
|         context.startActivity(Intent.createChooser(shareIntent, "SHARE JSON"));   | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     // 把字符串写入文件,指定 UTF-8 编码 | ||||
|     // | ||||
|     public static void writeStringToFile(String szFilePath, String szContent) throws IOException { | ||||
|         File file = new File(szFilePath); | ||||
|         if (!file.getParentFile().exists()) { | ||||
|             file.getParentFile().mkdirs(); | ||||
|         } | ||||
|         FileOutputStream outputStream = new FileOutputStream(file); | ||||
|         OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8); | ||||
|         writer.write(szContent); | ||||
|         writer.close(); | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     // 读取文件到字符串,指定 UTF-8 编码 | ||||
|     // | ||||
|     public static String readStringFromFile(String szFilePath) throws IOException { | ||||
|         File file = new File(szFilePath); | ||||
|         FileInputStream inputStream = new FileInputStream(file); | ||||
|         InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8); | ||||
|         StringBuilder content = new StringBuilder(); | ||||
|         int character; | ||||
|         while ((character = reader.read()) != -1) { | ||||
|             content.append((char) character); | ||||
|         } | ||||
|         reader.close(); | ||||
|         return content.toString(); | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     // 将应用内置assets文件夹内容拷贝至指定目录 | ||||
|     // | ||||
|     public static void copyAssetsToSD(Context context, String szSrcAssets, String szDstSD) { | ||||
|         LogUtils.d(TAG, "copyAssetsToSD [" + szSrcAssets + "] to [" + szDstSD + "]"); | ||||
|         AssetManager assetManager = context.getAssets(); | ||||
|         InputStream inputStream = null; | ||||
|         OutputStream outputStream = null; | ||||
|         try { | ||||
|             inputStream = assetManager.open(szSrcAssets); | ||||
|             File outputFile = new File(szDstSD); | ||||
|             outputStream = new FileOutputStream(outputFile); | ||||
|             byte[] buffer = new byte[1024]; | ||||
|             int length = 0; | ||||
|             while ((length = inputStream.read(buffer)) > 0) { | ||||
|                 outputStream.write(buffer, 0, length); | ||||
|             } | ||||
|             outputStream.flush(); | ||||
|             LogUtils.d(TAG, "copyAssetsToSD done."); | ||||
|         } catch (IOException e) { | ||||
|             LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); | ||||
|         } finally { | ||||
|             if (inputStream != null) { | ||||
|                 try { | ||||
|                     inputStream.close(); | ||||
|                 } catch (IOException e) { | ||||
|                     LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); | ||||
|                 } | ||||
|             } | ||||
|             if (outputStream != null) { | ||||
|                 try { | ||||
|                     outputStream.close(); | ||||
|                 } catch (IOException e) { | ||||
|                     LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static boolean copyFile(File srcFile, File dstFile) { | ||||
|         if (!srcFile.exists()) { | ||||
|             LogUtils.d(TAG, "The original file does not exist."); | ||||
|         } else { | ||||
|             try { | ||||
|                 // 源文件路径 | ||||
|                 Path sourcePath = Paths.get(srcFile.getPath()); | ||||
|                 // 目标文件路径 | ||||
|                 Path destPath = Paths.get(dstFile.getPath()); | ||||
|                 // 建立目标父级文件夹 | ||||
|                 if (!dstFile.getParentFile().exists()) { | ||||
|                     dstFile.getParentFile().mkdirs(); | ||||
|                 } | ||||
|                 // 删除旧的目标文件 | ||||
|                 if (dstFile.exists()) { | ||||
|                     dstFile.delete(); | ||||
|                 } | ||||
|                 // 拷贝文件 | ||||
|                 Files.copy(sourcePath, destPath); | ||||
|                 LogUtils.d(TAG, "File copy successfully."); | ||||
|                 return true; | ||||
|             } catch (Exception e) { | ||||
|                 LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|             } | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,101 @@ | ||||
| package cc.winboll.studio.shared.app; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/12/24 11:02:08 | ||||
|  */ | ||||
| import android.app.Activity; | ||||
| import android.app.Application; | ||||
| import android.content.ComponentName; | ||||
| import android.content.Intent; | ||||
| import android.net.Uri; | ||||
| import android.os.Bundle; | ||||
| import cc.winboll.studio.shared.log.LogUtils; | ||||
| import com.hjq.toast.ToastUtils; | ||||
| import java.util.Set;  | ||||
|  | ||||
| public class MyActivityLifecycleCallbacks implements Application.ActivityLifecycleCallbacks {  | ||||
|  | ||||
|     public static final String TAG = "MyActivityLifecycleCallbacks"; | ||||
|  | ||||
|     public String mInfo = ""; | ||||
|      | ||||
|     public MyActivityLifecycleCallbacks(WinBollApplication application) { | ||||
|          | ||||
|     } | ||||
|  | ||||
|     void createActivityeInfo(Activity activity) { | ||||
|         StringBuilder sb = new StringBuilder(); | ||||
|         Intent receivedIntent = activity.getIntent(); | ||||
|         sb.append("\nCallingActivity : \n"); | ||||
|         if (activity.getCallingActivity() != null) { | ||||
|             sb.append(activity.getCallingActivity().getPackageName()); | ||||
|         } | ||||
|         sb.append("\nReceived Intent Package : \n"); | ||||
|         sb.append(receivedIntent.getPackage()); | ||||
|  | ||||
|         Bundle extras = receivedIntent.getExtras(); | ||||
|         if (extras != null) { | ||||
|             for (String key : extras.keySet()) { | ||||
|                 sb.append("\nIntentInfo"); | ||||
|                 sb.append("\n键: "); | ||||
|                 sb.append(key); | ||||
|                 sb.append(", 值: "); | ||||
|                 sb.append(extras.get(key)); | ||||
|                 //Log.d("IntentInfo", "键: " + key + ", 值: " + extras.get(key)); | ||||
|             } | ||||
|         } | ||||
|         mInfo = sb.toString(); | ||||
|         //Log.d("IntentInfo", "发送Intent的应用包名: " + senderPackage); | ||||
|     } | ||||
|  | ||||
|     public void showActivityeInfo() { | ||||
|         ToastUtils.show("ActivityeInfo : " + mInfo); | ||||
|         LogUtils.d(TAG, "ActivityeInfo : " + mInfo); | ||||
|     } | ||||
|  | ||||
|     @Override  | ||||
|     public void onActivityCreated(Activity activity, Bundle savedInstanceState) {  | ||||
|         // 在这里可以做一些初始化相关的操作,例如记录Activity的创建时间等  | ||||
|         //System.out.println(activity.getLocalClassName() + " was created");  | ||||
|         LogUtils.d(TAG, activity.getLocalClassName() + " was created"); | ||||
|         createActivityeInfo(activity); | ||||
|     }  | ||||
|  | ||||
|     @Override  | ||||
|     public void onActivityStarted(Activity activity) {  | ||||
|         //System.out.println(activity.getLocalClassName() + " was started"); | ||||
|         LogUtils.d(TAG, activity.getLocalClassName() + " was started"); | ||||
|         //createActivityeInfo(activity); | ||||
|     }  | ||||
|  | ||||
|     @Override  | ||||
|     public void onActivityResumed(Activity activity) {  | ||||
|         //System.out.println(activity.getLocalClassName() + " was resumed"); | ||||
|         LogUtils.d(TAG, activity.getLocalClassName() + " was resumed"); | ||||
|         //createActivityeInfo(activity); | ||||
|     }  | ||||
|  | ||||
|     @Override  | ||||
|     public void onActivityPaused(Activity activity) {  | ||||
|         //System.out.println(activity.getLocalClassName() + " was paused"); | ||||
|         LogUtils.d(TAG, activity.getLocalClassName() + " was paused"); | ||||
|     }  | ||||
|  | ||||
|     @Override  | ||||
|     public void onActivityStopped(Activity activity) {  | ||||
|         //System.out.println(activity.getLocalClassName() + " was stopped"); | ||||
|         LogUtils.d(TAG, activity.getLocalClassName() + " was stopped"); | ||||
|     }  | ||||
|  | ||||
|     @Override  | ||||
|     public void onActivitySaveInstanceState(Activity activity, Bundle outState) {  | ||||
|         // 可以在这里添加保存状态的自定义逻辑  | ||||
|     }  | ||||
|  | ||||
|     @Override  | ||||
|     public void onActivityDestroyed(Activity activity) {  | ||||
|         //System.out.println(activity.getLocalClassName() + " was destroyed"); | ||||
|         LogUtils.d(TAG, activity.getLocalClassName() + " was destroyed"); | ||||
|     }  | ||||
| } | ||||
| @@ -0,0 +1,468 @@ | ||||
| package cc.winboll.studio.shared.app; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/08/12 14:32:08 | ||||
|  * @Describe WinBoll 活动窗口基础类 | ||||
|  */ | ||||
| import android.app.Activity; | ||||
| import android.content.BroadcastReceiver; | ||||
| import android.content.ComponentName; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.content.IntentFilter; | ||||
| import android.content.SharedPreferences; | ||||
| import android.content.pm.PackageManager; | ||||
| import android.content.pm.ResolveInfo; | ||||
| import android.content.res.Resources; | ||||
| import android.net.Uri; | ||||
| import android.os.Bundle; | ||||
| import android.os.Handler; | ||||
| import android.view.Menu; | ||||
| import android.view.MenuInflater; | ||||
| import android.view.MenuItem; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import androidx.appcompat.app.ActionBar; | ||||
| import androidx.appcompat.app.AppCompatActivity; | ||||
| import androidx.appcompat.widget.Toolbar; | ||||
| import androidx.fragment.app.Fragment; | ||||
| import androidx.fragment.app.FragmentManager; | ||||
| import cc.winboll.studio.R; | ||||
| import cc.winboll.studio.shared.activities.AboutActivity; | ||||
| import cc.winboll.studio.shared.log.LogActivity; | ||||
| import cc.winboll.studio.shared.log.LogUtils; | ||||
| import cc.winboll.studio.shared.view.AboutView; | ||||
| import com.hjq.toast.ToastUtils; | ||||
| import java.util.List; | ||||
| import java.util.Set; | ||||
|  | ||||
| abstract public class WinBollActivity extends AppCompatActivity { | ||||
|  | ||||
|     public static final String TAG = "WinBollActivity"; | ||||
|  | ||||
|     public static final int REQUEST_LOG_ACTIVITY = 0; | ||||
|  | ||||
|     Toolbar mToolBar; | ||||
|  | ||||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|         checkResolveActivity(); | ||||
|         LogUtils.d(TAG, "onCreate"); | ||||
|         super.onCreate(savedInstanceState); | ||||
|         archiveInstance(); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     boolean checkResolveActivity() { | ||||
|         PackageManager packageManager = getPackageManager(); | ||||
|         //Intent intent = new Intent("your_action_here"); | ||||
|         Intent intent = getIntent(); | ||||
|         if (intent != null) { | ||||
|             List<ResolveInfo> resolveInfoList = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); | ||||
|             if (resolveInfoList.size() > 0) { | ||||
|                 // 传入的Intent action在Activity清单的intent-filter的action节点里有定义 | ||||
|                 if (intent.getAction() != null) { | ||||
|                     if (intent.getAction().equals(cc.winboll.studio.intent.action.DEBUGVIEW)) { | ||||
|                         WinBollApplication.setIsDebug(true); | ||||
|                         //ToastUtils.show!("WinBollApplication.setIsDebug(true) by action : " + intent.getAction()); | ||||
|  | ||||
|                     } | ||||
|                 } | ||||
|                 return true; | ||||
|             } else { | ||||
|                 // 传入的Intent action在Activity清单的intent-filter的action节点里没有定义 | ||||
|                 //ToastUtils.show("false : " + intent.getAction()); | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             /* ResolveInfo resolveInfo = pm.resolveActivity(intent, 0); | ||||
|              if (resolveInfo != null) { | ||||
|              // action在清单文件中有声明 | ||||
|  | ||||
|              } else { | ||||
|  | ||||
|              }*/ | ||||
|         } | ||||
|  | ||||
|         // action在清单文件中没有声明 | ||||
|         ToastUtils.show("false"); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     void archiveInstance() { | ||||
|         Intent intent = getIntent(); | ||||
|         StringBuilder sb = new StringBuilder("\n### Archive Instance ###\n"); | ||||
|  | ||||
|         if (intent != null) { | ||||
|             ComponentName componentName = intent.getComponent(); | ||||
|             if (componentName != null) { | ||||
|                 String packageName = componentName.getPackageName(); | ||||
|                 //Log.d("AppStarter", "启动本应用的应用包名: " + packageName); | ||||
|                 sb.append("启动本应用的应用包名: \n" + packageName); | ||||
|             } | ||||
|  | ||||
|             sb.append("\nImplicit Intent Tracker :\n接收到的 Intent 动作: \n" + intent.getAction()); | ||||
|             Set<String> categories = intent.getCategories(); | ||||
|             if (categories != null) { | ||||
|                 for (String category : categories) { | ||||
|                     sb.append("\n接收到的 Intent 类别 :\n" + category); | ||||
|                 } | ||||
|             } | ||||
|             Uri data = intent.getData(); | ||||
|             if (data != null) { | ||||
|                 sb.append("\n接收到的 Intent 数据 :\n" + data.toString()); | ||||
|             } | ||||
|         } else { | ||||
|             sb.append("Intent is null."); | ||||
|         } | ||||
|         sb.append("\n\n"); | ||||
|         LogUtils.d(TAG, sb.toString()); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onPostCreate(Bundle savedInstanceState) { | ||||
|         super.onPostCreate(savedInstanceState); | ||||
|         mToolBar = initToolBar(); | ||||
|         setSupportActionBar(mToolBar); | ||||
|         if (isEnableDisplayHomeAsUp() && mToolBar != null) { | ||||
|             // 显示后退按钮 | ||||
|             getSupportActionBar().setDisplayHomeAsUpEnabled(true); | ||||
|         } | ||||
|  | ||||
|         // 缓存当前 activity | ||||
|         LogUtils.d(TAG, "ActManager.getInstance().add(this);"); | ||||
|         //ToastUtils.show("getTag() " + getTag()); | ||||
|         WinBollActivityManager.getInstance(this).add(this); | ||||
|         //WinBollActivityManager.getInstance().printAvtivityListInfo(); | ||||
|         //ToastUtils.show("WinBollUI_TYPE " + WinBollApplication.getWinBollUI_TYPE()); | ||||
|         //boolean isDebuging = WinBollApplication.loadDebugStatusIsDebuging(); | ||||
|         //ToastUtils.show(String.valueOf(isDebuging)); | ||||
|         //WinBollApplication.setIsDebug(true); | ||||
|  | ||||
|         setSubTitle(getTag()); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void recreate() { | ||||
|         super.recreate(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onDestroy() { | ||||
|         WinBollActivityManager.getInstance(this).registeRemove(this); | ||||
|         super.onDestroy(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void setContentView(int layoutResID) { | ||||
|         super.setContentView(layoutResID); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public <T extends View> T findViewById(int id) { | ||||
|         return super.findViewById(id); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public MenuInflater getMenuInflater() { | ||||
|         return super.getMenuInflater(); | ||||
|     } | ||||
|  | ||||
|     public WinBollActivity getActivity() { | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Context getApplicationContext() { | ||||
|         return super.getApplicationContext(); | ||||
|     } | ||||
|  | ||||
|     public <T extends FragmentManager> T getWinBollActivitySupportFragmentManager() { | ||||
|         return (T)super.getSupportFragmentManager(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public ActionBar getSupportActionBar() { | ||||
|         return super.getSupportActionBar(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void setSupportActionBar(Toolbar toolbar) { | ||||
|         super.setSupportActionBar(toolbar); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void setActionBar(android.widget.Toolbar toolbar) { | ||||
|         super.setActionBar(toolbar); | ||||
|     } | ||||
|  | ||||
|     public void setSubTitle(CharSequence title) { | ||||
|         if (super.getSupportActionBar() != null) { | ||||
|             super.getSupportActionBar().setSubtitle(title); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public CharSequence getSubTitle() { | ||||
|         if (super.getSupportActionBar() != null) { | ||||
|             return super.getSupportActionBar().getSubtitle(); | ||||
|         } | ||||
|         return ""; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void setTitle(CharSequence title) { | ||||
|         super.setTitle(title); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void setTitle(int titleId) { | ||||
|         super.setTitle(titleId); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void setTheme(Resources.Theme theme) { | ||||
|         super.setTheme(theme); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void setTheme(int resid) { | ||||
|         super.setTheme(resid); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void setTitleColor(int textColor) { | ||||
|         super.setTitleColor(textColor); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void setContentView(View view, ViewGroup.LayoutParams params) { | ||||
|         super.setContentView(view, params); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void setContentView(View view) { | ||||
|         super.setContentView(view); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public SharedPreferences getSharedPreferences(String name, int mode) { | ||||
|         return super.getSharedPreferences(name, mode); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     @Override | ||||
|     public boolean releaseInstance() { | ||||
|         return super.releaseInstance(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags) { | ||||
|         return super.registerReceiver(receiver, filter, flags); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler) { | ||||
|         return super.registerReceiver(receiver, filter, broadcastPermission, scheduler); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler, int flags) { | ||||
|         return super.registerReceiver(receiver, filter, broadcastPermission, scheduler, flags); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void startActivities(Intent[] intents) { | ||||
|         super.startActivities(intents); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void startActivityFromFragment(Fragment fragment, Intent intent, int requestCode) { | ||||
|         super.startActivityFromFragment(fragment, intent, requestCode); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void startActivityFromFragment(android.app.Fragment fragment, Intent intent, int requestCode, Bundle options) { | ||||
|         super.startActivityFromFragment(fragment, intent, requestCode, options); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void startActivityFromChild(Activity child, Intent intent, int requestCode, Bundle options) { | ||||
|         super.startActivityFromChild(child, intent, requestCode, options); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void startActivities(Intent[] intents, Bundle options) { | ||||
|         super.startActivities(intents, options); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean startActivityIfNeeded(Intent intent, int requestCode) { | ||||
|         return super.startActivityIfNeeded(intent, requestCode); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean startActivityIfNeeded(Intent intent, int requestCode, Bundle options) { | ||||
|         return super.startActivityIfNeeded(intent, requestCode, options); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void startActivityFromChild(Activity child, Intent intent, int requestCode) { | ||||
|         super.startActivityFromChild(child, intent, requestCode); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public ComponentName startService(Intent service) { | ||||
|         return super.startService(service); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void startActivityForResult(Intent intent, int requestCode) { | ||||
|         super.startActivityForResult(intent, requestCode); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void startActivityForResult(Intent intent, int requestCode, Bundle options) { | ||||
|         super.startActivityForResult(intent, requestCode, options); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void startActivityFromFragment(android.app.Fragment fragment, Intent intent, int requestCode) { | ||||
|         super.startActivityFromFragment(fragment, intent, requestCode); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void startActivityFromFragment(Fragment requestIndex, Intent fragment, int intent, Bundle requestCode) { | ||||
|         super.startActivityFromFragment(requestIndex, fragment, intent, requestCode); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void startActivity(Intent intent, Bundle options) { | ||||
|         super.startActivity(intent, options); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean onCreateOptionsMenu(Menu menu) { | ||||
|         if (isAddWinBollToolBar()) { | ||||
|             getMenuInflater().inflate(R.menu.toolbar_winboll_shared_main, menu); | ||||
|         } | ||||
|         if (WinBollApplication.isDebug()) { | ||||
|             getMenuInflater().inflate(R.menu.toolbar_studio_debug, menu); | ||||
|         } | ||||
|         return super.onCreateOptionsMenu(menu); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean onOptionsItemSelected(MenuItem item) { | ||||
|         LogUtils.d(TAG, "onOptionsItemSelected"); | ||||
|         if (item.getItemId() == R.id.item_log) { | ||||
|             //WinBollActivityManager.getInstance().printAvtivityListInfo(); | ||||
|             WinBollActivityManager.getInstance(this).startWinBollActivity(this, LogActivity.class); | ||||
|         } else if (item.getItemId() == R.id.item_exit) { | ||||
|             WinBollActivityManager.getInstance(this).finishAll(); | ||||
|         } else if (item.getItemId() == R.id.item_info) { | ||||
|             WinBollApplication application = (WinBollApplication) getApplication(); | ||||
|             application.getMyActivityLifecycleCallbacks().showActivityeInfo(); | ||||
|         } else if (item.getItemId() == R.id.item_exitdebug) { | ||||
|             AboutView.setApp2NormalMode(getApplicationContext()); | ||||
|         } else if (item.getItemId() == R.id.item_about) { | ||||
|             startAboutActivity(); | ||||
|         } else if (item.getItemId() == android.R.id.home) { | ||||
|             WinBollActivityManager.getInstance(this).finish(this); | ||||
|         } | ||||
|         // else if (item.getItemId() == android.R.id.home) { | ||||
|         // 回到主窗口速度缓慢,方法备用。现在用 WinBollActivityManager resumeActivity 和 finish 方法管理。 | ||||
|         // _mMainWinBollActivity 是 WinBollActivity 的静态属性 | ||||
|         // onCreate 函数下 _mMainWinBollActivity 为空时就用 _mMainWinBollActivity = this 赋值。 | ||||
|         //startWinBollActivity(new Intent(_mMainWinBollActivity, _mMainWinBollActivity.getClass()), _mMainWinBollActivity.getTag(), _mMainWinBollActivity); | ||||
|         //} | ||||
|         return super.onOptionsItemSelected(item); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onBackPressed() { | ||||
|         //super.onBackPressed(); | ||||
|         //ToastUtils.show("onBackPressed"); | ||||
|         WinBollActivityManager.getInstance(this).finish(this); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /*public void getInstanse() { | ||||
|      startWinBollActivity(new Intent(), getTag(), null); | ||||
|      }*/ | ||||
|  | ||||
|     // | ||||
|     // activity: 为 null 时, | ||||
|     // intent.putExtra 函数 "tag" 参数为 tag | ||||
|     // activity: 不为 null 时, | ||||
|     // intent.putExtra 函数 "tag" 参数为 activity.getTag() | ||||
|     // | ||||
| //    protected <T extends WinBollActivity> void startWinBollActivity(Intent intent, String tag, T activity) { | ||||
| //        LogUtils.d(TAG, "startWinBollActivityForResult tag " + tag); | ||||
| //        //ToastUtils.show("startWinBollActivityForResult tag " + tag); | ||||
| //        //打开多任务窗口 flags | ||||
| //        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); | ||||
| //        intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); | ||||
| //        if (activity != null) { | ||||
| //            intent.putExtra("tag", activity.getTag()); | ||||
| //        } else { | ||||
| //            intent.putExtra("tag", tag); | ||||
| //        } | ||||
| //        //ToastUtils.show("super.startActivityForResult(intent, requestCode); tag " + tag); | ||||
| //        LogUtils.d(TAG, "startActivityForResult(intent, requestCode);" + tag); | ||||
| //        startActivity(intent); | ||||
| //    } | ||||
|  | ||||
|     // | ||||
|     // activity: 为 null 时, | ||||
|     // intent.putExtra 函数 "tag" 参数为 tag | ||||
|     // activity: 不为 null 时, | ||||
|     // intent.putExtra 函数 "tag" 参数为 activity.getTag() | ||||
|     // | ||||
|     protected <T extends AboutActivity> void startAboutActivity() { | ||||
|         Intent intent = new Intent(this, AboutActivity.class); | ||||
|         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); | ||||
|         intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); | ||||
|         intent.putExtra("tag", AboutActivity.TAG); | ||||
|         startActivity(intent); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void startActivity(Intent intent) { | ||||
|         //绑定唯一标识 tag,存在 则根据 taskId 移动到前台 | ||||
|         String tag = intent.getStringExtra("tag"); | ||||
|         //ToastUtils.show("startActivityForResult tag " + tag); | ||||
|         //WinBollActivityManager.getInstance(this).printAvtivityListInfo(); | ||||
|         if (WinBollActivityManager.getInstance(this).isActive(tag)) { | ||||
|             //ToastUtils.show("resumeActivity"); | ||||
|             LogUtils.d(TAG, "resumeActivity"); | ||||
|             WinBollActivityManager.getInstance(this).resumeActivity(this, tag); | ||||
|         } else { | ||||
|             //ToastUtils.show("super.startActivity(intent);"); | ||||
|             super.startActivity(intent); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onActivityResult(int requestCode, int targetFragment, Intent data) { | ||||
|         LogUtils.d(TAG, "onActivityResult"); | ||||
|         switch (requestCode) { | ||||
|             case REQUEST_LOG_ACTIVITY : { | ||||
|                     LogUtils.d(TAG, "REQUEST_LOG_ACTIVITY"); | ||||
|                     break; | ||||
|                 } | ||||
|             default : { | ||||
|                     super.onActivityResult(requestCode, targetFragment, data); | ||||
|                 } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public boolean isAddWinBollInfoMenu() { | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     abstract public String getTag(); | ||||
|     abstract protected Toolbar initToolBar(); | ||||
|     abstract protected boolean isEnableDisplayHomeAsUp(); | ||||
|     abstract protected boolean isAddWinBollToolBar(); | ||||
| } | ||||
| @@ -0,0 +1,315 @@ | ||||
| package cc.winboll.studio.shared.app; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/08/12 16:09:15 | ||||
|  * @Describe 应用活动窗口管理器 | ||||
|  * 参考 : | ||||
|  * android 类似微信小程序多任务窗口 及 设置 TaskDescription 修改 icon 和 label | ||||
|  * https://blog.csdn.net/qq_29364417/article/details/109379915?app_version=6.4.2&code=app_1562916241&csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22109379915%22%2C%22source%22%3A%22weixin_38986226%22%7D&uLinkId=usr1mkqgl919blen&utm_source=app | ||||
|  */ | ||||
| import android.app.ActivityManager; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import androidx.core.app.TaskStackBuilder; | ||||
| import cc.winboll.studio.shared.log.LogUtils; | ||||
| import java.util.HashMap; | ||||
| import java.util.Iterator; | ||||
| import java.util.Map; | ||||
|  | ||||
| public class WinBollActivityManager { | ||||
|  | ||||
|     public static final String TAG = "WinBollActivityManager"; | ||||
|  | ||||
|     Context mContext; | ||||
|     static WinBollActivityManager _mWinBollActivityManager; | ||||
|     static Map<String, WinBollActivity> _mapActivityList; | ||||
|     //static ArrayList<WinBollActivity> _mWinBollActivityList; | ||||
|  | ||||
|     public WinBollActivityManager(Context context) { | ||||
|         mContext = context; | ||||
|         LogUtils.d(TAG, "WinBollActivityManager()"); | ||||
|         _mapActivityList = new HashMap<String, WinBollActivity>(); | ||||
|         //_mWinBollActivityList = new ArrayList<WinBollActivity>(); | ||||
|     } | ||||
|  | ||||
|     public static synchronized WinBollActivityManager getInstance(Context context) { | ||||
|         LogUtils.d(TAG, "getInstance"); | ||||
|         if (_mWinBollActivityManager == null) { | ||||
|             LogUtils.d(TAG, "_mWinBollActivityManager == null"); | ||||
|             _mWinBollActivityManager = new WinBollActivityManager(context); | ||||
|         } | ||||
|         return _mWinBollActivityManager; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 把Activity添加到管理中 | ||||
|      */ | ||||
|     public <T extends WinBollActivity> void add(T activity) { | ||||
|         /*for (int i = 0; i < _mWinBollActivityList.size(); i++) { | ||||
|          LogUtils.d(TAG, String.format("add for i %d\nget(i).getTag() %s", i, _mWinBollActivityList.get(i).getTag())); | ||||
|          if (_mWinBollActivityList.get(i).getTag().equals(activity.getTag())) { | ||||
|          _mWinBollActivityList.add(i, activity); | ||||
|          _mWinBollActivityList.remove(i); | ||||
|          LogUtils.d(TAG, String.format("Replace activity : %s\nSize %d", activity.getTag(), _mWinBollActivityList.size())); | ||||
|          return; | ||||
|          } | ||||
|          }*/ | ||||
|         if (isActive(activity.getTag())) { | ||||
|             LogUtils.d(TAG, String.format("add(...) %s is active.", activity.getTag())); | ||||
|         } else { | ||||
|             _mapActivityList.put(activity.getTag(), activity); | ||||
|             LogUtils.d(TAG, String.format("Add activity : %s\n_mapActivityList.size() : %d", activity.getTag(), _mapActivityList.size())); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     // | ||||
|     // activity: 为 null 时, | ||||
|     // intent.putExtra 函数 "tag" 参数为 tag | ||||
|     // activity: 不为 null 时, | ||||
|     // intent.putExtra 函数 "tag" 参数为 activity.getTag() | ||||
|     // | ||||
|     public <T extends WinBollActivity> void startWinBollActivity(Context context, Class<T> clazz) { | ||||
|         try { | ||||
|             // 如果窗口已存在就重启窗口 | ||||
|             String tag = clazz.newInstance().getTag(); | ||||
|             if (isActive(tag)) { | ||||
|                 resumeActivity(context, tag); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             // 新建一个任务窗口 | ||||
|             Intent intent = new Intent(context, clazz); | ||||
|             //打开多任务窗口 flags | ||||
|             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); | ||||
|             intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); | ||||
|             intent.putExtra("tag", tag); | ||||
|             mContext.startActivity(intent); | ||||
|         } catch (InstantiationException | IllegalAccessException e) { | ||||
|             LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public <T extends WinBollActivity> void startWinBollActivity(Context context, Intent intent, Class<T> clazz) { | ||||
|         try { | ||||
|             // 如果窗口已存在就重启窗口 | ||||
|             String tag = clazz.newInstance().getTag(); | ||||
|             if (isActive(tag)) { | ||||
|                 resumeActivity(context, tag); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             // 新建一个任务窗口 | ||||
|             //Intent intent = new Intent(context, clazz); | ||||
|             //打开多任务窗口 flags | ||||
|             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); | ||||
|             intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); | ||||
|             intent.putExtra("tag", tag); | ||||
|             mContext.startActivity(intent); | ||||
|         } catch (InstantiationException | IllegalAccessException e) { | ||||
|             LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 判断 tag绑定的 MyActivity是否存在 | ||||
|      */ | ||||
|     public boolean isActive(String tag) { | ||||
|         printAvtivityListInfo(); | ||||
|         WinBollActivity activity = getWinBollActivity(tag); | ||||
|         if (activity != null) { | ||||
|             LogUtils.d(TAG, "isActive(...) activity != null tag " + tag); | ||||
|             //ToastUtils.show("activity != null tag " + tag); | ||||
|             //判断是否为 BaseActivity,如果已经销毁,则移除 | ||||
|             if (activity.isFinishing() || activity.isDestroyed()) { | ||||
|                 _mapActivityList.remove(activity.getTag()); | ||||
|                 //_mWinBollActivityList.remove(activity); | ||||
|                 LogUtils.d(TAG, String.format("isActive(...) remove activity.\ntag : %s", tag)); | ||||
|                 return false; | ||||
|             } else { | ||||
|                 LogUtils.d(TAG, String.format("isActive(...) activity is exist.\ntag : %s", tag)); | ||||
|                 return true; | ||||
|             } | ||||
|         } else { | ||||
|             LogUtils.d(TAG, String.format("isActive(...) activity == null\ntag : %s", tag)); | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     static WinBollActivity getWinBollActivity(String tag) { | ||||
|         return _mapActivityList.get(tag); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 找到tag 绑定的 BaseActivity ,通过 getTaskId() 移动到前台 | ||||
|      */ | ||||
|     public <T extends WinBollActivity> void resumeActivity(Context context, String tag) { | ||||
|         LogUtils.d(TAG, "resumeActivty"); | ||||
|         T activity = (T)getWinBollActivity(tag); | ||||
|         LogUtils.d(TAG, "activity " + activity.getTag()); | ||||
|         if (activity != null && !activity.isFinishing() && !activity.isDestroyed()) { | ||||
|             resumeActivity(context, activity); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 找到tag 绑定的 BaseActivity ,通过 getTaskId() 移动到前台 | ||||
|      */ | ||||
|     public <T extends WinBollActivity> void resumeActivity(Context context, T activity) { | ||||
|         ActivityManager am = (ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE); | ||||
|         //返回启动它的根任务(home 或者 MainActivity) | ||||
|         Intent intent = new Intent(context, activity.getClass()); | ||||
|         TaskStackBuilder stackBuilder = TaskStackBuilder.create(context); | ||||
|         stackBuilder.addNextIntentWithParentStack(intent); | ||||
|         stackBuilder.startActivities(); | ||||
|         //moveTaskToFront(YourTaskId, 0); | ||||
|         LogUtils.d(TAG, "am.moveTaskToFront"); | ||||
|         //ToastUtils.show("resumeActivity am.moveTaskToFront"); | ||||
|         am.moveTaskToFront(activity.getTaskId(), ActivityManager.MOVE_TASK_NO_USER_ACTION); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * 结束所有 Activity | ||||
|      */ | ||||
|     public void finishAll() { | ||||
|         try { | ||||
|             LogUtils.d(TAG, "finishAll no yet."); | ||||
|             //ToastUtils.show(String.format("finishAll() size : %d", _mWinBollActivityList.size())); | ||||
| //            for (int i = _mWinBollActivityList.size() - 1; i > -1; i--) { | ||||
| //                WinBollActivity activity = _mWinBollActivityList.get(i); | ||||
| //                ToastUtils.show("finishAll() activity"); | ||||
| //                if (activity != null && !activity.isFinishing() && !activity.isDestroyed()) { | ||||
| //                    //ToastUtils.show("activity != null ..."); | ||||
| //                    if (WinBollApplication.getWinBollUI_TYPE() == WinBollApplication.WinBollUI_TYPE.Service) { | ||||
| //                        // 结束窗口和最近任务栏, 建议前台服务类应用使用,可以方便用户再次调用 UI 操作。 | ||||
| //                        activity.finishAndRemoveTask(); | ||||
| //                        //ToastUtils.show("finishAll() activity.finishAndRemoveTask();"); | ||||
| //                    } else if (WinBollApplication.getWinBollUI_TYPE() == WinBollApplication.WinBollUI_TYPE.Aplication) { | ||||
| //                        // 结束窗口保留最近任务栏,建议前台服务类应用使用,可以保持应用的系统自觉性。 | ||||
| //                        activity.finish(); | ||||
| //                        //ToastUtils.show("finishAll() activity.finish();"); | ||||
| //                    } else { | ||||
| //                        ToastUtils.show("WinBollApplication.WinBollUI_TYPE error."); | ||||
| //                    } | ||||
| //                } | ||||
| //            } | ||||
|         } catch (Exception e) { | ||||
|             LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 结束指定Activity | ||||
|      */ | ||||
|     public <T extends WinBollActivity> void finish(T activity) { | ||||
|         try { | ||||
|             if (activity != null && !activity.isFinishing() && !activity.isDestroyed()) { | ||||
|                 //根据tag 移除 MyActivity | ||||
|                 //String tag= activity.getTag(); | ||||
|                 //_mWinBollActivityList.remove(tag); | ||||
|                 //ToastUtils.show("remove"); | ||||
|                 //ToastUtils.show("_mWinBollActivityArrayMap.size() " + Integer.toString(_mWinBollActivityArrayMap.size())); | ||||
|  | ||||
|                 // 窗口回调规则: | ||||
|                 // [] 当前窗口位置 >> 调度出的窗口位置 | ||||
|                 // ★:[0] 1 2 3 4 >> 1 | ||||
|                 // ★:0 1 [2] 3 4 >> 1 | ||||
|                 // ★:0 1 2 [3] 4 >> 2 | ||||
|                 // ★:0 1 2 3 [4] >> 3 | ||||
|                 // ★:[0] >> 直接关闭当前窗口 | ||||
|                 LogUtils.d(TAG, "finish no yet."); | ||||
|                 WinBollActivity preActivity = getPreActivity(activity); | ||||
|                 activity.finish(); | ||||
|                 if (preActivity != null) { | ||||
|                     resumeActivity(mContext, preActivity); | ||||
|                 } | ||||
|  | ||||
| //                for (int i = 0; i < _mWinBollActivityList.size(); i++) { | ||||
| //                    if (_mWinBollActivityList.get(i).getTag().equals(activity.getTag())) { | ||||
| //                        //ToastUtils.show(String.format("equals i : %d\nTag : %s\nSize : %d", i, activity.getTag(), _mWinBollActivityList.size())); | ||||
| //                        if (i == 0) { | ||||
| //                            finishAll(); | ||||
| //                            //ToastUtils.show("finish finishAll"); | ||||
| //                            return; | ||||
| //                        } | ||||
| //                        if (i > 0) { | ||||
| //                            activity.finish(); | ||||
| //                            resumeActivity(mContext, _mWinBollActivityList.get(i - 1)); | ||||
| //                            return; | ||||
| //                        } | ||||
| //                    } | ||||
| //                } | ||||
|             } | ||||
|  | ||||
|         } catch (Exception e) { | ||||
|             LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     WinBollActivity getPreActivity(WinBollActivity activity) { | ||||
|         try { | ||||
|             boolean bingo = false; | ||||
|             WinBollActivity preActivity = null; | ||||
|             for (Map.Entry<String, WinBollActivity> entity : _mapActivityList.entrySet()) { | ||||
|                 if (entity.getKey().equals(activity.getTag())) { | ||||
|                     bingo = true; | ||||
|                     LogUtils.d(TAG, "bingo"); | ||||
|                     break; | ||||
|                 } | ||||
|                 preActivity = entity.getValue(); | ||||
|             } | ||||
|  | ||||
|             if (bingo) { | ||||
|                 return preActivity; | ||||
|             } | ||||
|         } catch (Exception e) { | ||||
|             LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|         } | ||||
|  | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     public <T extends WinBollActivity> boolean registeRemove(T activity) { | ||||
| //        for (int i = 0; i < _mWinBollActivityList.size(); i++) { | ||||
| //            if (registeRemove(activity, i)) { | ||||
| //                return true; | ||||
| //            } | ||||
| //        } | ||||
|         WinBollActivity activityTest = _mapActivityList.get(activity.getTag()); | ||||
|         if (activityTest != null) { | ||||
|             _mapActivityList.remove(activity.getTag()); | ||||
|             return true; | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
| //    public <T extends WinBollActivity> boolean registeRemove(T activity, int position) { | ||||
| //        if (_mWinBollActivityList.get(position) == activity) { | ||||
| //            _mWinBollActivityList.remove(position); | ||||
| //            //ToastUtils.show(String.format("registeRemove remove.\nTag %s\nposition %d", activity.getTag(), position)); | ||||
| //            return true; | ||||
| //        } | ||||
| //        return false; | ||||
| //    } | ||||
|  | ||||
|     public static void printAvtivityListInfo() { | ||||
|         //LogUtils.d(TAG, "printAvtivityListInfo"); | ||||
|         if (!_mapActivityList.isEmpty()) { | ||||
|             StringBuilder sb = new StringBuilder("Map entries : " + Integer.toString(_mapActivityList.size())); | ||||
|             Iterator<Map.Entry<String, WinBollActivity>> iterator = _mapActivityList.entrySet().iterator(); | ||||
|             while (iterator.hasNext()) { | ||||
|                 Map.Entry<String, WinBollActivity> entry = iterator.next(); | ||||
|                 sb.append("\nKey: " + entry.getKey() + ", \nValue: " + entry.getValue().getTag()); | ||||
|                 //ToastUtils.show("\nKey: " + entry.getKey() + ", Value: " + entry.getValue().getTag()); | ||||
|             } | ||||
|             sb.append("\nMap entries end."); | ||||
|             LogUtils.d(TAG, sb.toString()); | ||||
|         } else { | ||||
|             LogUtils.d(TAG, "The map is empty."); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,88 @@ | ||||
| package cc.winboll.studio.shared.app; | ||||
|  | ||||
| import android.app.Application; | ||||
| import android.view.Gravity; | ||||
| import cc.winboll.studio.shared.bean.DebugBean; | ||||
| import cc.winboll.studio.shared.log.LogUtils; | ||||
| import com.hjq.toast.ToastUtils; | ||||
| import com.hjq.toast.style.WhiteToastStyle; | ||||
|  | ||||
| public class WinBollApplication extends Application { | ||||
|  | ||||
|     public static final String TAG = "WinBollApplication"; | ||||
|  | ||||
|     public static final String _ACTION_DEBUGVIEW = WinBollApplication.class.getName() + "_ACTION_DEBUGVIEW"; | ||||
|  | ||||
|     // 保存当前 WINBOLL 应用是否处于调试状态。 | ||||
|     volatile static boolean isDebug; | ||||
|     static WinBollApplication _WinBollApplication; | ||||
|  | ||||
|     MyActivityLifecycleCallbacks mMyActivityLifecycleCallbacks; | ||||
|  | ||||
|     synchronized public static void setIsDebug(boolean isDebug) { | ||||
|         WinBollApplication.isDebug = isDebug; | ||||
|     } | ||||
|  | ||||
|     public static boolean isDebug() { | ||||
|         return isDebug; | ||||
|     } | ||||
|  | ||||
|     public static enum WinBollUI_TYPE { | ||||
|         Aplication, // 退出应用后,保持最近任务栏任务记录主窗口 | ||||
|         Service // 退出应用后,清理所有最近任务栏任务记录窗口 | ||||
|         }; | ||||
|  | ||||
|     // 应用类型标志 | ||||
|     volatile static WinBollUI_TYPE _mWinBollUI_TYPE = WinBollUI_TYPE.Service; | ||||
|  | ||||
|     // | ||||
|     // 设置 WinBoll 应用 UI 类型 | ||||
|     // | ||||
|     public static void setWinBollUI_TYPE(WinBollUI_TYPE mWinBollUI_TYPE) { | ||||
|         _mWinBollUI_TYPE = mWinBollUI_TYPE; | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     // 获取 WinBoll 应用 UI 类型 | ||||
|     // | ||||
|     public static WinBollUI_TYPE getWinBollUI_TYPE() { | ||||
|         return _mWinBollUI_TYPE; | ||||
|     } | ||||
|  | ||||
|     MyActivityLifecycleCallbacks getMyActivityLifecycleCallbacks() { | ||||
|         return mMyActivityLifecycleCallbacks; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onCreate() { | ||||
|         super.onCreate(); | ||||
|         _WinBollApplication = this; | ||||
|         // 应用环境初始化, 基本调试环境 | ||||
|         // | ||||
|         CrashHandler.init(this); | ||||
|         LogUtils.init(this); | ||||
|         // 初始化 Toast 框架 | ||||
|         ToastUtils.init(this); | ||||
|         // 设置 Toast 布局样式 | ||||
|         //ToastUtils.setView(R.layout.view_toast); | ||||
|         ToastUtils.setStyle(new WhiteToastStyle()); | ||||
|         ToastUtils.setGravity(Gravity.BOTTOM, 0, 200); | ||||
|  | ||||
|         DebugBean debugBean = DebugBean.loadBean(this, DebugBean.class); | ||||
|         if (debugBean == null) { | ||||
|             //ToastUtils.show("debugBean == null"); | ||||
|             setIsDebug(false); | ||||
|         } else { | ||||
|             //ToastUtils.show("saveDebugStatus(" + String.valueOf(debugBean.isDebuging()) + ")"); | ||||
|             setIsDebug(debugBean.isDebuging()); | ||||
|         } | ||||
|  | ||||
|         // 应用运行状态环境设置 | ||||
|         // | ||||
|         mMyActivityLifecycleCallbacks = new MyActivityLifecycleCallbacks(this); | ||||
|         registerActivityLifecycleCallbacks(mMyActivityLifecycleCallbacks); | ||||
|         // 设置默认 WinBoll 应用 UI 类型 | ||||
|         setWinBollUI_TYPE(WinBollUI_TYPE.Service); | ||||
|         //ToastUtils.show("WinBollUI_TYPE " + getWinBollUI_TYPE()); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,75 @@ | ||||
| package cc.winboll.studio.shared.bean; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/12/25 12:38:07 | ||||
|  * @Describe 应用调试配置类 | ||||
|  */ | ||||
| import android.util.JsonReader; | ||||
| import android.util.JsonWriter; | ||||
| import cc.winboll.studio.shared.app.BaseBean; | ||||
| import java.io.IOException; | ||||
|  | ||||
| public class DebugBean extends BaseBean { | ||||
|  | ||||
|     public static final String TAG = "DebugBean"; | ||||
|  | ||||
|     // 应用是否处于正在调试状态 | ||||
|     // | ||||
|     boolean isDebuging = false; | ||||
|      | ||||
|     public DebugBean() { | ||||
|         this.isDebuging = false; | ||||
|     } | ||||
|  | ||||
|     public DebugBean(boolean isDebuging) { | ||||
|         this.isDebuging = isDebuging; | ||||
|     } | ||||
|  | ||||
|     public void setIsDebuging(boolean isDebuging) { | ||||
|         this.isDebuging = isDebuging; | ||||
|     } | ||||
|  | ||||
|     public boolean isDebuging() { | ||||
|         return isDebuging; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getName() { | ||||
|         return DebugBean.class.getName(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { | ||||
|         super.writeThisToJsonWriter(jsonWriter); | ||||
|         DebugBean bean = this; | ||||
|         jsonWriter.name("isDebuging").value(bean.isDebuging()); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException { | ||||
|         if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else { | ||||
|             if (name.equals("isDebuging")) { | ||||
|                 setIsDebuging(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; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,60 @@ | ||||
| package cc.winboll.studio.shared.git; | ||||
| import android.content.Context; | ||||
| import cc.winboll.studio.shared.app.FileUtils; | ||||
| import cc.winboll.studio.shared.log.LogUtils; | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/12/09 12:30:59 | ||||
|  * @Describe 应用文件接口基础类 | ||||
|  */ | ||||
| public interface IAPPFiles { | ||||
|  | ||||
|     public static final String TAG = "IAPPFiles"; | ||||
|     public static final String UUID_LOGUTILS_JSON = "cef58919-88a6-47c4-9e10-44d8c810328e"; | ||||
|     public static final String UUID_WINBOLLCLIENTSERVICEBEAN_JSON = "d7816eda-9724-4a86-b9d5-43eeee7be76d"; | ||||
|  | ||||
|     public static final String APPRoot = ""; | ||||
|  | ||||
|     HashFile getFile(String szAPPFilesURI); | ||||
|  | ||||
|     class HashFile { | ||||
|         static Map<String, String> _mapFiles = new HashMap<String, String>(); | ||||
|          | ||||
|         volatile static HashFile _mHashFile; | ||||
|         File mFile; | ||||
|         static String _mFilesRoot = ""; | ||||
|  | ||||
|         HashFile(Context context) { | ||||
|             _mapFiles.put(UUID_WINBOLLCLIENTSERVICEBEAN_JSON, "/BaseBean/cc.winboll.studio.shared.service.WinBollClientServiceBean.json"); | ||||
|             _mapFiles.put(UUID_LOGUTILS_JSON, "/LogUtils/LogUtils.json"); | ||||
|         } | ||||
|  | ||||
|         synchronized static HashFile getInstance(Context context, String uuid) throws IOException { | ||||
|             if (_mHashFile == null) { | ||||
|                 _mHashFile = new HashFile(context); | ||||
|             } | ||||
|              | ||||
|             try { | ||||
|                 _mHashFile.mFile = new File(context.getExternalFilesDir("") + _mapFiles.get(uuid)); | ||||
|             } catch (Exception e) { | ||||
|                 LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|             } finally { | ||||
|                 if (_mHashFile.mFile == null) { | ||||
|                     LogUtils.d(TAG, "_mHashFile.mFile == null"); | ||||
|                 } | ||||
|             } | ||||
|             return _mHashFile; | ||||
|         } | ||||
|  | ||||
|         static File getFileByUUID(Context context, String uuid) throws IOException { | ||||
|             return new File(getInstance(context, uuid)._mapFiles.get(uuid)); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     //class HashFileManager | ||||
| } | ||||
| @@ -0,0 +1,65 @@ | ||||
| package cc.winboll.studio.shared.log; | ||||
|  | ||||
| import android.os.Bundle; | ||||
| import androidx.appcompat.widget.Toolbar; | ||||
| import cc.winboll.studio.R; | ||||
| import cc.winboll.studio.shared.app.WinBollActivity; | ||||
| import cc.winboll.studio.shared.ads.ADsView; | ||||
| import cc.winboll.studio.shared.app.WinBollApplication; | ||||
| import android.view.View; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/08/12 15:07:58 | ||||
|  * @Describe WinBoll 应用日志窗口 | ||||
|  */ | ||||
| public class LogActivity extends WinBollActivity { | ||||
|  | ||||
|     public static final String TAG = "LogActivity"; | ||||
|  | ||||
|     LogView mLogView; | ||||
|     //ADsView mADsView; | ||||
|  | ||||
|     @Override | ||||
|     protected boolean isEnableDisplayHomeAsUp() { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|         LogUtils.d(TAG, "onCreate"); | ||||
|         super.onCreate(savedInstanceState); | ||||
|         setContentView(R.layout.activity_log); | ||||
|         mLogView = findViewById(R.id.logview); | ||||
|         //mADsView = findViewById(R.id.adsview); | ||||
|         //mADsView.loadUrl("https://www.winboll.cc"); | ||||
|         //mLogView.setVisibility(WinBollApplication.isDebug()?View.GONE:View.VISIBLE); | ||||
|         //mADsView.setVisibility(WinBollApplication.isDebug()?View.GONE:View.VISIBLE); | ||||
|          | ||||
|         if(WinBollApplication.isDebug()) { mLogView.start(); } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onResume() { | ||||
|         LogUtils.d(TAG, "onResume"); | ||||
|         super.onResume(); | ||||
|         mLogView.start(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected boolean isAddWinBollToolBar() { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected Toolbar initToolBar() { | ||||
|         LogUtils.d(TAG, "initToolBar"); | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getTag() { | ||||
|         LogUtils.d(TAG, "getTag"); | ||||
|         return TAG; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,370 @@ | ||||
| package cc.winboll.studio.shared.log; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/08/12 13:44:06 | ||||
|  * @Describe LogUtils | ||||
|  * @Describe 应用日志类 | ||||
|  */ | ||||
| import android.content.Context; | ||||
| import cc.winboll.studio.shared.app.FileUtils; | ||||
| import cc.winboll.studio.shared.app.WinBollApplication; | ||||
| import dalvik.system.DexFile; | ||||
| import java.io.BufferedReader; | ||||
| import java.io.BufferedWriter; | ||||
| import java.io.File; | ||||
| import java.io.FileInputStream; | ||||
| import java.io.FileOutputStream; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStreamReader; | ||||
| import java.io.OutputStreamWriter; | ||||
| import java.lang.reflect.Field; | ||||
| import java.lang.reflect.Modifier; | ||||
| import java.text.SimpleDateFormat; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Enumeration; | ||||
| import java.util.HashMap; | ||||
| import java.util.Iterator; | ||||
| import java.util.List; | ||||
| import java.util.Locale; | ||||
| import java.util.Map; | ||||
|  | ||||
|  | ||||
| public class LogUtils { | ||||
|  | ||||
|     public static final String TAG = "LogUtils"; | ||||
|  | ||||
|     public static enum LOG_LEVEL { Off, Error, Warn, Info, Debug, Verbose } | ||||
|  | ||||
|     static volatile boolean _IsInited = false; | ||||
|     static Context _mContext; | ||||
|     // 日志显示时间格式 | ||||
|     static SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat("[yyyyMMdd_HHmmSS]", Locale.getDefault()); | ||||
|     // 应用日志文件夹 | ||||
|     static File _mfLogCacheDir; | ||||
|     static File _mfLogDataDir; | ||||
|     // 应用日志文件 | ||||
|     static File _mfLogCatchFile; | ||||
|     static File _mfLogUtilsBeanFile; | ||||
|     static LogUtilsBean _mLogUtilsBean; | ||||
|     public static Map<String, Boolean> mapTAGList = new HashMap<String, Boolean>(); | ||||
|  | ||||
|     // | ||||
|     // 初始化函数 | ||||
|     // | ||||
|     public static void init(Context context) { | ||||
|         _mContext = context; | ||||
|         init(context, LOG_LEVEL.Off); | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     // 初始化函数 | ||||
|     // | ||||
|     public static void init(Context context, LOG_LEVEL logLevel) { | ||||
|         if (WinBollApplication.isDebug()) { | ||||
|             // 初始化日志缓存文件路径 | ||||
|             _mfLogCacheDir = new File(context.getApplicationContext().getExternalCacheDir(), TAG); | ||||
|             if (!_mfLogCacheDir.exists()) { | ||||
|                 _mfLogCacheDir.mkdirs(); | ||||
|             } | ||||
|             _mfLogCatchFile = new File(_mfLogCacheDir, "log.txt"); | ||||
|  | ||||
|             // 初始化日志配置文件路径 | ||||
|             _mfLogDataDir = context.getApplicationContext().getExternalFilesDir(TAG); | ||||
|             if (!_mfLogDataDir.exists()) { | ||||
|                 _mfLogDataDir.mkdirs(); | ||||
|             } | ||||
|             _mfLogUtilsBeanFile = new File(_mfLogDataDir, TAG + ".json"); | ||||
|         } else { | ||||
|             // 初始化日志缓存文件路径 | ||||
|             _mfLogCacheDir = new File(context.getApplicationContext().getCacheDir(), TAG); | ||||
|             if (!_mfLogCacheDir.exists()) { | ||||
|                 _mfLogCacheDir.mkdirs(); | ||||
|             } | ||||
|             _mfLogCatchFile = new File(_mfLogCacheDir, "log.txt"); | ||||
|  | ||||
|             // 初始化日志配置文件路径 | ||||
|             _mfLogDataDir = new File(context.getApplicationContext().getFilesDir(), TAG); | ||||
|             if (!_mfLogDataDir.exists()) { | ||||
|                 _mfLogDataDir.mkdirs(); | ||||
|             } | ||||
|             _mfLogUtilsBeanFile = new File(_mfLogDataDir, TAG + ".json"); | ||||
|         } | ||||
|  | ||||
| //        Toast.makeText(context, | ||||
| //                       "_mfLogUtilsBeanFile : " + _mfLogUtilsBeanFile | ||||
| //                       + "\n_mfLogCatchFile : " + _mfLogCatchFile, | ||||
| //                       Toast.LENGTH_SHORT).show(); | ||||
| // | ||||
|         _mLogUtilsBean = LogUtilsBean.loadBeanFromFile(_mfLogUtilsBeanFile.getPath(), LogUtilsBean.class); | ||||
|         if (_mLogUtilsBean == null) { | ||||
|             _mLogUtilsBean = new LogUtilsBean(); | ||||
|             _mLogUtilsBean.saveBeanToFile(_mfLogUtilsBeanFile.getPath(), _mLogUtilsBean); | ||||
|         } | ||||
|  | ||||
|         // 加载当前应用下的所有类的 TAG | ||||
|         addClassTAGList(); | ||||
|         loadTAGBeanSettings(); | ||||
|         _IsInited = true; | ||||
|         LogUtils.d(TAG, String.format("mapTAGList : %s", mapTAGList.toString())); | ||||
|     } | ||||
|  | ||||
|     public static Map<String, Boolean> getMapTAGList() { | ||||
|         return mapTAGList; | ||||
|     } | ||||
|  | ||||
|     static void loadTAGBeanSettings() { | ||||
|         ArrayList<LogUtilsClassTAGBean> list = new ArrayList<LogUtilsClassTAGBean>(); | ||||
|         LogUtilsClassTAGBean.loadBeanList(_mContext, list, LogUtilsClassTAGBean.class); | ||||
|         for (int i = 0; i < list.size(); i++) { | ||||
|             LogUtilsClassTAGBean beanSetting = list.get(i); | ||||
|             for (Map.Entry<String, Boolean> entry : mapTAGList.entrySet()) { | ||||
|                 if (entry.getKey().equals(beanSetting.getTag())) { | ||||
|                     entry.setValue(beanSetting.getEnable()); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     static void saveTAGBeanSettings() { | ||||
|         ArrayList<LogUtilsClassTAGBean> list = new ArrayList<LogUtilsClassTAGBean>(); | ||||
|         for (Map.Entry<String, Boolean> entry : mapTAGList.entrySet()) { | ||||
|             list.add(new LogUtilsClassTAGBean(entry.getKey(), entry.getValue())); | ||||
|         } | ||||
|         LogUtilsClassTAGBean.saveBeanList(_mContext, list, LogUtilsClassTAGBean.class); | ||||
|     } | ||||
|  | ||||
|     static void addClassTAGList() { | ||||
|         //ClassLoader classLoader = getClass().getClassLoader(); | ||||
|         try { | ||||
|             //String packageName = context.getPackageName(); | ||||
|             String packageNamePrefix = "cc.winboll.studio"; | ||||
|             List<String> classNames = new ArrayList<>(); | ||||
|             String apkPath = _mContext.getPackageCodePath(); | ||||
|             //Log.d("APK_PATH", "The APK path is: " + apkPath); | ||||
|             LogUtils.d(TAG, String.format("apkPath : %s", apkPath)); | ||||
|             //String apkPath = "/data/app/" + packageName + "-"; | ||||
|  | ||||
|             //DexFile dexfile = new DexFile(apkPath + "1/base.apk"); | ||||
|             DexFile dexfile = new DexFile(apkPath); | ||||
|  | ||||
|             int countTemp = 0; | ||||
|             Enumeration<String> entries = dexfile.entries(); | ||||
|             while (entries.hasMoreElements()) { | ||||
|                 countTemp++; | ||||
|                 String className = entries.nextElement(); | ||||
|                 if (className.startsWith(packageNamePrefix)) { | ||||
|                     classNames.add(className); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             LogUtils.d(TAG, String.format("countTemp : %d\nClassNames size : %d", countTemp, classNames.size())); | ||||
|  | ||||
|             for (String className : classNames) { | ||||
|                 try { | ||||
|                     Class<?> clazz = Class.forName(className); | ||||
|                     Field[] fields = clazz.getDeclaredFields(); | ||||
|                     for (Field field : fields) { | ||||
|                         if (Modifier.isStatic(field.getModifiers()) && Modifier.isPublic(field.getModifiers()) && field.getType() == String.class && "TAG".equals(field.getName())) { | ||||
|                             String tagValue = (String) field.get(null); | ||||
|                             //Log.d("TAG_INFO", "Class: " + className + ", TAG value: " + tagValue); | ||||
|                             //LogUtils.d(TAG, String.format("Tag Value : %s", tagValue)); | ||||
|                             //mapTAGList.put(tagValue, true); | ||||
|                             mapTAGList.put(tagValue, false); | ||||
|                         } | ||||
|                     } | ||||
|                 } catch (ClassNotFoundException | IllegalAccessException e) { | ||||
|                     LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|                     //Toast.makeText(context, TAG + " : " + e.getMessage(), Toast.LENGTH_SHORT).show(); | ||||
|                 } | ||||
|             } | ||||
|         } catch (IOException e) { | ||||
|             LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|             //Toast.makeText(context, TAG + " : " + e.getMessage(), Toast.LENGTH_SHORT).show(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static void setTAGListEnable(String tag, boolean isEnable) { | ||||
|         Iterator<Map.Entry<String, Boolean>> iterator = mapTAGList.entrySet().iterator(); | ||||
|         while (iterator.hasNext()) { | ||||
|             Map.Entry<String, Boolean> entry = iterator.next(); | ||||
|             if (tag.equals(entry.getKey())) { | ||||
|                 entry.setValue(isEnable); | ||||
|                 //System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue()); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         saveTAGBeanSettings(); | ||||
|         LogUtils.d(TAG, String.format("mapTAGList : %s", mapTAGList.toString())); | ||||
|     } | ||||
|  | ||||
|     public static void setALlTAGListEnable(boolean isEnable) { | ||||
|         Iterator<Map.Entry<String, Boolean>> iterator = mapTAGList.entrySet().iterator(); | ||||
|         while (iterator.hasNext()) { | ||||
|             Map.Entry<String, Boolean> entry = iterator.next(); | ||||
|             entry.setValue(isEnable); | ||||
|             //System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue()); | ||||
|         } | ||||
|         saveTAGBeanSettings(); | ||||
|         LogUtils.d(TAG, String.format("mapTAGList : %s", mapTAGList.toString())); | ||||
|     } | ||||
|  | ||||
|     public static void setLogLevel(LOG_LEVEL logLevel) { | ||||
|         LogUtils._mLogUtilsBean.setLogLevel(logLevel); | ||||
|         _mLogUtilsBean.saveBeanToFile(_mfLogUtilsBeanFile.getPath(), _mLogUtilsBean); | ||||
|     } | ||||
|  | ||||
|     public static LOG_LEVEL getLogLevel() { | ||||
|         return LogUtils._mLogUtilsBean.getLogLevel(); | ||||
|     } | ||||
|  | ||||
|     static boolean isLoggable(String tag, LOG_LEVEL logLevel) { | ||||
|         return _IsInited && mapTAGList.get(tag) && isInTheLevel(logLevel); | ||||
|     } | ||||
|  | ||||
|     static boolean isInTheLevel(LOG_LEVEL logLevel) { | ||||
|         return (LogUtils._mLogUtilsBean.getLogLevel().ordinal() == logLevel.ordinal() | ||||
|             || LogUtils._mLogUtilsBean.getLogLevel().ordinal() > logLevel.ordinal()); | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     // 获取应用日志文件夹 | ||||
|     // | ||||
|     public static File getLogCacheDir() { | ||||
|         return _mfLogCacheDir; | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     // 调试日志写入函数 | ||||
|     // | ||||
|     public static void e(String szTAG, String szMessage) { | ||||
|         if (isLoggable(szTAG, LogUtils.LOG_LEVEL.Error)) { | ||||
|             saveLog(szTAG, LogUtils.LOG_LEVEL.Error, szMessage); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     // 调试日志写入函数 | ||||
|     // | ||||
|     public static void w(String szTAG, String szMessage) { | ||||
|         if (isLoggable(szTAG, LogUtils.LOG_LEVEL.Warn)) { | ||||
|             saveLog(szTAG, LogUtils.LOG_LEVEL.Warn, szMessage); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     // 调试日志写入函数 | ||||
|     // | ||||
|     public static void i(String szTAG, String szMessage) { | ||||
|         if (isLoggable(szTAG, LogUtils.LOG_LEVEL.Info)) { | ||||
|             saveLog(szTAG, LogUtils.LOG_LEVEL.Info, szMessage); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     // 调试日志写入函数 | ||||
|     // | ||||
|     public static void d(String szTAG, String szMessage) { | ||||
|         if (isLoggable(szTAG, LogUtils.LOG_LEVEL.Debug)) { | ||||
|             saveLog(szTAG, LogUtils.LOG_LEVEL.Debug, szMessage); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     // 调试日志写入函数 | ||||
|     // 包含线程调试堆栈信息 | ||||
|     // | ||||
|     public static void d(String szTAG, String szMessage, StackTraceElement[] listStackTrace) { | ||||
|         if (isLoggable(szTAG, LogUtils.LOG_LEVEL.Debug)) { | ||||
|             StringBuilder sbMessage = new StringBuilder(szMessage); | ||||
|             sbMessage.append(" \nAt "); | ||||
|             sbMessage.append(listStackTrace[2].getMethodName()); | ||||
|             sbMessage.append(" ("); | ||||
|             sbMessage.append(listStackTrace[2].getFileName()); | ||||
|             sbMessage.append(":"); | ||||
|             sbMessage.append(listStackTrace[2].getLineNumber()); | ||||
|             sbMessage.append(")"); | ||||
|             saveLog(szTAG, LogUtils.LOG_LEVEL.Debug, sbMessage.toString()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     // 调试日志写入函数 | ||||
|     // 包含异常信息和线程调试堆栈信息 | ||||
|     // | ||||
|     public static void d(String szTAG, Exception e, StackTraceElement[] listStackTrace) { | ||||
|         if (isLoggable(szTAG, LogUtils.LOG_LEVEL.Debug)) { | ||||
|             StringBuilder sbMessage = new StringBuilder(e.getClass().toGenericString()); | ||||
|             sbMessage.append(" : "); | ||||
|             sbMessage.append(e.getMessage()); | ||||
|             sbMessage.append(" \nAt "); | ||||
|             sbMessage.append(listStackTrace[2].getMethodName()); | ||||
|             sbMessage.append(" ("); | ||||
|             sbMessage.append(listStackTrace[2].getFileName()); | ||||
|             sbMessage.append(":"); | ||||
|             sbMessage.append(listStackTrace[2].getLineNumber()); | ||||
|             sbMessage.append(")"); | ||||
|             saveLog(szTAG, LogUtils.LOG_LEVEL.Debug, sbMessage.toString()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     // 调试日志写入函数 | ||||
|     // | ||||
|     public static void v(String szTAG, String szMessage) { | ||||
|         if (isLoggable(szTAG, LogUtils.LOG_LEVEL.Verbose)) { | ||||
|             saveLog(szTAG, LogUtils.LOG_LEVEL.Verbose, szMessage); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     // 日志文件保存函数 | ||||
|     // | ||||
|     static void saveLog(String szTAG, LogUtils.LOG_LEVEL logLevel, String szMessage) { | ||||
|         try { | ||||
|             BufferedWriter out = null; | ||||
|             out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(_mfLogCatchFile, true), "UTF-8")); | ||||
|             out.write("[" + logLevel + "]  " + mSimpleDateFormat.format(System.currentTimeMillis()) + "  [" + szTAG + "]\n" + szMessage + "\n"); | ||||
|             out.close(); | ||||
|         } catch (IOException e) { | ||||
|             LogUtils.d(TAG, "IOException : " + e.getMessage()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     // 历史日志加载函数 | ||||
|     // | ||||
|     public static String loadLog() { | ||||
|         if (_mfLogCatchFile.exists()) { | ||||
|             StringBuffer sb = new StringBuffer(); | ||||
|             try { | ||||
|                 BufferedReader in = null; | ||||
|                 in = new BufferedReader(new InputStreamReader(new FileInputStream(_mfLogCatchFile), "UTF-8")); | ||||
|                 String line = ""; | ||||
|                 while ((line = in.readLine()) != null) { | ||||
|                     sb.append(line); | ||||
|                     sb.append("\n"); | ||||
|                 } | ||||
|             } catch (IOException e) { | ||||
|                 LogUtils.d(TAG, "IOException : " + e.getMessage()); | ||||
|             }  | ||||
|             return sb.toString(); | ||||
|         } | ||||
|         return ""; | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     // 清理日志函数 | ||||
|     // | ||||
|     public static void cleanLog() { | ||||
|         if (_mfLogCatchFile.exists()) { | ||||
|             try { | ||||
|                 FileUtils.writeStringToFile(_mfLogCatchFile.getPath(), ""); | ||||
|                 //LogUtils.d(TAG, "cleanLog"); | ||||
|             } catch (IOException e) { | ||||
|                 LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,71 @@ | ||||
| package cc.winboll.studio.shared.log; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/08/23 15:39:07 | ||||
|  * @Describe LogUtils 数据配置类。 | ||||
|  */ | ||||
| import android.util.JsonReader; | ||||
| import android.util.JsonWriter; | ||||
| import cc.winboll.studio.shared.app.BaseBean; | ||||
| import java.io.IOException; | ||||
| public class LogUtilsBean extends BaseBean { | ||||
|  | ||||
|     public static final String TAG = "LogUtilsBean"; | ||||
|  | ||||
|     LogUtils.LOG_LEVEL logLevel; | ||||
|  | ||||
|     public LogUtilsBean() { | ||||
|         this.logLevel = LogUtils.LOG_LEVEL.Off; | ||||
|     } | ||||
|  | ||||
|     public LogUtilsBean(LogUtils.LOG_LEVEL logLevel) { | ||||
|         this.logLevel = logLevel; | ||||
|     } | ||||
|  | ||||
|     public void setLogLevel(LogUtils.LOG_LEVEL logLevel) { | ||||
|         this.logLevel = logLevel; | ||||
|     } | ||||
|  | ||||
|     public LogUtils.LOG_LEVEL getLogLevel() { | ||||
|         return logLevel; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getName() { | ||||
|         return LogUtilsBean.class.getName(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { | ||||
|         super.writeThisToJsonWriter(jsonWriter); | ||||
|         LogUtilsBean bean = this; | ||||
|         jsonWriter.name("logLevel").value(bean.getLogLevel().ordinal()); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException { | ||||
|         if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else { | ||||
|             if (name.equals("logLevel")) { | ||||
|                 setLogLevel(LogUtils.LOG_LEVEL.values()[jsonReader.nextInt()]); | ||||
|             } else { | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException { | ||||
|         jsonReader.beginObject(); | ||||
|         while (jsonReader.hasNext()) { | ||||
|             String name = jsonReader.nextName(); | ||||
|             if (!initObjectsFromJsonReader(jsonReader, name)) { | ||||
|                 jsonReader.skipValue(); | ||||
|             } | ||||
|         } | ||||
|         // 结束 JSON 对象 | ||||
|         jsonReader.endObject(); | ||||
|         return this; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,88 @@ | ||||
| package cc.winboll.studio.shared.log; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2025/01/04 14:17:02 | ||||
|  * @Describe 日志类class TAG 标签数据类 | ||||
|  */ | ||||
| import android.util.JsonReader; | ||||
| import android.util.JsonWriter; | ||||
| import cc.winboll.studio.shared.app.BaseBean; | ||||
| import java.io.IOException; | ||||
|  | ||||
| public class LogUtilsClassTAGBean extends BaseBean { | ||||
|  | ||||
|     public static final String TAG = "LogUtilsClassTAGBean"; | ||||
|  | ||||
|     // 标签名 | ||||
|     String tag; | ||||
|     // 是否启用 | ||||
|     Boolean enable; | ||||
|  | ||||
|     public LogUtilsClassTAGBean() { | ||||
|         this.tag = TAG; | ||||
|         this.enable = true; | ||||
|     } | ||||
|  | ||||
|     public LogUtilsClassTAGBean(String tag, Boolean enable) { | ||||
|         this.tag = tag; | ||||
|         this.enable = enable; | ||||
|     } | ||||
|  | ||||
|     public void setTag(String tag) { | ||||
|         this.tag = tag; | ||||
|     } | ||||
|  | ||||
|     public String getTag() { | ||||
|         return tag; | ||||
|     } | ||||
|  | ||||
|     public void setEnable(Boolean enable) { | ||||
|         this.enable = enable; | ||||
|     } | ||||
|  | ||||
|     public Boolean getEnable() { | ||||
|         return enable; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getName() { | ||||
|         return LogUtilsClassTAGBean.class.getName(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { | ||||
|         super.writeThisToJsonWriter(jsonWriter); | ||||
|         LogUtilsClassTAGBean bean = this; | ||||
|         jsonWriter.name("tag").value(bean.getTag()); | ||||
|         jsonWriter.name("enable").value(bean.getEnable()); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException { | ||||
|         if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else { | ||||
|             if (name.equals("tag")) { | ||||
|                 setTag(jsonReader.nextString()); | ||||
|             } else if (name.equals("enable")) { | ||||
|                 setEnable(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; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,386 @@ | ||||
| package cc.winboll.studio.shared.log; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/08/12 14:36:18 | ||||
|  * @Describe 日志视图类,继承 RelativeLayout 类。 | ||||
|  */ | ||||
| import android.content.ClipData; | ||||
| import android.content.ClipboardManager; | ||||
| import android.content.Context; | ||||
| import android.os.Handler; | ||||
| import android.os.Message; | ||||
| import android.util.AttributeSet; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.widget.AdapterView; | ||||
| import android.widget.ArrayAdapter; | ||||
| import android.widget.CheckBox; | ||||
| import android.widget.RelativeLayout; | ||||
| import android.widget.ScrollView; | ||||
| import android.widget.Spinner; | ||||
| import android.widget.TextView; | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.recyclerview.widget.LinearLayoutManager; | ||||
| import androidx.recyclerview.widget.RecyclerView; | ||||
| import cc.winboll.studio.R; | ||||
| import java.text.Collator; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collections; | ||||
| import java.util.Comparator; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
|  | ||||
| public class LogView extends RelativeLayout { | ||||
|  | ||||
|     public static final String TAG = "LogView"; | ||||
|  | ||||
|     public volatile boolean mIsHandling; | ||||
|     public volatile boolean mIsAddNewLog; | ||||
|  | ||||
|     Context mContext; | ||||
|     ScrollView mScrollView; | ||||
|     TextView mTextView; | ||||
|     CheckBox mSelectableCheckBox; | ||||
|     CheckBox mSelectAllTAGCheckBox; | ||||
|     TAGListAdapter mTAGListAdapter; | ||||
|     LogViewThread mLogViewThread; | ||||
|     LogViewHandler mLogViewHandler; | ||||
|     Spinner mLogLevelSpinner; | ||||
|     ArrayAdapter<CharSequence> mLogLevelSpinnerAdapter; | ||||
|     // 标签列表 | ||||
|     RecyclerView recyclerView; | ||||
|  | ||||
|     public LogView(Context context) { | ||||
|         super(context); | ||||
|         initView(context); | ||||
|     } | ||||
|  | ||||
|     public LogView(Context context, AttributeSet attrs) { | ||||
|         super(context, attrs); | ||||
|         initView(context); | ||||
|     } | ||||
|  | ||||
|     public LogView(Context context, AttributeSet attrs, int defStyleAttr) { | ||||
|         super(context, attrs, defStyleAttr); | ||||
|         initView(context); | ||||
|     } | ||||
|  | ||||
|     public LogView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { | ||||
|         super(context, attrs, defStyleAttr, defStyleRes); | ||||
|         initView(context); | ||||
|     } | ||||
|  | ||||
|     public void start() { | ||||
|         mLogViewThread = new LogViewThread(LogView.this); | ||||
|         mLogViewThread.start(); | ||||
|         // 显示日志 | ||||
|         showAndScrollLogView(); | ||||
|     } | ||||
|  | ||||
|     public void scrollLogUp() { | ||||
|         mScrollView.post(new Runnable() { | ||||
|                 @Override | ||||
|                 public void run() { | ||||
|                     mScrollView.fullScroll(ScrollView.FOCUS_DOWN); | ||||
|                     // 日志显示结束 | ||||
|                     mLogViewHandler.setIsHandling(false); | ||||
|                     // 检查是否添加了新日志 | ||||
|                     if (mLogViewHandler.isAddNewLog()) { | ||||
|                         // 有新日志添加,先更改新日志标志 | ||||
|                         mLogViewHandler.setIsAddNewLog(false); | ||||
|                         // 再次发送显示日志的显示 | ||||
|                         Message message = mLogViewHandler.obtainMessage(LogViewHandler.MSG_LOGVIEW_UPDATE); | ||||
|                         mLogViewHandler.sendMessage(message); | ||||
|                     } | ||||
|                 } | ||||
|             }); | ||||
|     } | ||||
|  | ||||
|     void initView(Context context) { | ||||
|         mContext = context; | ||||
|         mLogViewHandler = new LogViewHandler(); | ||||
|         // 加载视图布局 | ||||
|         addView(inflate(mContext, cc.winboll.studio.R.layout.view_log, null)); | ||||
|         // 初始化日志子控件视图 | ||||
|         // | ||||
|         mScrollView = findViewById(cc.winboll.studio.R.id.viewlogScrollViewLog); | ||||
|         mTextView = findViewById(cc.winboll.studio.R.id.viewlogTextViewLog); | ||||
|         // 获取Log Level spinner实例 | ||||
|         mLogLevelSpinner = findViewById(cc.winboll.studio.R.id.viewlogSpinner1); | ||||
|  | ||||
|         (findViewById(cc.winboll.studio.R.id.viewlogButtonClean)).setOnClickListener(new View.OnClickListener(){ | ||||
|  | ||||
|                 @Override | ||||
|                 public void onClick(View v) { | ||||
|                     LogUtils.cleanLog(); | ||||
|                     LogUtils.d(TAG, "Log is cleaned."); | ||||
|                 } | ||||
|             }); | ||||
|         (findViewById(cc.winboll.studio.R.id.viewlogButtonCopy)).setOnClickListener(new View.OnClickListener(){ | ||||
|  | ||||
|                 @Override | ||||
|                 public void onClick(View v) { | ||||
|  | ||||
|                     ClipboardManager cm = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE); | ||||
|                     cm.setPrimaryClip(ClipData.newPlainText(mContext.getPackageName(), LogUtils.loadLog())); | ||||
|                     LogUtils.d(TAG, "Log is copied."); | ||||
|                 } | ||||
|             }); | ||||
|         mSelectableCheckBox = findViewById(cc.winboll.studio.R.id.viewlogCheckBoxSelectable); | ||||
|         mSelectableCheckBox.setOnClickListener(new View.OnClickListener(){ | ||||
|                 @Override | ||||
|                 public void onClick(View v) { | ||||
|                     if (mSelectableCheckBox.isChecked()) { | ||||
|                         setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); | ||||
|                     } else { | ||||
|                         setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); | ||||
|                     } | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|         // 设置日志级别列表 | ||||
|         ArrayList<String> adapterItems = new ArrayList<>(); | ||||
|         for (LogUtils.LOG_LEVEL e : LogUtils.LOG_LEVEL.values()) { | ||||
|             adapterItems.add(e.name()); | ||||
|         } | ||||
|         // 假设你有一个字符串数组作为选项列表 | ||||
|         //String[] options = {"Option 1", "Option 2", "Option 3"}; | ||||
|         // 创建一个ArrayAdapter来绑定数据到spinner | ||||
|         mLogLevelSpinnerAdapter = ArrayAdapter.createFromResource( | ||||
|             context, cc.winboll.studio.R.array.enum_loglevel_array, android.R.layout.simple_spinner_item); | ||||
|         mLogLevelSpinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); | ||||
|  | ||||
|         // 设置适配器并将它应用到spinner上 | ||||
|         mLogLevelSpinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); // 设置下拉视图样式 | ||||
|         mLogLevelSpinner.setAdapter(mLogLevelSpinnerAdapter); | ||||
|         // 为Spinner添加监听器 | ||||
|         mLogLevelSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { | ||||
|                 @Override | ||||
|                 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { | ||||
|                     //String selectedOption = mLogLevelSpinnerAdapter.getItem(position); | ||||
|                     // 处理选中的选项... | ||||
|                     LogUtils.setLogLevel(LogUtils.LOG_LEVEL.values()[position]); | ||||
|                 } | ||||
|                 @Override | ||||
|                 public void onNothingSelected(AdapterView<?> parent) { | ||||
|                     // 如果没有选择,则执行此操作... | ||||
|                 } | ||||
|             }); | ||||
|         // 获取默认值的索引 | ||||
|         int defaultValueIndex = LogUtils.getLogLevel().ordinal(); | ||||
|  | ||||
|         if (defaultValueIndex != -1) { | ||||
|             // 如果找到了默认值,设置默认选项 | ||||
|             mLogLevelSpinner.setSelection(defaultValueIndex); | ||||
|         } | ||||
|  | ||||
|         // 加载标签列表 | ||||
|         Map<String, Boolean> mapTAGList = LogUtils.getMapTAGList(); | ||||
|         boolean isAllSelect = true; | ||||
|         for (Map.Entry<String, Boolean> entry : mapTAGList.entrySet()) { | ||||
|             if (entry.getValue() == false) { | ||||
|                 isAllSelect = false; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         CheckBox cbALLTAG = findViewById(cc.winboll.studio.R.id.viewlogCheckBox1); | ||||
|         cbALLTAG.setChecked(isAllSelect); | ||||
|  | ||||
|         // 加载标签表 | ||||
|         recyclerView = findViewById(cc.winboll.studio.R.id.viewlogRecyclerView1); | ||||
|         LinearLayoutManager layoutManager = new LinearLayoutManager(mContext, LinearLayoutManager.HORIZONTAL, false); | ||||
|         recyclerView.setLayoutManager(layoutManager); | ||||
|         mTAGListAdapter = new TAGListAdapter(mapTAGList); | ||||
|         recyclerView.setAdapter(mTAGListAdapter); | ||||
|  | ||||
|         // 可以添加点击监听器来处理勾选框状态变化后的逻辑,比如获取当前勾选情况等 | ||||
|         mTAGListAdapter.notifyDataSetChanged(); | ||||
|  | ||||
|         mSelectAllTAGCheckBox = findViewById(cc.winboll.studio.R.id.viewlogCheckBox1); | ||||
|         mSelectAllTAGCheckBox.setOnClickListener(new View.OnClickListener(){ | ||||
|                 @Override | ||||
|                 public void onClick(View v) { | ||||
|                     LogUtils.setALlTAGListEnable(mSelectAllTAGCheckBox.isChecked()); | ||||
|                     //LogUtils.setALlTAGListEnable(false); | ||||
|                     //mTAGListAdapter.notifyDataSetChanged(); | ||||
|                     mTAGListAdapter.reload(); | ||||
|                     //ToastUtils.show(String.format("onClick\nmSelectAllTAGCheckBox.isChecked() : %s", mSelectAllTAGCheckBox.isChecked())); | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|  | ||||
|         // 设置滚动时不聚焦日志 | ||||
|         setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); | ||||
|     } | ||||
|  | ||||
|     public void updateLogView() { | ||||
|         if (mLogViewHandler.isHandling() == true) { | ||||
|             // 正在处理日志显示, | ||||
|             // 就先设置一个新日志标志位 | ||||
|             // 以便日志显示完后,再次显示新日志内容 | ||||
|             mLogViewHandler.setIsAddNewLog(true); | ||||
|         } else { | ||||
|             //LogUtils.d(TAG, "LogListener showLog(String path)"); | ||||
|             Message message = mLogViewHandler.obtainMessage(LogViewHandler.MSG_LOGVIEW_UPDATE); | ||||
|             mLogViewHandler.sendMessage(message); | ||||
|             mLogViewHandler.setIsAddNewLog(false); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void showAndScrollLogView() { | ||||
|         mTextView.setText(LogUtils.loadLog()); | ||||
|         scrollLogUp(); | ||||
|     } | ||||
|  | ||||
|     class LogViewHandler extends Handler { | ||||
|  | ||||
|         final static int MSG_LOGVIEW_UPDATE = 0; | ||||
|         volatile boolean isHandling; | ||||
|         volatile boolean isAddNewLog; | ||||
|  | ||||
|         public LogViewHandler() { | ||||
|             setIsHandling(false); | ||||
|             setIsAddNewLog(false); | ||||
|         } | ||||
|  | ||||
|         public void setIsHandling(boolean isHandling) { | ||||
|             this.isHandling = isHandling; | ||||
|         } | ||||
|  | ||||
|         public boolean isHandling() { | ||||
|             return isHandling; | ||||
|         } | ||||
|  | ||||
|         public void setIsAddNewLog(boolean isAddNewLog) { | ||||
|             this.isAddNewLog = isAddNewLog; | ||||
|         } | ||||
|  | ||||
|         public boolean isAddNewLog() { | ||||
|             return isAddNewLog; | ||||
|         } | ||||
|  | ||||
|         public void handleMessage(Message msg) { | ||||
|             switch (msg.what) { | ||||
|                 case MSG_LOGVIEW_UPDATE:{ | ||||
|                         if (isHandling() == false) { | ||||
|                             setIsHandling(true); | ||||
|                             showAndScrollLogView(); | ||||
|                         } | ||||
|                         break; | ||||
|                     } | ||||
|                 default: | ||||
|                     break; | ||||
|             } | ||||
|             super.handleMessage(msg); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public class TAGItemModel { | ||||
|         private String tag; | ||||
|         private boolean isChecked; | ||||
|  | ||||
|         public TAGItemModel(String tag, boolean isChecked) { | ||||
|             this.tag = tag; | ||||
|             this.isChecked = isChecked; | ||||
|         } | ||||
|  | ||||
|         public String getTag() { | ||||
|             return tag; | ||||
|         } | ||||
|  | ||||
|         public void setTag(String tag) { | ||||
|             this.tag = tag; | ||||
|         } | ||||
|  | ||||
|         public boolean isChecked() { | ||||
|             return isChecked; | ||||
|         } | ||||
|  | ||||
|         public void setChecked(boolean checked) { | ||||
|             isChecked = checked; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     public class TAGListAdapter extends RecyclerView.Adapter<TAGListAdapter.ViewHolder> { | ||||
|  | ||||
|         private Map<String, Boolean> mapOrigin; | ||||
|         private List<TAGItemModel> itemList; | ||||
|  | ||||
|         public TAGListAdapter(Map<String, Boolean> map) { | ||||
|             mapOrigin = map; | ||||
|             loadMap(mapOrigin); | ||||
|         } | ||||
|  | ||||
|         void loadMap(Map<String, Boolean> map) { | ||||
|             itemList = new ArrayList<TAGItemModel>(); | ||||
|             for (Map.Entry<String, Boolean> entry : map.entrySet()) { | ||||
|                 itemList.add(new TAGItemModel(entry.getKey(), entry.getValue())); | ||||
|             } | ||||
|             // 添加排序功能,按照tag进行升序排序 | ||||
|             Collections.sort(itemList, new SortMapEntryByKeyString(true)); | ||||
|             //Collections.sort(itemList, new SortMapEntryByKeyString(false)); | ||||
|         } | ||||
|  | ||||
|         public void reload() { | ||||
|             loadMap(mapOrigin); | ||||
|             super.notifyDataSetChanged(); | ||||
|         } | ||||
|  | ||||
|         @NonNull | ||||
|         @Override | ||||
|         public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { | ||||
|             View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_logtag, parent, false); | ||||
|             return new ViewHolder(view); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void onBindViewHolder(@NonNull ViewHolder holder, int position) { | ||||
|             final TAGItemModel item = itemList.get(position); | ||||
|             holder.tvText.setText(item.getTag()); | ||||
|             holder.cbChecked.setChecked(item.isChecked()); | ||||
|             holder.cbChecked.setOnClickListener(new View.OnClickListener(){ | ||||
|  | ||||
|                     @Override | ||||
|                     public void onClick(View v) { | ||||
|                         LogUtils.setTAGListEnable(item.getTag(), ((CheckBox)v).isChecked()); | ||||
|                     } | ||||
|                 }); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public int getItemCount() { | ||||
|             return itemList.size(); | ||||
|         } | ||||
|  | ||||
|         public class ViewHolder extends RecyclerView.ViewHolder { | ||||
|             TextView tvText; | ||||
|             CheckBox cbChecked; | ||||
|  | ||||
|             public ViewHolder(@NonNull View itemView) { | ||||
|                 super(itemView); | ||||
|                 tvText = itemView.findViewById(R.id.viewlogtagTextView1); | ||||
|                 cbChecked = itemView.findViewById(R.id.viewlogtagCheckBox1); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     class SortMapEntryByKeyString implements Comparator<TAGItemModel> { | ||||
|         private boolean mIsDesc = true; | ||||
|         // isDesc 是否降序排列 | ||||
|         public SortMapEntryByKeyString(boolean isDesc) { | ||||
|             mIsDesc = isDesc; | ||||
|         } | ||||
|         Collator cmp = Collator.getInstance(java.util.Locale.CHINA); | ||||
|         @Override | ||||
|         public int compare(TAGItemModel o1, TAGItemModel o2) { | ||||
|             if (mIsDesc) { | ||||
|                 return o1.getTag().compareTo(o2.getTag()); | ||||
|             } else { | ||||
|                 return o2.getTag().compareTo(o1.getTag()); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,79 @@ | ||||
| package cc.winboll.studio.shared.log; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/08/12 14:43:50 | ||||
|  * @Describe 日志视图线程类 | ||||
|  */ | ||||
| import android.os.FileObserver; | ||||
| import java.lang.ref.WeakReference; | ||||
|  | ||||
| public class LogViewThread extends Thread { | ||||
|  | ||||
|     public static final String TAG = "LogViewThread"; | ||||
|  | ||||
|     // 线程退出标志 | ||||
|     volatile boolean isExist = false; | ||||
|     // 应用日志文件监听实例 | ||||
|     LogListener mLogListener; | ||||
|     // 日志视图弱引用 | ||||
|     WeakReference<LogView> mwrLogView; | ||||
|  | ||||
|     // | ||||
|     // 构造函数 | ||||
|     // @logView : 日志显示输出视图类 | ||||
|     public LogViewThread(LogView logView) { | ||||
|         mwrLogView = new WeakReference<LogView>(logView); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     public void setIsExist(boolean isExist) { | ||||
|         this.isExist = isExist; | ||||
|     } | ||||
|  | ||||
|     public boolean isExist() { | ||||
|         return isExist; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void run() { | ||||
|         String szLogDir = LogUtils.getLogCacheDir().getPath(); | ||||
|         mLogListener = new LogListener(szLogDir); | ||||
|         mLogListener.startWatching(); | ||||
|         while (isExist() == false) { | ||||
|             try { | ||||
|                 Thread.sleep(1000); | ||||
|             } catch (InterruptedException e) {} | ||||
|         } | ||||
|         mLogListener.stopWatching(); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     // | ||||
|     // 日志文件监听类 | ||||
|     // | ||||
|     class LogListener extends FileObserver { | ||||
|         public LogListener(String path) { | ||||
|             super(path); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void onEvent(int event, String path) { | ||||
|             int e = event & FileObserver.ALL_EVENTS; | ||||
|             switch (e) { | ||||
|                 case FileObserver.CLOSE_WRITE:{ | ||||
|                         if (mwrLogView.get() != null) { | ||||
|                             mwrLogView.get().updateLogView(); | ||||
|                         } | ||||
|                         break; | ||||
|                     } | ||||
|                 case FileObserver.DELETE:{ | ||||
|                         if (mwrLogView.get() != null) { | ||||
|                             mwrLogView.get().updateLogView(); | ||||
|                         } | ||||
|                         break; | ||||
|                     } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,95 @@ | ||||
| package cc.winboll.studio.shared.service; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/12/08 20:15:42 | ||||
|  * @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.shared.util.ServiceUtils; | ||||
|  | ||||
| public class AssistantService extends Service { | ||||
|  | ||||
|     public final static String TAG = "AssistantService"; | ||||
|  | ||||
|     WinBollClientServiceBean mWinBollServiceBean; | ||||
|     MyServiceConnection mMyServiceConnection; | ||||
|     volatile boolean mIsServiceRunning; | ||||
|  | ||||
|     @Override | ||||
|     public IBinder onBind(Intent intent) { | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onCreate() { | ||||
|         super.onCreate(); | ||||
|         mWinBollServiceBean = WinBollClientServiceBean.loadWinBollClientServiceBean(this); | ||||
|         if (mMyServiceConnection == null) { | ||||
|             mMyServiceConnection = new MyServiceConnection(); | ||||
|         } | ||||
|         // 设置运行参数 | ||||
|         mIsServiceRunning = false; | ||||
|         run(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int onStartCommand(Intent intent, int flags, int startId) { | ||||
|         run(); | ||||
|         return START_STICKY; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onDestroy() { | ||||
|         mIsServiceRunning = false; | ||||
|         super.onDestroy(); | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     // 运行服务内容 | ||||
|     // | ||||
|     void run() { | ||||
|         mWinBollServiceBean = WinBollClientServiceBean.loadWinBollClientServiceBean(this); | ||||
|         if (mWinBollServiceBean.isEnable()) { | ||||
|             if (mIsServiceRunning == false) { | ||||
|                 // 设置运行状态 | ||||
|                 mIsServiceRunning = true; | ||||
|                 // 唤醒和绑定主进程 | ||||
|                 wakeupAndBindMain(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     // 唤醒和绑定主进程 | ||||
|     // | ||||
|     void wakeupAndBindMain() { | ||||
|         if (ServiceUtils.isServiceAlive(getApplicationContext(), WinBollClientService.class.getName()) == false) { | ||||
|             startForegroundService(new Intent(AssistantService.this, WinBollClientService.class)); | ||||
|         } | ||||
|  | ||||
|         bindService(new Intent(AssistantService.this, WinBollClientService.class), mMyServiceConnection, Context.BIND_IMPORTANT); | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     // 主进程与守护进程连接时需要用到此类 | ||||
|     // | ||||
|     class MyServiceConnection implements ServiceConnection { | ||||
|         @Override | ||||
|         public void onServiceConnected(ComponentName name, IBinder service) { | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void onServiceDisconnected(ComponentName name) { | ||||
|             mWinBollServiceBean = WinBollClientServiceBean.loadWinBollClientServiceBean(AssistantService.this); | ||||
|             if (mWinBollServiceBean.isEnable()) { | ||||
|                 wakeupAndBindMain(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,35 @@ | ||||
| package cc.winboll.studio.shared.service; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/12/09 08:37:31 | ||||
|  * @Describe WinBoll UI 状态图标枚举 | ||||
|  */ | ||||
| import cc.winboll.studio.R; | ||||
|  | ||||
| public enum EWUIStatusIconDrawable { | ||||
|     NORMAL(0), | ||||
|     NEWS(1) | ||||
|     ; | ||||
|      | ||||
|     static final String TAG = "WUIStatusIconDrawable"; | ||||
|  | ||||
|     static String[] _mlistCNName = { "正常", "新的消息" }; | ||||
|  | ||||
|     private int value = 0; | ||||
|     private EWUIStatusIconDrawable(int value) {    //必须是private的,否则编译错误 | ||||
|         this.value = value; | ||||
|     } | ||||
|      | ||||
|     public static int getIconDrawableId(EWUIStatusIconDrawable drawableId) { | ||||
|         int res; | ||||
|         switch(drawableId){ | ||||
|             case NEWS : | ||||
|                 res = R.drawable.ic_winbollbeta; | ||||
|                 break; | ||||
|             default : | ||||
|                 res = R.drawable.ic_winboll; | ||||
|         } | ||||
|         return res; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,17 @@ | ||||
| package cc.winboll.studio.shared.service; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/12/08 23:40:05 | ||||
|  * @Describe WinBollService 服务 Binder。 | ||||
|  */ | ||||
| import android.graphics.drawable.Drawable; | ||||
|  | ||||
| public interface IWinBollClientServiceBinder { | ||||
|      | ||||
|     public static final String TAG = "IWinBollClientServiceBinder"; | ||||
|      | ||||
|     public WinBollClientService getService(); | ||||
|      | ||||
|     public Drawable getCurrentStatusIconDrawable(); | ||||
| } | ||||
| @@ -0,0 +1,193 @@ | ||||
| package cc.winboll.studio.shared.service; | ||||
|  | ||||
| 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 android.os.IInterface; | ||||
| import android.os.Parcel; | ||||
| import android.os.RemoteException; | ||||
| import cc.winboll.studio.shared.log.LogUtils; | ||||
| import cc.winboll.studio.shared.util.ServiceUtils; | ||||
| import com.hjq.toast.ToastUtils; | ||||
| import java.io.FileDescriptor; | ||||
| import android.graphics.drawable.Drawable; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/12/08 19:42:07 | ||||
|  * @Describe WinBoll 客户端服务 | ||||
|  */ | ||||
| public class WinBollClientService extends Service implements IWinBollClientServiceBinder { | ||||
|  | ||||
|     public static final String TAG = "WinBollClientService"; | ||||
|  | ||||
|     WinBollClientServiceBean mWinBollClientServiceBean; | ||||
|     MyServiceConnection mMyServiceConnection; | ||||
|     volatile boolean mIsWinBollClientThreadRunning; | ||||
|     volatile boolean mIsEnableService; | ||||
|     WinBollClientThread mWinBollClientThread; | ||||
|  | ||||
|     public boolean isWinBollClientThreadRunning() { | ||||
|         return mIsWinBollClientThreadRunning; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public WinBollClientService getService() { | ||||
|         return WinBollClientService.this; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Drawable getCurrentStatusIconDrawable() { | ||||
|         return mIsWinBollClientThreadRunning ? | ||||
|             getDrawable(EWUIStatusIconDrawable.getIconDrawableId(EWUIStatusIconDrawable.NORMAL)) | ||||
|             : getDrawable(EWUIStatusIconDrawable.getIconDrawableId(EWUIStatusIconDrawable.NEWS)); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public IBinder onBind(Intent intent) { | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onCreate() { | ||||
|         ToastUtils.show("onCreate"); | ||||
|         super.onCreate(); | ||||
|         mWinBollClientThread = null; | ||||
|         mWinBollClientServiceBean = WinBollClientServiceBean.loadWinBollClientServiceBean(this); | ||||
|         mIsEnableService = mWinBollClientServiceBean.isEnable(); | ||||
|  | ||||
|         if (mMyServiceConnection == null) { | ||||
|             mMyServiceConnection = new MyServiceConnection(); | ||||
|         } | ||||
|  | ||||
|         // 由系统启动时,应用可以通过下面函数实例化实际服务进程。 | ||||
|         runMainThread(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int onStartCommand(Intent intent, int flags, int startId) { | ||||
|         ToastUtils.show("onStartCommand"); | ||||
|         // 由应用 Intent 启动时,应用可以通过下面函数实例化实际服务进程。 | ||||
|         runMainThread(); | ||||
|  | ||||
|         // 返回运行参数持久化存储后,服务状态控制参数 | ||||
|         // 无论 Intent 传入如何,服务状态一直以持久化存储后的参数控制, | ||||
|         // PS: 另外当然可以通过 Intent 传入的指标来修改 mWinBollServiceBean, | ||||
|         //     不过本服务的应用方向会变得繁琐, | ||||
|         //     现阶段只要满足手机端启动与停止本服务,WinBoll 客户端实例运行在手机端就可以了。 | ||||
|         return mIsEnableService ? Service.START_STICKY: super.onStartCommand(intent, flags, startId); | ||||
|     } | ||||
|  | ||||
|     void runMainThread() { | ||||
|         if (mWinBollClientThread == null) { | ||||
|             ToastUtils.show("runMainThread()"); | ||||
|             mWinBollClientThread = new WinBollClientThread(); | ||||
|             mWinBollClientThread.start(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void syncWinBollClientThreadStatus() { | ||||
|         mWinBollClientServiceBean = WinBollClientServiceBean.loadWinBollClientServiceBean(this); | ||||
|         mIsEnableService = mWinBollClientServiceBean.isEnable(); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     // 唤醒和绑定守护进程 | ||||
|     // | ||||
|     void wakeupAndBindAssistant() { | ||||
|         if (ServiceUtils.isServiceAlive(getApplicationContext(), AssistantService.class.getName()) == false) { | ||||
|             startService(new Intent(WinBollClientService.this, AssistantService.class)); | ||||
|             //LogUtils.d(TAG, "call wakeupAndBindAssistant() : Binding... AssistantService"); | ||||
|             bindService(new Intent(WinBollClientService.this, AssistantService.class), mMyServiceConnection, Context.BIND_IMPORTANT); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // 主进程与守护进程连接时需要用到此类 | ||||
|     // | ||||
|     private class MyServiceConnection implements ServiceConnection { | ||||
|         @Override | ||||
|         public void onServiceConnected(ComponentName name, IBinder service) { | ||||
|  | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void onServiceDisconnected(ComponentName name) {             | ||||
|             mWinBollClientServiceBean = WinBollClientServiceBean.loadWinBollClientServiceBean(WinBollClientService.this); | ||||
|             if (mWinBollClientServiceBean.isEnable()) { | ||||
|                 // 唤醒守护进程 | ||||
|                 wakeupAndBindAssistant(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onDestroy() { | ||||
|         super.onDestroy(); | ||||
|         ToastUtils.show("onDestroy"); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onStart(Intent intent, int startId) { | ||||
|         super.onStart(intent, startId); | ||||
|     } | ||||
|  | ||||
|     void setWinBollServiceEnableStatus(boolean isEnable) { | ||||
|         WinBollClientServiceBean bean = WinBollClientServiceBean.loadWinBollClientServiceBean(this); | ||||
|         bean.setIsEnable(isEnable); | ||||
|         WinBollClientServiceBean.saveWinBollServiceBean(this, bean); | ||||
|     } | ||||
|  | ||||
|     boolean getWinBollServiceEnableStatus(Context context) { | ||||
|         mWinBollClientServiceBean = WinBollClientServiceBean.loadWinBollClientServiceBean(context); | ||||
|         return mWinBollClientServiceBean.isEnable(); | ||||
|     } | ||||
|  | ||||
|     /*public interface OnServiceStatusChangeListener { | ||||
|      void onServerStatusChange(boolean isServiceAlive); | ||||
|      } | ||||
|  | ||||
|      public void setOnServerStatusChangeListener(OnServiceStatusChangeListener l) { | ||||
|      mOnServerStatusChangeListener = l; | ||||
|      }*/ | ||||
|  | ||||
|     class WinBollClientThread extends Thread { | ||||
|         @Override | ||||
|         public void run() { | ||||
|             ToastUtils.show("WinBollClientThread"); | ||||
|             super.run(); | ||||
|             syncWinBollClientThreadStatus(); | ||||
|             if (mIsEnableService) { | ||||
|                 if (mIsWinBollClientThreadRunning == false) { | ||||
|                     // 设置运行状态 | ||||
|                     mIsWinBollClientThreadRunning = true; | ||||
|  | ||||
|                     ToastUtils.show("run()"); | ||||
|  | ||||
|                     // 唤醒守护进程 | ||||
|                     //wakeupAndBindAssistant(); | ||||
|  | ||||
|                     while (mIsEnableService) { | ||||
|                         // 显示运行状态 | ||||
|                         ToastUtils.show(TAG + " is running."); | ||||
|                         try { | ||||
|                             Thread.sleep(2 * 1000); | ||||
|                         } catch (InterruptedException e) { | ||||
|                             LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|                         } | ||||
|                         syncWinBollClientThreadStatus(); | ||||
|                         //ToastUtils.show("syncServiceThreadStatus OK."); | ||||
|                         //ToastUtils.show("mIsExist : " + Boolean.toString(!mIsEnableService)); | ||||
|                         //break; | ||||
|                     } | ||||
|  | ||||
|                     // 服务进程退出, 重置进程运行状态 | ||||
|                     mIsWinBollClientThreadRunning = false; | ||||
|                     mWinBollClientThread = null; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,77 @@ | ||||
| package cc.winboll.studio.shared.service; | ||||
| import cc.winboll.studio.shared.app.BaseBean; | ||||
| import android.util.JsonReader; | ||||
| import java.io.IOException; | ||||
| import android.util.JsonWriter; | ||||
| import android.content.Context; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/12/08 19:44:57 | ||||
|  * @Describe WinBollService 运行参数配置 | ||||
|  */ | ||||
| public class WinBollClientServiceBean extends BaseBean { | ||||
|  | ||||
|     public static final String TAG = "WinBollClientServiceBean"; | ||||
|  | ||||
|     volatile boolean isEnable; | ||||
|  | ||||
|     public WinBollClientServiceBean() { | ||||
|         isEnable = false; | ||||
|     } | ||||
|  | ||||
|     public void setIsEnable(boolean isEnable) { | ||||
|         this.isEnable = isEnable; | ||||
|     } | ||||
|  | ||||
|     public boolean isEnable() { | ||||
|         return isEnable; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getName() { | ||||
|         return WinBollClientServiceBean.class.getName(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { | ||||
|         super.writeThisToJsonWriter(jsonWriter); | ||||
|         WinBollClientServiceBean bean = this; | ||||
|         jsonWriter.name("isEnable").value(bean.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; | ||||
|     } | ||||
|  | ||||
|     public static WinBollClientServiceBean loadWinBollClientServiceBean(Context context) { | ||||
|         WinBollClientServiceBean bean = WinBollClientServiceBean.loadBean(context, WinBollClientServiceBean.class); | ||||
|         return bean == null ? new WinBollClientServiceBean() : bean; | ||||
|     } | ||||
|  | ||||
|     public static boolean saveWinBollServiceBean(WinBollClientService service, WinBollClientServiceBean bean) { | ||||
|         return WinBollClientServiceBean.saveBean(service, bean); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,22 @@ | ||||
| package cc.winboll.studio.shared.service; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/12/09 08:19:06 | ||||
|  * @Describe WinBoll 邮件服务 | ||||
|  */ | ||||
| import android.app.Service; | ||||
| import android.content.Intent; | ||||
| import android.os.IBinder; | ||||
|  | ||||
| public class WinBollMail extends Service { | ||||
|      | ||||
|     public static final String TAG = "WinBollMail"; | ||||
|      | ||||
|     @Override | ||||
|     public IBinder onBind(Intent intent) { | ||||
|          | ||||
|         return null; | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -0,0 +1,123 @@ | ||||
| package cc.winboll.studio.shared.util; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/07/19 14:30:57 | ||||
|  * @Describe 文件工具类 | ||||
|  */ | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.content.res.AssetManager; | ||||
| import android.net.Uri; | ||||
| import androidx.core.content.FileProvider; | ||||
| import cc.winboll.studio.shared.log.LogUtils; | ||||
| import java.io.File; | ||||
| import java.io.FileInputStream; | ||||
| import java.io.FileOutputStream; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.io.InputStreamReader; | ||||
| import java.io.OutputStream; | ||||
| import java.io.OutputStreamWriter; | ||||
| import java.nio.charset.StandardCharsets; | ||||
|  | ||||
| public class FileUtils { | ||||
|  | ||||
|     public static final String TAG = "FileUtil"; | ||||
|  | ||||
|     public static void shareJSONFile(Context context, String szConfigFile) { | ||||
|         Uri uri; | ||||
|         File file = new File(szConfigFile); | ||||
|         uri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", file); | ||||
|         /*if (Build.VERSION.SDK_INT >= 24) {//android 7.0以上 | ||||
|          uri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", file); | ||||
|          } else { | ||||
|          uri = Uri.fromFile(file); | ||||
|          }*/ | ||||
|         Intent shareIntent = new Intent();     | ||||
|         shareIntent.setAction(Intent.ACTION_SEND);     | ||||
|         shareIntent.putExtra(Intent.EXTRA_STREAM, uri);     | ||||
|         shareIntent.setType("application/json"); | ||||
|  | ||||
|         shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); | ||||
|         /*if (Build.VERSION.SDK_INT >= 24) { | ||||
|          shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); | ||||
|          }*/ | ||||
|  | ||||
|         // 设置分享的标题     | ||||
|         context.startActivity(Intent.createChooser(shareIntent, "SHARE JSON"));   | ||||
|     } | ||||
|      | ||||
|     public static void shareHtmlFile(Context context, String szHtmlFile) { | ||||
|         File htmlFile = new File(szHtmlFile); | ||||
|         Uri contentUri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", htmlFile); | ||||
|          | ||||
|         Intent intent = new Intent(Intent.ACTION_VIEW); | ||||
|         intent.setDataAndType(contentUri, "text/html"); | ||||
|         intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); | ||||
|         context.startActivity(intent); | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     // 把字符串写入文件,指定 UTF-8 编码 | ||||
|     // | ||||
|     public static void writeFile(String filePath, String content) throws IOException { | ||||
|         File file = new File(filePath); | ||||
|         FileOutputStream outputStream = new FileOutputStream(file); | ||||
|         OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8); | ||||
|         writer.write(content); | ||||
|         writer.close(); | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     // 读取文件到字符串,指定 UTF-8 编码 | ||||
|     // | ||||
|     public static String readFile(String filePath) throws IOException { | ||||
|         File file = new File(filePath); | ||||
|         FileInputStream inputStream = new FileInputStream(file); | ||||
|         InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8); | ||||
|         StringBuilder content = new StringBuilder(); | ||||
|         int character; | ||||
|         while ((character = reader.read()) != -1) { | ||||
|             content.append((char) character); | ||||
|         } | ||||
|         reader.close(); | ||||
|         return content.toString(); | ||||
|     } | ||||
|  | ||||
|     public static void copyAssetsToSD(Context context, String szSrcAssets, String szDstSD) { | ||||
|         LogUtils.d(TAG, "copyAssetsToSD [" + szSrcAssets + "] to [" + szDstSD + "]"); | ||||
|         AssetManager assetManager = context.getAssets(); | ||||
|         InputStream inputStream = null; | ||||
|         OutputStream outputStream = null; | ||||
|         try { | ||||
|             inputStream = assetManager.open(szSrcAssets); | ||||
|             File outputFile = new File(szDstSD); | ||||
|             outputStream = new FileOutputStream(outputFile); | ||||
|             byte[] buffer = new byte[1024]; | ||||
|             int length = 0; | ||||
|             while ((length = inputStream.read(buffer)) > 0) { | ||||
|                 outputStream.write(buffer, 0, length); | ||||
|             } | ||||
|             outputStream.flush(); | ||||
|             LogUtils.d(TAG, "copyAssetsToSD done."); | ||||
|         } catch (IOException e) { | ||||
|             LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); | ||||
|         } finally { | ||||
|             if (inputStream != null) { | ||||
|                 try { | ||||
|                     inputStream.close(); | ||||
|                 } catch (IOException e) { | ||||
|                     LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); | ||||
|                 } | ||||
|             } | ||||
|             if (outputStream != null) { | ||||
|                 try { | ||||
|                     outputStream.close(); | ||||
|                 } catch (IOException e) { | ||||
|                     LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,37 @@ | ||||
| package cc.winboll.studio.shared.util; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/12/19 15:26:58 | ||||
|  */ | ||||
| import java.math.BigInteger; | ||||
| import java.security.MessageDigest; | ||||
| import java.security.NoSuchAlgorithmException; | ||||
|  | ||||
| public class MD5Utils { | ||||
|     public static String encrypt(String input) { | ||||
|         try { | ||||
|             // 获取MD5实例 | ||||
|             MessageDigest md = MessageDigest.getInstance("MD5"); | ||||
|             // 对输入的字符串进行摘要计算,得到字节数组 | ||||
|             byte[] messageDigest = md.digest(input.getBytes()); | ||||
|             // 将字节数组转换为十六进制的字符串表示形式 | ||||
|             BigInteger no = new BigInteger(1, messageDigest); | ||||
|             String hashtext = no.toString(16); | ||||
|             while (hashtext.length() < 32) { | ||||
|                 hashtext = "0" + hashtext; | ||||
|             } | ||||
|             return hashtext; | ||||
|         } catch (NoSuchAlgorithmException e) { | ||||
|             e.printStackTrace(); | ||||
|             return null; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static void main(String[] args) { | ||||
|         String str = "要加密的字符串"; | ||||
|         String encryptedStr = encrypt(str); | ||||
|         System.out.println(encryptedStr); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -0,0 +1,33 @@ | ||||
| package cc.winboll.studio.shared.util; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/11/28 15:03:12 | ||||
|  * @Describe 应用变量保存工具 | ||||
|  */ | ||||
| import android.content.Context; | ||||
| import android.content.SharedPreferences; | ||||
| import android.widget.EditText; | ||||
|  | ||||
| public class PrefUtils { | ||||
|      | ||||
|     public static final String TAG = "PrefUtils"; | ||||
|      | ||||
|     // | ||||
|     // 保存字符串到SharedPreferences的函数 | ||||
|     // | ||||
|     public static void saveString(Context context, String key, String value) { | ||||
|         SharedPreferences sharedPreferences = context.getSharedPreferences("myPrefs", Context.MODE_PRIVATE); | ||||
|         SharedPreferences.Editor editor = sharedPreferences.edit(); | ||||
|         editor.putString(key, value); | ||||
|         editor.apply(); | ||||
|     } | ||||
|      | ||||
|     // | ||||
|     // 从SharedPreferences读取字符串的函数 | ||||
|     // | ||||
|     public static String getString(Context context, String key, String defaultValue) { | ||||
|         SharedPreferences sharedPreferences = context.getSharedPreferences("myPrefs", Context.MODE_PRIVATE); | ||||
|         return sharedPreferences.getString(key, defaultValue); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,36 @@ | ||||
| package cc.winboll.studio.shared.util; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/12/08 20:09:02 | ||||
|  * @Describe  应用服务组件工具类 | ||||
|  */ | ||||
| import android.app.ActivityManager; | ||||
| import android.content.Context; | ||||
| import java.util.List; | ||||
|  | ||||
| public class ServiceUtils { | ||||
|      | ||||
|     public static final String TAG = "ServiceUtils"; | ||||
|      | ||||
|     public static boolean isServiceAlive(Context context, String szServiceName) { | ||||
|         // 获取Activity管理者对象 | ||||
|         ActivityManager manager = (ActivityManager) context | ||||
|             .getSystemService(Context.ACTIVITY_SERVICE); | ||||
|         // 获取正在运行的服务(此处设置最多取1000个) | ||||
|         List<ActivityManager.RunningServiceInfo> runningServices = manager | ||||
|             .getRunningServices(1000); | ||||
|         if (runningServices.size() <= 0) { | ||||
|             return false; | ||||
|         } | ||||
|         // 遍历,若存在名字和传入的serviceName的一致则说明存在 | ||||
|         for (ActivityManager.RunningServiceInfo runningServiceInfo : runningServices) { | ||||
|             if (runningServiceInfo.service.getClassName().equals(szServiceName)) { | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return false; | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -0,0 +1,130 @@ | ||||
| package cc.winboll.studio.shared.util; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Describe Uri 资源管理工具类 | ||||
|  */ | ||||
| import android.content.ContentResolver; | ||||
| import android.content.Context; | ||||
| import android.database.Cursor; | ||||
| import android.net.Uri; | ||||
| import android.provider.MediaStore; | ||||
| import java.io.File; | ||||
| import java.io.FileOutputStream; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.io.OutputStream; | ||||
|  | ||||
| public class UriUtils { | ||||
|  | ||||
|     public static final String TAG = "UriUtil"; | ||||
|  | ||||
|     // | ||||
|     // 获取真实路径 | ||||
|     // | ||||
|     // @param context | ||||
|     // | ||||
|     public static String getFileFromUri(Context context, Uri uri) { | ||||
|         if (uri == null) { | ||||
|             return null; | ||||
|         } | ||||
|         switch (uri.getScheme()) { | ||||
|             case ContentResolver.SCHEME_CONTENT: | ||||
|                 //Android7.0之后的uri content:// URI | ||||
|                 return getFilePathFromContentUri(context, uri); | ||||
|             case ContentResolver.SCHEME_FILE: | ||||
|             default: | ||||
|                 //Android7.0之前的uri file:// | ||||
|                 return new File(uri.getPath()).getAbsolutePath(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     // 从uri获取path | ||||
|     // | ||||
|     // @param uri content://media/external/file/109009 | ||||
|     //            <p> | ||||
|     //            FileProvider适配 | ||||
|     //            content://com.tencent.mobileqq.fileprovider/external_files/storage/emulated/0/Tencent/QQfile_recv/ | ||||
|     //            content://com.tencent.mm.external.fileprovider/external/tencent/MicroMsg/Download/ | ||||
|     // | ||||
|     private static String getFilePathFromContentUri(Context context, Uri uri) { | ||||
|         if (null == uri) return null; | ||||
|         String data = null; | ||||
|  | ||||
|         String[] filePathColumn = {MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.DISPLAY_NAME}; | ||||
|         Cursor cursor = context.getContentResolver().query(uri, filePathColumn, null, null, null); | ||||
|         if (null != cursor) { | ||||
|             if (cursor.moveToFirst()) { | ||||
|                 int index = cursor.getColumnIndex(MediaStore.MediaColumns.DATA); | ||||
|                 if (index > -1) { | ||||
|                     data = cursor.getString(index); | ||||
|                 } else { | ||||
|                     int nameIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME); | ||||
|                     String fileName = cursor.getString(nameIndex); | ||||
|                     data = getPathFromInputStreamUri(context, uri, fileName); | ||||
|                 } | ||||
|             } | ||||
|             cursor.close(); | ||||
|         } | ||||
|         return data; | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     // 用流拷贝文件一份到自己APP私有目录下 | ||||
|     // | ||||
|     // @param context | ||||
|     // @param uri | ||||
|     // @param fileName | ||||
|     // | ||||
|     private static String getPathFromInputStreamUri(Context context, Uri uri, String fileName) { | ||||
|         InputStream inputStream = null; | ||||
|         String filePath = null; | ||||
|  | ||||
|         if (uri.getAuthority() != null) { | ||||
|             try { | ||||
|                 inputStream = context.getContentResolver().openInputStream(uri); | ||||
|                 File file = createTemporalFileFrom(context, inputStream, fileName); | ||||
|                 filePath = file.getPath(); | ||||
|  | ||||
|             } catch (Exception e) { | ||||
|             } finally { | ||||
|                 try { | ||||
|                     if (inputStream != null) { | ||||
|                         inputStream.close(); | ||||
|                     } | ||||
|                 } catch (Exception e) { | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return filePath; | ||||
|     } | ||||
|  | ||||
|     private static File createTemporalFileFrom(Context context, InputStream inputStream, String fileName) | ||||
|     throws IOException { | ||||
|         File targetFile = null; | ||||
|         if (inputStream != null) { | ||||
|             int read; | ||||
|             byte[] buffer = new byte[8 * 1024]; | ||||
|             //自己定义拷贝文件路径 | ||||
|             targetFile = new File(context.getExternalCacheDir(), fileName); | ||||
|             if (targetFile.exists()) { | ||||
|                 targetFile.delete(); | ||||
|             } | ||||
|             OutputStream outputStream = new FileOutputStream(targetFile); | ||||
|  | ||||
|             while ((read = inputStream.read(buffer)) != -1) { | ||||
|                 outputStream.write(buffer, 0, read); | ||||
|             } | ||||
|             outputStream.flush(); | ||||
|  | ||||
|             try { | ||||
|                 outputStream.close(); | ||||
|             } catch (IOException e) { | ||||
|                 e.printStackTrace(); | ||||
|             } | ||||
|         } | ||||
|         return targetFile; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,345 @@ | ||||
| package cc.winboll.studio.shared.view; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/08/12 14:38:03 | ||||
|  * @Describe AboutView | ||||
|  */ | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.content.pm.PackageManager; | ||||
| import android.content.res.TypedArray; | ||||
| import android.net.Uri; | ||||
| import android.os.Message; | ||||
| import android.util.AttributeSet; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.View; | ||||
| import android.widget.EditText; | ||||
| import android.widget.LinearLayout; | ||||
| import cc.winboll.studio.R; | ||||
| import cc.winboll.studio.shared.app.AppVersionUtils; | ||||
| import cc.winboll.studio.shared.app.WinBollActivityManager; | ||||
| import cc.winboll.studio.shared.app.WinBollApplication; | ||||
| import cc.winboll.studio.shared.bean.DebugBean; | ||||
| import cc.winboll.studio.shared.log.LogUtils; | ||||
| import cc.winboll.studio.shared.util.PrefUtils; | ||||
| import com.hjq.toast.ToastUtils; | ||||
| import java.io.IOException; | ||||
| import mehdi.sakout.aboutpage.AboutPage; | ||||
| import mehdi.sakout.aboutpage.Element; | ||||
| import okhttp3.Call; | ||||
| import okhttp3.Callback; | ||||
| import okhttp3.Credentials; | ||||
| import okhttp3.OkHttpClient; | ||||
| import okhttp3.Request; | ||||
| import okhttp3.Response; | ||||
|  | ||||
| public class AboutView extends LinearLayout { | ||||
|  | ||||
|     public static final String TAG = "AboutView"; | ||||
|  | ||||
|     public static final int MSG_APPUPDATE_CHECKED = 0; | ||||
|  | ||||
|     Context mContext; | ||||
|     WinBollServiceStatusView mWinBollServiceStatusView; | ||||
|     OnRequestDevUserInfoAutofillListener mOnRequestDevUserInfoAutofillListener; | ||||
|     String mszAppName = ""; | ||||
|     String mszAppAPKFolderName = ""; | ||||
|     String mszAppAPKName = ""; | ||||
|     String mszAppGitName = ""; | ||||
|     String mszAppVersionName = ""; | ||||
|     String mszCurrentAppPackageName = ""; | ||||
|     volatile String mszNewestAppPackageName = ""; | ||||
|     String mszAppDescription = ""; | ||||
|     String mszHomePage = ""; | ||||
|     String mszGitea = ""; | ||||
|     int mnAppIcon = 0; | ||||
|     String mszWinBollServerHost; | ||||
|     String mszReleaseAPKName; | ||||
|     EditText metDevUserName; | ||||
|     EditText metDevUserPassword; | ||||
|  | ||||
|     public AboutView(Context context, AttributeSet attrs) { | ||||
|         super(context, attrs); | ||||
|         initView(context, attrs); | ||||
|     } | ||||
|  | ||||
|     void initView(Context context, AttributeSet attrs) { | ||||
|         mContext = context; | ||||
|         mszWinBollServerHost = WinBollApplication.isDebug() ?  "http://10.8.0.13": "https://www.winboll.cc"; | ||||
|         TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.AboutView); | ||||
|         mszAppName = typedArray.getString(R.styleable.AboutView_app_name); | ||||
|         mszAppAPKFolderName = typedArray.getString(R.styleable.AboutView_app_apkfoldername); | ||||
|         mszAppAPKName = typedArray.getString(R.styleable.AboutView_app_apkname); | ||||
|         mszAppGitName = typedArray.getString(R.styleable.AboutView_app_gitname); | ||||
|         mszAppDescription = typedArray.getString(R.styleable.AboutView_appdescription); | ||||
|         mnAppIcon = typedArray.getResourceId(R.styleable.AboutView_appicon, R.drawable.ic_winboll); | ||||
|         // 返回一个绑定资源结束的信号给资源 | ||||
|         typedArray.recycle(); | ||||
|  | ||||
|         try { | ||||
|             mszAppVersionName = mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0).versionName; | ||||
|         } catch (PackageManager.NameNotFoundException e) { | ||||
|             LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|         } | ||||
|         mszCurrentAppPackageName = mszAppName + "_" + mszAppVersionName + ".apk"; | ||||
|         mszHomePage = mszWinBollServerHost + "/studio/details.php?app=" + mszAppAPKFolderName; | ||||
|         mszGitea = "https://gitea.winboll.cc/Studio/" + mszAppGitName + ".git"; | ||||
|  | ||||
|         if (WinBollApplication.isDebug()) { | ||||
|             LayoutInflater inflater = LayoutInflater.from(mContext); | ||||
|             View addedView = inflater.inflate(R.layout.view_about_dev, this, false); | ||||
|             LinearLayout llMain = addedView.findViewById(R.id.viewaboutdevLinearLayout1); | ||||
|             metDevUserName = addedView.findViewById(R.id.viewaboutdevEditText1); | ||||
|             metDevUserPassword = addedView.findViewById(R.id.viewaboutdevEditText2); | ||||
|             metDevUserName.setText(PrefUtils.getString(mContext, "metDevUserName", "")); | ||||
|             metDevUserPassword.setText(PrefUtils.getString(mContext, "metDevUserPassword", "")); | ||||
|             //mDevelopHostConnectionStatusView = new DevelopHostConnectionStatusView(context); | ||||
|             mWinBollServiceStatusView = addedView.findViewById(R.id.viewaboutdevWinBollServiceStatusView1); | ||||
|             mWinBollServiceStatusView.setServerHost(mszWinBollServerHost); | ||||
|             mWinBollServiceStatusView.setAuthInfo(metDevUserName.getText().toString(), metDevUserPassword.getText().toString()); | ||||
|             //llMain.addView(mDevelopHostConnectionStatusView); | ||||
|             llMain.addView(createAboutPage()); | ||||
|             addView(addedView); | ||||
|         } else { | ||||
|             LayoutInflater inflater = LayoutInflater.from(mContext); | ||||
|             View addedView = inflater.inflate(R.layout.view_about_www, this, false); | ||||
|             LinearLayout llMain = addedView.findViewById(R.id.viewaboutwwwLinearLayout1); | ||||
|             //mDevelopHostConnectionStatusView = new DevelopHostConnectionStatusView(context); | ||||
|             mWinBollServiceStatusView = addedView.findViewById(R.id.viewaboutwwwWinBollServiceStatusView1); | ||||
|             mWinBollServiceStatusView.setServerHost(mszWinBollServerHost); | ||||
|             mWinBollServiceStatusView.setAuthInfo("", ""); | ||||
|             //llMain.addView(mDevelopHostConnectionStatusView); | ||||
|             llMain.addView(createAboutPage()); | ||||
|             addView(addedView); | ||||
|         } | ||||
|  | ||||
|         // 初始化标题栏 | ||||
|         //setSubtitle(getContext().getString(R.string.text_about)); | ||||
|         //LinearLayout llMain = findViewById(R.id.viewaboutLinearLayout1); | ||||
|         //llMain.addView(createAboutPage()); | ||||
|  | ||||
|         // 就读取正式版应用包版本号,设置 Release 应用包文件名 | ||||
|         String szReleaseAppVersionName = ""; | ||||
|         try { | ||||
|             szReleaseAppVersionName = mContext.getPackageManager().getPackageInfo(subBetaSuffix(mContext.getPackageName()), 0).versionName; | ||||
|         } catch (PackageManager.NameNotFoundException e) { | ||||
|             LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|         } | ||||
|         mszReleaseAPKName = mszAppAPKName + "_" + szReleaseAppVersionName + ".apk"; | ||||
|  | ||||
|  | ||||
|     } | ||||
|  | ||||
|     public static String subBetaSuffix(String input) { | ||||
|         if (input.endsWith(".beta")) { | ||||
|             return input.substring(0, input.length() - ".beta".length()); | ||||
|         } | ||||
|         return input; | ||||
|     } | ||||
|  | ||||
|     android.os.Handler mHandler = new android.os.Handler() { | ||||
|         @Override | ||||
|         public void handleMessage(Message msg) { | ||||
|             super.handleMessage(msg); | ||||
|             switch (msg.what) { | ||||
|                 case MSG_APPUPDATE_CHECKED : { | ||||
|                         /*//检查当前应用包文件名是否是测试版,如果是就忽略检查 | ||||
|                          if(mszCurrentAppPackageName.matches(".*_\\d+\\.\\d+\\.\\d+-beta.*\\.apk")) { | ||||
|                          ToastUtils.show("APP is the beta Version. Version check ignore."); | ||||
|                          return; | ||||
|                          }*/ | ||||
|  | ||||
|                         if (!AppVersionUtils.isHasNewStageReleaseVersion(mszReleaseAPKName, mszNewestAppPackageName)) { | ||||
|                             ToastUtils.delayedShow("Current app is the newest.", 5000); | ||||
|                         } else { | ||||
|                             String szMsg = "Current app is :\n[ " + mszReleaseAPKName | ||||
|                                 + " ]\nThe last app is :\n[ " + mszNewestAppPackageName | ||||
|                                 + " ]\nIs download the last app?"; | ||||
|                             YesNoAlertDialog.show(mContext, "Application Update Prompt", szMsg, mIsDownlaodUpdateListener); | ||||
|                         } | ||||
|                         break; | ||||
|                     } | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     protected View createAboutPage() { | ||||
|         // 定义应用调试按钮 | ||||
|         // | ||||
|         Element elementAppMode; | ||||
|         if (WinBollApplication.isDebug()) { | ||||
|             elementAppMode = new Element(mContext.getString(R.string.app_normal), R.drawable.ic_winboll); | ||||
|             elementAppMode.setOnClickListener(mAppNormalOnClickListener); | ||||
|         } else { | ||||
|             elementAppMode = new Element(mContext.getString(R.string.app_debug), R.drawable.ic_winboll); | ||||
|             elementAppMode.setOnClickListener(mAppDebugOnClickListener); | ||||
|         } | ||||
|         // 定义 GitWeb 按钮 | ||||
|         // | ||||
|         Element elementGitWeb = new Element(mContext.getString(R.string.gitea_home), R.drawable.ic_winboll); | ||||
|         elementGitWeb.setOnClickListener(mGitWebOnClickListener); | ||||
|         // 定义检查更新按钮 | ||||
|         // | ||||
|         Element elementAppUpdate = new Element(mContext.getString(R.string.app_update), R.drawable.ic_winboll); | ||||
|         elementAppUpdate.setOnClickListener(mAppUpdateOnClickListener); | ||||
|  | ||||
|         String szAppInfo = ""; | ||||
|         try { | ||||
|             szAppInfo = mszAppName + " " | ||||
|                 + mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0).versionName | ||||
|                 + "\n" + mszAppDescription; | ||||
|         } catch (PackageManager.NameNotFoundException e) { | ||||
|             LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|         } | ||||
|         View aboutPage = new AboutPage(mContext) | ||||
|             .setDescription(szAppInfo) | ||||
|             //.isRTL(false) | ||||
|             //.setCustomFont(String) // or Typeface | ||||
|             .setImage(mnAppIcon) | ||||
|             //.addItem(versionElement) | ||||
|             //.addItem(adsElement) | ||||
|             //.addGroup("Connect with us") | ||||
|             .addEmail("ZhanGSKen@QQ.COM") | ||||
|             .addWebsite(mszHomePage) | ||||
|             .addItem(elementAppMode) | ||||
|             .addItem(elementGitWeb) | ||||
|             .addItem(elementAppUpdate) | ||||
|             //.addFacebook("the.medy") | ||||
|             //.addTwitter("medyo80") | ||||
|             //.addYoutube("UCdPQtdWIsg7_pi4mrRu46vA") | ||||
|             //.addPlayStore("com.ideashower.readitlater.pro") | ||||
|             //.addGitHub("medyo") | ||||
|             //.addInstagram("medyo80") | ||||
|             .create(); | ||||
|         return aboutPage; | ||||
|     } | ||||
|  | ||||
|     View.OnClickListener mAppDebugOnClickListener = new View.OnClickListener(){ | ||||
|         @Override | ||||
|         public void onClick(View view) { | ||||
|             setApp2DebugMode(mContext); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     View.OnClickListener mAppNormalOnClickListener = new View.OnClickListener(){ | ||||
|         @Override | ||||
|         public void onClick(View view) { | ||||
|             setApp2NormalMode(mContext); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     public static void setApp2DebugMode(Context context) { | ||||
|         Intent intent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName()); | ||||
|         if (intent != null) { | ||||
|             intent.setAction(cc.winboll.studio.intent.action.DEBUGVIEW); | ||||
|             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||||
|             WinBollApplication.setIsDebug(true); | ||||
|             DebugBean.saveBean(context, new DebugBean(true)); | ||||
|  | ||||
|             WinBollActivityManager.getInstance(context).finishAll(); | ||||
|             context.startActivity(intent); | ||||
|         }  | ||||
|     } | ||||
|  | ||||
|     public static void setApp2NormalMode(Context context) { | ||||
|         Intent intent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName()); | ||||
|         if (intent != null) { | ||||
|             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||||
|             WinBollApplication.setIsDebug(false); | ||||
|             DebugBean.saveBean(context, new DebugBean(false)); | ||||
|  | ||||
|             WinBollActivityManager.getInstance(context).finishAll(); | ||||
|             context.startActivity(intent); | ||||
|         }  | ||||
|     } | ||||
|  | ||||
|     View.OnClickListener mGitWebOnClickListener = new View.OnClickListener(){ | ||||
|         @Override | ||||
|         public void onClick(View view) { | ||||
|             Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(mszGitea)); | ||||
|             mContext.startActivity(browserIntent); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     View.OnClickListener mAppUpdateOnClickListener = new View.OnClickListener(){ | ||||
|         @Override | ||||
|         public void onClick(View view) { | ||||
|             ToastUtils.show("Start app update checking."); | ||||
|             new Thread(new Runnable() { | ||||
|                     @Override | ||||
|                     public void run() { | ||||
|                         String szUrl = mszWinBollServerHost + "/studio/details.php?app=" + mszAppAPKFolderName; | ||||
|                         // 构建包含认证信息的请求 | ||||
|                         String credential = ""; | ||||
|                         if (WinBollApplication.isDebug()) { | ||||
|                             credential = Credentials.basic(metDevUserName.getText().toString(), metDevUserPassword.getText().toString()); | ||||
|                             PrefUtils.saveString(mContext, "metDevUserName", metDevUserName.getText().toString()); | ||||
|                             PrefUtils.saveString(mContext, "metDevUserPassword", metDevUserPassword.getText().toString()); | ||||
|                         } | ||||
|                         OkHttpClient client = new OkHttpClient(); | ||||
|                         Request request = new Request.Builder() | ||||
|                             .url(szUrl) | ||||
|                             .header("Accept", "text/plain") // 设置正确的Content-Type头 | ||||
|                             .header("Authorization", credential) | ||||
|                             .build(); | ||||
|                         Call call = client.newCall(request); | ||||
|                         call.enqueue(new Callback() { | ||||
|                                 @Override | ||||
|                                 public void onFailure(Call call, IOException e) { | ||||
|                                     // 处理网络请求失败 | ||||
|                                     LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|                                 } | ||||
|  | ||||
|                                 @Override | ||||
|                                 public void onResponse(Call call, Response response) throws IOException { | ||||
|                                     if (!response.isSuccessful()) { | ||||
|                                         LogUtils.d(TAG, "Unexpected code " + response, Thread.currentThread().getStackTrace()); | ||||
|                                         return; | ||||
|                                     } | ||||
|  | ||||
|                                     try { | ||||
|                                         // 读取响应体作为字符串,注意这里可能需要解码 | ||||
|                                         String text = response.body().string(); | ||||
|                                         org.jsoup.nodes.Document doc = org.jsoup.Jsoup.parse(text); | ||||
|                                         LogUtils.v(TAG, doc.text()); | ||||
|  | ||||
|                                         // 使用id选择器找到具有特定id的元素 | ||||
|                                         org.jsoup.nodes.Element elementWithId = doc.select("#LastRelease").first(); // 获取第一个匹配的元素 | ||||
|  | ||||
|                                         // 提取并打印元素的文本内容 | ||||
|                                         mszNewestAppPackageName = elementWithId.text(); | ||||
|                                         //ToastUtils.delayedShow(text + "\n" + mszNewestAppPackageName, 5000); | ||||
|  | ||||
|                                         mHandler.sendMessage(mHandler.obtainMessage(MSG_APPUPDATE_CHECKED)); | ||||
|                                     } catch (Exception e) { | ||||
|                                         LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|                                     } | ||||
|                                 } | ||||
|                             }); | ||||
|                     } | ||||
|                 }).start(); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     YesNoAlertDialog.OnDialogResultListener mIsDownlaodUpdateListener = new YesNoAlertDialog.OnDialogResultListener() { | ||||
|         @Override | ||||
|         public void onYes() { | ||||
|             String szUrl = mszWinBollServerHost + "/studio/download.php?appname=" + mszAppAPKFolderName + "&apkname=" + mszNewestAppPackageName; | ||||
|             Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(szUrl)); | ||||
|             mContext.startActivity(browserIntent); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void onNo() { | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     public interface OnRequestDevUserInfoAutofillListener { | ||||
|         void requestAutofill(EditText etDevUserName, EditText etDevUserPassword); | ||||
|     } | ||||
|  | ||||
|     public void setOnRequestDevUserInfoAutofillListener(OnRequestDevUserInfoAutofillListener l) { | ||||
|         mOnRequestDevUserInfoAutofillListener = l; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,38 @@ | ||||
| package cc.winboll.studio.shared.view; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2025/01/03 11:05:45 | ||||
|  * @Describe 简单网页视图类 | ||||
|  */ | ||||
| import android.content.Context; | ||||
| import android.util.AttributeSet; | ||||
| import android.webkit.WebSettings; | ||||
| import android.webkit.WebView; | ||||
|  | ||||
| public class SimpleWebView extends WebView { | ||||
|  | ||||
|     public static final String TAG = "SimpleWebView"; | ||||
|  | ||||
|     public SimpleWebView(Context context) { | ||||
|         super(context); | ||||
|         initWebView(); | ||||
|     } | ||||
|  | ||||
|     public SimpleWebView(Context context, AttributeSet attrs) { | ||||
|         super(context, attrs); | ||||
|         initWebView(); | ||||
|     } | ||||
|  | ||||
|     public SimpleWebView(Context context, AttributeSet attrs, int defStyleAttr) { | ||||
|         super(context, attrs, defStyleAttr); | ||||
|         initWebView(); | ||||
|     } | ||||
|  | ||||
|     private void initWebView() { | ||||
|         // 获取WebView的设置对象 | ||||
|         WebSettings webSettings = getSettings(); | ||||
|         // 启用JavaScript | ||||
|         webSettings.setJavaScriptEnabled(true); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,107 @@ | ||||
| package cc.winboll.studio.shared.view; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/12/19 13:49:14 | ||||
|  */ | ||||
| import android.content.Context; | ||||
| import android.graphics.Bitmap; | ||||
| import android.graphics.BitmapFactory; | ||||
| import android.graphics.drawable.BitmapDrawable; | ||||
| import android.graphics.drawable.Drawable; | ||||
| import android.view.View; | ||||
| import android.widget.ImageView; | ||||
| import android.widget.LinearLayout; | ||||
| import android.widget.TextView; | ||||
| import cc.winboll.studio.R; | ||||
| import cc.winboll.studio.shared.log.LogUtils; | ||||
| import com.google.zxing.EncodeHintType; | ||||
| import com.hjq.toast.ToastUtils; | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.nio.file.Files; | ||||
| import java.nio.file.Paths; | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
| import java.util.UUID; | ||||
|  | ||||
| public class StringToQrCodeView extends LinearLayout { | ||||
|  | ||||
|     static String TAG = "StringToQrCodeView"; | ||||
|  | ||||
|     public static final String ACTION_UNITTEST_QRCODE = StringToQrCodeView.class.getName() + "_ACTION_UNITTEST_QRCODE"; | ||||
|     public static final String EXTRA_UNITTEST_QRCODE_IMAGENULL_SRCPATH = StringToQrCodeView.class.getName() + "_EXTRA_UNITTEST_QRCODE_IMAGENULL_SRCPATH"; | ||||
|  | ||||
|     static Context _Context; | ||||
|     static TextView mTextView; | ||||
|     static ImageView mImageView; | ||||
|  | ||||
|     public StringToQrCodeView(Context context) { | ||||
|         super(context); | ||||
|         _Context = context; | ||||
|         View view = inflate(context, R.layout.view_string2qrcode, null); | ||||
|         mTextView = view.findViewById(R.id.viewstring2qrcodeTextView1); | ||||
|         mImageView = view.findViewById(R.id.viewstring2qrcodeImageView1); | ||||
|         addView(view); | ||||
|     } | ||||
|  | ||||
|     public void stringToQrCode(String text) { | ||||
|         String imageFilePath = StringToQrCode.stringToQrCode(text); | ||||
|         if (!imageFilePath.equals("")) { | ||||
|             // 在Java中 | ||||
|             //String filePath = "/storage/emulated/0/your_folder/your_png_file.png"; | ||||
|             String filePath = imageFilePath; | ||||
|             Bitmap bitmap = BitmapFactory.decodeFile(filePath); | ||||
|             Drawable drawable = new BitmapDrawable(getResources(), bitmap); | ||||
|             mTextView.setText(text); | ||||
|             mImageView.setBackground(drawable); | ||||
|             ToastUtils.show("filePath : " + filePath); | ||||
|         } else { | ||||
|             mTextView.setText("NULL"); | ||||
|             ToastUtils.show("NULL"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     static class StringToQrCode { | ||||
|  | ||||
|         static String TAG = "StringToQrCode";  | ||||
|  | ||||
|         public static String stringToQrCode(String text) {  | ||||
|             //String text = "这是一个示例字符串";  | ||||
|             text = "这是一个示例字符串"; | ||||
|             int width = 300;  | ||||
|             int height = 300;  | ||||
|             String format = "png";  | ||||
|  | ||||
|             Map<EncodeHintType, Object> hints = new HashMap<>();  | ||||
|             hints.put(EncodeHintType.CHARACTER_SET, "UTF - 8");  | ||||
|  | ||||
|             //try { | ||||
|             //String qrFileName = MD5Utils.encrypt(UUID.randomUUID().toString()) + ".dat"; | ||||
|             String qrFileName = UUID.randomUUID().toString() + ".dat"; | ||||
|             File outputFile = new File(_Context.getCacheDir(), File.separator + qrFileName);  | ||||
|             //ToastUtils.show("outputFile : " + outputFile.getAbsolutePath()); | ||||
|  | ||||
|             /*BitMatrix bitMatrix = new MultiFormatWriter().encode(text, BarcodeFormat.QR_CODE, width, height, hints);  | ||||
|              MatrixToImageWriter.writeToFile(bitMatrix, format, outputFile);  | ||||
|              //System.out.println("二维码已生成: " + outputFile.getAbsolutePath());  | ||||
|              //_ImageView.setBackground(); | ||||
|              String msg = "二维码已生成: " + outputFile.getAbsolutePath(); | ||||
|              ToastUtils.show(msg); | ||||
|              return outputFile.getAbsolutePath(); | ||||
|  | ||||
|              } catch (WriterException | IOException e) {  | ||||
|              //e.printStackTrace(); | ||||
|              LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|              }*/ | ||||
|             try { | ||||
|                 Files.copy(Paths.get("/storage/emulated/0/Pictures/Gallery/owner/篮球/kuX8B6aXAG_small-选取图标位置-透明背景.png"), | ||||
|                            Paths.get(outputFile.getPath())); | ||||
|                 return outputFile.getAbsolutePath(); | ||||
|             } catch (IOException e) { | ||||
|                 LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|             } | ||||
|             return ""; | ||||
|         }  | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,347 @@ | ||||
| package cc.winboll.studio.shared.view; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/12/07 20:15:47 | ||||
|  * @Describe WinBoll 服务主机连接状态视图 | ||||
|  */ | ||||
| import android.content.ComponentName; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.content.ServiceConnection; | ||||
| import android.graphics.drawable.Drawable; | ||||
| import android.os.Handler; | ||||
| import android.os.IBinder; | ||||
| import android.os.Message; | ||||
| import android.util.AttributeSet; | ||||
| import android.view.View; | ||||
| import android.widget.ImageView; | ||||
| import android.widget.LinearLayout; | ||||
| import android.widget.TextView; | ||||
| import androidx.core.content.ContextCompat; | ||||
| import cc.winboll.studio.R; | ||||
| import cc.winboll.studio.shared.log.LogUtils; | ||||
| import cc.winboll.studio.shared.service.IWinBollClientServiceBinder; | ||||
| import cc.winboll.studio.shared.service.WinBollClientService; | ||||
| import cc.winboll.studio.shared.service.WinBollClientServiceBean; | ||||
| import com.hjq.toast.ToastUtils; | ||||
| import java.io.IOException; | ||||
| import java.time.LocalDateTime; | ||||
| import java.time.format.DateTimeFormatter; | ||||
| import okhttp3.Authenticator; | ||||
| import okhttp3.Credentials; | ||||
| import okhttp3.OkHttpClient; | ||||
| import okhttp3.Request; | ||||
| import okhttp3.Response; | ||||
| import okhttp3.Route; | ||||
|  | ||||
| public class WinBollServiceStatusView extends LinearLayout { | ||||
|  | ||||
|     public static final String TAG = "WinBollServiceStatusView"; | ||||
|  | ||||
|     public static final int MSG_CONNECTION_INFO = 0; | ||||
|     public static final int MSG_UPDATE_CONNECTION_STATUS = 1; | ||||
|  | ||||
|     Context mContext; | ||||
|     //boolean mIsConnected; | ||||
|     ConnectionThread mConnectionThread; | ||||
|  | ||||
|     String mszServerHost; | ||||
|     WinBollClientService mWinBollService; | ||||
|     ImageView mImageView; | ||||
|     TextView mTextView; | ||||
|     WinBollServiceViewHandler mWinBollServiceViewHandler; | ||||
|     //WebView mWebView; | ||||
|     static volatile ConnectionStatus mConnectionStatus; | ||||
|     View.OnClickListener mViewOnClickListener; | ||||
|     static String _mUserName; | ||||
|     static String _mPassword; | ||||
|  | ||||
|     static enum ConnectionStatus { | ||||
|         DISCONNECTED, | ||||
|         START_CONNECT, | ||||
|         CONNECTING, | ||||
|         CONNECTED; | ||||
|     }; | ||||
|  | ||||
|     boolean isBound = false; | ||||
|     ServiceConnection connection = new ServiceConnection() { | ||||
|         @Override | ||||
|         public void onServiceConnected(ComponentName name, IBinder service) { | ||||
|             IWinBollClientServiceBinder binder = (IWinBollClientServiceBinder) service; | ||||
|             mWinBollService = binder.getService(); | ||||
|             isBound = true; | ||||
|             // 可以在这里调用Service的方法进行通信,比如获取数据 | ||||
|             mImageView.setBackgroundDrawable(mWinBollService.getCurrentStatusIconDrawable()); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void onServiceDisconnected(ComponentName name) { | ||||
|             isBound = false; | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     public WinBollServiceStatusView(Context context) { | ||||
|         super(context); | ||||
|         mContext = context; | ||||
|         initView(); | ||||
|     } | ||||
|  | ||||
|     public WinBollServiceStatusView(Context context, AttributeSet attrs) { | ||||
|         super(context, attrs); | ||||
|         mContext = context; | ||||
|         initView(); | ||||
|     } | ||||
|  | ||||
|     public WinBollServiceStatusView(Context context, AttributeSet attrs, int defStyleAttr) { | ||||
|         super(context, attrs, defStyleAttr); | ||||
|         mContext = context; | ||||
|         initView(); | ||||
|     } | ||||
|  | ||||
|     public WinBollServiceStatusView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { | ||||
|         super(context, attrs, defStyleAttr, defStyleRes); | ||||
|         mContext = context; | ||||
|         initView(); | ||||
|     } | ||||
|  | ||||
|     ConnectionStatus getConnectionStatus() { | ||||
|         return false ? | ||||
|             ConnectionStatus.CONNECTED  | ||||
|             : ConnectionStatus.DISCONNECTED; | ||||
|     } | ||||
|  | ||||
|     void initView() { | ||||
|         mImageView = new ImageView(mContext); | ||||
|         setImageViewByConnection(mImageView, false); | ||||
|         mConnectionStatus = getConnectionStatus(); | ||||
|         //mIsConnected = false; | ||||
|         //mWinBollServerHostConnectionStatus = WinBollServerHostConnectionStatus.DISCONNECTED; | ||||
|         //ToastUtils.show("initView()"); | ||||
|  | ||||
|         mViewOnClickListener = new View.OnClickListener(){ | ||||
|             @Override | ||||
|             public void onClick(View v) { | ||||
|                 //ToastUtils.show("onClick()"); | ||||
|                 //ToastUtils.show("mWinBollServerHostConnectionStatus : " + mWinBollServerHostConnectionStatus); | ||||
|                 //isConnected = !isConnected; | ||||
|                 if (mConnectionStatus == ConnectionStatus.CONNECTED) { | ||||
|                     ToastUtils.show("Click to stop service."); | ||||
|                     WinBollClientServiceBean bean = WinBollClientServiceBean.loadWinBollClientServiceBean(mContext); | ||||
|                     bean.setIsEnable(false); | ||||
|                     WinBollClientServiceBean.saveBean(mContext, bean); | ||||
|                     Intent intent = new Intent(mContext, WinBollClientService.class); | ||||
|                     mContext.stopService(intent); | ||||
|                     mConnectionStatus = ConnectionStatus.DISCONNECTED; | ||||
| //                   | ||||
|                     /*//ToastUtils.show("CONNECTED"); | ||||
|                      setConnectionStatusView(false); | ||||
|                      mWinBollServerHostConnectionStatusViewHandler.postMessageText(""); | ||||
|                      if (mConnectionThread != null) { | ||||
|                      mConnectionThread.mIsExist = true; | ||||
|                      mConnectionThread = null; | ||||
|                      mWinBollServerHostConnectionStatus = WinBollServerHostConnectionStatus.DISCONNECTED; | ||||
|                      ToastUtils.show("WinBoll Server Disconnected."); | ||||
|                      }*/ | ||||
|                 } else if (mConnectionStatus == ConnectionStatus.DISCONNECTED) { | ||||
|                     ToastUtils.show("Click to start service."); | ||||
|                     WinBollClientServiceBean bean = WinBollClientServiceBean.loadWinBollClientServiceBean(mContext); | ||||
|                     bean.setIsEnable(true); | ||||
|                     WinBollClientServiceBean.saveBean(mContext, bean); | ||||
|                     Intent intent = new Intent(mContext, WinBollClientService.class); | ||||
|                     mContext.startService(intent); | ||||
|                     mConnectionStatus = ConnectionStatus.CONNECTED; | ||||
|                     ToastUtils.show("startService"); | ||||
|                     /*//ToastUtils.show("DISCONNECTED"); | ||||
|                      setConnectionStatusView(true); | ||||
|  | ||||
|                      if (mConnectionThread == null) { | ||||
|                      ToastUtils.show("mConnectionThread == null"); | ||||
|                      mConnectionThread = new ConnectionThread(); | ||||
|                      mWinBollServerHostConnectionStatus = WinBollServerHostConnectionStatus.START_CONNECT; | ||||
|                      mConnectionThread.start(); | ||||
|                      }*/ | ||||
|                 } else { | ||||
|                     ToastUtils.show("Other Click condition."); | ||||
|                 } | ||||
|  | ||||
|                 /*if (isConnected) { | ||||
|                  mWebView.loadUrl("https://dev.winboll.cc"); | ||||
|                  } else { | ||||
|                  mWebView.stopLoading(); | ||||
|                  }*/ | ||||
|                 //ToastUtils.show(mDevelopHostConnectionStatus); | ||||
|                 //LogUtils.d(TAG, "mDevelopHostConnectionStatus : " + mWinBollServerHostConnectionStatus); | ||||
|             } | ||||
|         }; | ||||
|         setOnClickListener(mViewOnClickListener); | ||||
|         addView(mImageView); | ||||
|         mTextView = new TextView(mContext); | ||||
|         mWinBollServiceViewHandler = new WinBollServiceViewHandler(this); | ||||
|         addView(mTextView); | ||||
|         /*mWebView = new WebView(mContext); | ||||
|          mWebView.setWebViewClient(new WebViewClient() { | ||||
|          @Override | ||||
|          public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) { | ||||
|          // 弹出系统基本HTTP验证窗口 | ||||
|          handler.proceed("username", "password"); | ||||
|          } | ||||
|          }); | ||||
|          addView(mWebView);*/ | ||||
|     } | ||||
|  | ||||
|     void checkWinBollServerStatusAndUpdateCurrentView() { | ||||
|         LogUtils.d(TAG, "checkWinBollServerStatusAndUpdateCurrentView()"); | ||||
|         /*if (getConnectionStatus() == ConnectionStatus.CONNECTED) { | ||||
|          mConnectionStatus = ConnectionStatus.CONNECTED; | ||||
|          } else { | ||||
|          mConnectionStatus = ConnectionStatus.DISCONNECTED; | ||||
|          }*/ | ||||
|     } | ||||
|  | ||||
|     public void setServerHost(String szWinBollServerHost) { | ||||
|         mszServerHost = szWinBollServerHost; | ||||
|     } | ||||
|  | ||||
|     public void setAuthInfo(String username, String password) { | ||||
|         _mUserName = username; | ||||
|         _mPassword = password; | ||||
|     } | ||||
|  | ||||
|     void setImageViewByConnection(ImageView imageView, boolean isConnected) { | ||||
|         //mIsConnected = isConnected; | ||||
|         // 获取vector drawable | ||||
|         Drawable drawable = ContextCompat.getDrawable(mContext, isConnected ? R.drawable.ic_dev_connected : R.drawable.ic_dev_disconnected); | ||||
|         if (drawable != null) { | ||||
|             imageView.setImageDrawable(drawable); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void requestWithBasicAuth(WinBollServiceViewHandler textViewHandler, String targetUrl, final String username, final String password) { | ||||
|         // 用户名和密码,替换为实际的认证信息 | ||||
|         //String username = "your_username"; | ||||
|         //String password = "your_password"; | ||||
|  | ||||
|         OkHttpClient client = new OkHttpClient.Builder() | ||||
|             .authenticator(new Authenticator() { | ||||
|                 @Override | ||||
|                 public Request authenticate(Route route, Response response) throws IOException { | ||||
|                     String credential = Credentials.basic(username, password); | ||||
|                     return response.request().newBuilder() | ||||
|                         .header("Authorization", credential) | ||||
|                         .build(); | ||||
|                 } | ||||
|             }) | ||||
|             .build(); | ||||
|  | ||||
|         Request request = new Request.Builder() | ||||
|             .url(targetUrl) // 替换为实际要请求的网页地址 | ||||
|             .build(); | ||||
|  | ||||
|         try { | ||||
|             Response response = client.newCall(request).execute(); | ||||
|             if (response.isSuccessful()) { | ||||
|                 //System.out.println(response.body().string()); | ||||
|                 //ToastUtils.show("Develop Host Connection IP is : " + response.body().string()); | ||||
|                 // 获取当前时间 | ||||
|                 LocalDateTime now = LocalDateTime.now(); | ||||
|  | ||||
|                 // 定义时间格式 | ||||
|                 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"); | ||||
|                 // 按照指定格式格式化时间并输出 | ||||
|                 String formattedDateTime = now.format(formatter); | ||||
|                 //System.out.println(formattedDateTime); | ||||
|                 textViewHandler.postMessageText("ClientIP<" + formattedDateTime + ">: " + response.body().string()); | ||||
|                 textViewHandler.postMessageConnectionStatus(true); | ||||
|             } else { | ||||
|                 String sz = "请求失败,状态码: " + response.code(); | ||||
|                 setImageViewByConnection(mImageView, false); | ||||
|                 textViewHandler.postMessageText(sz); | ||||
|                 textViewHandler.postMessageConnectionStatus(false); | ||||
|                 LogUtils.d(TAG, sz); | ||||
|             } | ||||
|         } catch (IOException e) { | ||||
|             textViewHandler.postMessageText(e.getMessage()); | ||||
|             textViewHandler.postMessageConnectionStatus(false); | ||||
|             LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     class WinBollServiceViewHandler extends Handler { | ||||
|         WinBollServiceStatusView mDevelopHostConnectionStatusView; | ||||
|  | ||||
|         public WinBollServiceViewHandler(WinBollServiceStatusView view) { | ||||
|             mDevelopHostConnectionStatusView = view; | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void handleMessage(Message msg) { | ||||
|             if (msg.what == MSG_CONNECTION_INFO) { | ||||
|                 mDevelopHostConnectionStatusView.mTextView.setText((String)msg.obj); | ||||
|             } else if (msg.what == MSG_UPDATE_CONNECTION_STATUS) { | ||||
|                 mDevelopHostConnectionStatusView.setImageViewByConnection(mImageView, (boolean)msg.obj); | ||||
|                 mDevelopHostConnectionStatusView.mConnectionStatus = ((boolean)msg.obj) ? ConnectionStatus.CONNECTED : ConnectionStatus.DISCONNECTED; | ||||
|             } | ||||
|             super.handleMessage(msg); | ||||
|         } | ||||
|  | ||||
|         void postMessageText(String szMSG) { | ||||
|             Message msg = new Message(); | ||||
|             msg.what = MSG_CONNECTION_INFO; | ||||
|             msg.obj = szMSG; | ||||
|             sendMessage(msg); | ||||
|         } | ||||
|  | ||||
|         void postMessageConnectionStatus(boolean isConnected) { | ||||
|             Message msg = new Message(); | ||||
|             msg.what = MSG_UPDATE_CONNECTION_STATUS; | ||||
|             msg.obj = isConnected; | ||||
|             sendMessage(msg); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     class ConnectionThread extends Thread { | ||||
|  | ||||
|         public volatile boolean mIsExist; | ||||
|  | ||||
|         //DevelopHostConnectionStatusViewHandler mDevelopHostConnectionStatusViewHandler; | ||||
|  | ||||
|         //public ConnectionThread(DevelopHostConnectionStatusViewHandler developHostConnectionStatusViewHandler) { | ||||
|         //mDevelopHostConnectionStatusViewHandler = developHostConnectionStatusViewHandler; | ||||
|         //} | ||||
|         public ConnectionThread() { | ||||
|             mIsExist = false; | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void run() { | ||||
|             super.run(); | ||||
|             while (mIsExist == false) { | ||||
|                 if (mConnectionStatus == ConnectionStatus.START_CONNECT) { | ||||
|                     mConnectionStatus = ConnectionStatus.CONNECTING; | ||||
|                     ToastUtils.show("WinBoll Server Connection Start."); | ||||
|                     //LogUtils.d(TAG, "Develop Host Connection Start."); | ||||
|                     String targetUrl = "https://" + mszServerHost + "/cip/?simple=true";  // 这里替换成你实际要访问的网址 | ||||
|                     requestWithBasicAuth(mWinBollServiceViewHandler, targetUrl, _mUserName, _mPassword); | ||||
|                 } else if (mConnectionStatus == ConnectionStatus.CONNECTED | ||||
|                            && mConnectionStatus == ConnectionStatus.DISCONNECTED) { | ||||
|                     ToastUtils.show("mWinBollServerHostConnectionStatus " + mConnectionStatus); | ||||
|                 } | ||||
|  | ||||
|                 try { | ||||
|                     Thread.sleep(5 * 1000); | ||||
|                 } catch (InterruptedException e) { | ||||
|                     LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|                 } | ||||
|             } | ||||
|             //ToastUtils.show("ConnectionThread exit."); | ||||
|             LogUtils.d(TAG, "ConnectionThread exit."); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /*WinBollService.OnServiceStatusChangeListener mOnServerStatusChangeListener = new WinBollService.OnServiceStatusChangeListener(){ | ||||
|      @Override | ||||
|      public void onServerStatusChange(boolean isServiceAlive) { | ||||
|      } | ||||
|      };*/ | ||||
| } | ||||
| @@ -0,0 +1,59 @@ | ||||
| package cc.winboll.studio.shared.view; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/08/12 14:46:25 | ||||
|  * @Describe 询问用户确定与否的选择框 | ||||
|  */ | ||||
| import android.app.AlertDialog; | ||||
| import android.content.Context; | ||||
| import android.content.DialogInterface; | ||||
|  | ||||
| public class YesNoAlertDialog { | ||||
|  | ||||
|     public static final String TAG = "YesNoAlertDialog"; | ||||
|  | ||||
|     public static void show(Context context, String szTitle, String szMessage, final OnDialogResultListener listener) { | ||||
|         AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder( | ||||
|             context); | ||||
|  | ||||
|         // set title | ||||
|         alertDialogBuilder.setTitle(szTitle); | ||||
|  | ||||
|         // set dialog message | ||||
|         alertDialogBuilder | ||||
|             .setMessage(szMessage) | ||||
|             .setCancelable(true) | ||||
|             .setOnCancelListener(new DialogInterface.OnCancelListener(){ | ||||
|                 @Override | ||||
|                 public void onCancel(DialogInterface dialog) { | ||||
|                     listener.onNo(); | ||||
|                 } | ||||
|             }) | ||||
|             .setPositiveButton("YES", new DialogInterface.OnClickListener() { | ||||
|                 public void onClick(DialogInterface dialog, int id) { | ||||
|                     // if this button is clicked, close | ||||
|                     // current activity | ||||
|                     listener.onYes(); | ||||
|                 } | ||||
|             }) | ||||
|             .setNegativeButton("NO", new DialogInterface.OnClickListener() { | ||||
|                 public void onClick(DialogInterface dialog, int id) { | ||||
|                     // if this button is clicked, just close | ||||
|                     // the dialog box and do nothing | ||||
|                     dialog.cancel(); | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|         // create alert dialog | ||||
|         AlertDialog alertDialog = alertDialogBuilder.create(); | ||||
|  | ||||
|         // show it | ||||
|         alertDialog.show(); | ||||
|     } | ||||
|  | ||||
|     public interface OnDialogResultListener { | ||||
|         abstract void onYes(); | ||||
|         abstract void onNo(); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,114 @@ | ||||
| package cc.winboll.studio.unittest; | ||||
|  | ||||
| import android.content.Intent; | ||||
| import android.net.Uri; | ||||
| import android.os.Bundle; | ||||
| import android.text.TextUtils; | ||||
| import android.view.View; | ||||
| import android.widget.LinearLayout; | ||||
| import androidx.appcompat.widget.Toolbar; | ||||
| import cc.winboll.studio.R; | ||||
| import cc.winboll.studio.shared.app.WinBollActivity; | ||||
| import cc.winboll.studio.shared.log.LogUtils; | ||||
| import cc.winboll.studio.shared.util.UriUtils; | ||||
| import cc.winboll.studio.shared.view.StringToQrCodeView; | ||||
| import com.hjq.toast.ToastUtils; | ||||
| import java.util.UUID; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/12/19 14:00:26 | ||||
|  */ | ||||
| public class UnitTestActivity extends WinBollActivity { | ||||
|  | ||||
|     public static final String TAG = "UnitTestActivity"; | ||||
|  | ||||
|     @Override | ||||
|     public String getTag() { | ||||
|         return this.TAG; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected Toolbar initToolBar() { | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected boolean isEnableDisplayHomeAsUp() { | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected boolean isAddWinBollToolBar() { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|         // 接收并处理 Intent 数据,函数 Intent 处理接收就直接返回 | ||||
|         if (prosessIntents(getIntent())) return; | ||||
|         // 以下正常创建主窗口 | ||||
|         super.onCreate(savedInstanceState); | ||||
|         setContentView(R.layout.activity_unittest); | ||||
|  | ||||
|         // 接收分享数据 | ||||
|         Intent intent = getIntent(); | ||||
|         prosessIntents(getIntent()); | ||||
|  | ||||
|         LinearLayout llMain = findViewById(R.id.activityunittestLinearLayout2); | ||||
|         final StringToQrCodeView stringToQrCodeView = new StringToQrCodeView(this){}; | ||||
|         stringToQrCodeView.setOnClickListener(new View.OnClickListener(){ | ||||
|                 @Override | ||||
|                 public void onClick(View v) { | ||||
|                     ToastUtils.show("onClick"); | ||||
|                     stringToQrCodeView.stringToQrCode(UUID.randomUUID().toString()); | ||||
|                 } | ||||
|             } | ||||
|         ); | ||||
|         stringToQrCodeView.stringToQrCode(UUID.randomUUID().toString()); | ||||
|         llMain.addView(new StringToQrCodeView(this){}); | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     // 处理传入的 Intent 数据 | ||||
|     // | ||||
|     boolean prosessIntents(Intent intent) { | ||||
|         if (intent == null  | ||||
|             || intent.getAction() == null | ||||
|             || intent.getAction().equals("")) | ||||
|             return false; | ||||
|              | ||||
|         if (intent.getAction().equals(StringToQrCodeView.ACTION_UNITTEST_QRCODE)) { | ||||
|             LogUtils.d(TAG, "prosessIntents"); | ||||
|  | ||||
|             StringBuilder sb = new StringBuilder(); | ||||
|             String szSrcPath = ""; | ||||
|  | ||||
|             String action = intent.getAction();//action | ||||
|             String type = intent.getType();//类型 | ||||
|             //LogUtils.d(TAG, "action is " + action); | ||||
|             //LogUtils.d(TAG, "type is " + type); | ||||
|             if ((Intent.ACTION_SEND.equals(action) || Intent.ACTION_VIEW.equals(action) || Intent.ACTION_EDIT.equals(action)) | ||||
|                 && type != null && (("application/json".equals(type)) || ("text/x-json".equals(type)))) { | ||||
|  | ||||
|                 //取出文件uri | ||||
|                 Uri uri = intent.getData(); | ||||
|                 if (uri == null) { | ||||
|                     uri = intent.getParcelableExtra(Intent.EXTRA_STREAM); | ||||
|                 } | ||||
|                 //获取文件真实地址 | ||||
|                 szSrcPath = UriUtils.getFileFromUri(getApplication(), uri); | ||||
|                 if (TextUtils.isEmpty(szSrcPath)) { | ||||
|                     return false; | ||||
|                 } | ||||
|             } else { | ||||
|                 sb.append("Not supported action."); | ||||
|             } | ||||
|             LogUtils.d(TAG, "szSrcPath : " + szSrcPath); | ||||
|         } else { | ||||
|             LogUtils.d(TAG, "prosessIntents|" + intent.getAction() + "|yet"); | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										41
									
								
								winboll-shared/src/main/res/drawable/bg_shadow.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								winboll-shared/src/main/res/drawable/bg_shadow.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?>  | ||||
| <layer-list xmlns:android="http://schemas.android.com/apk/res/android" >  | ||||
|     <!-- 阴影部分 -->  | ||||
|     <!-- 个人觉得更形象的表达:top代表下边的阴影高度,left代表右边的阴影宽度。其实也就是相对应的offset,solid中的颜色是阴影的颜色,也可以设置角度等等 -->  | ||||
|     <item  | ||||
|         android:left="2dp"  | ||||
|         android:top="2dp"  | ||||
|         android:right="2dp"  | ||||
|         android:bottom="2dp">  | ||||
|         <shape android:shape="rectangle" >  | ||||
|             <gradient  | ||||
|                 android:angle="270"  | ||||
|                 android:endColor="#0F000000"  | ||||
|                 android:startColor="#0F000000" />  | ||||
|             <corners  | ||||
|                 android:bottomLeftRadius="6dip"  | ||||
|                 android:bottomRightRadius="6dip"  | ||||
|                 android:topLeftRadius="6dip"  | ||||
|                 android:topRightRadius="6dip" />  | ||||
|         </shape>  | ||||
|     </item>  | ||||
|     <!-- 背景部分 -->  | ||||
|     <!-- 形象的表达:bottom代表背景部分在上边缘超出阴影的高度,right代表背景部分在左边超出阴影的宽度(相对应的offset) -->  | ||||
|     <item  | ||||
|         android:left="3dp"  | ||||
|         android:top="3dp"  | ||||
|         android:right="3dp"  | ||||
|         android:bottom="5dp">  | ||||
|         <shape android:shape="rectangle" >  | ||||
|             <gradient  | ||||
|                 android:angle="270"  | ||||
|                 android:endColor="@color/colorAccent"  | ||||
|                 android:startColor="@color/colorAccent" />  | ||||
|             <corners  | ||||
|                 android:bottomLeftRadius="6dip"  | ||||
|                 android:bottomRightRadius="6dip"  | ||||
|                 android:topLeftRadius="6dip"  | ||||
|                 android:topRightRadius="6dip" />  | ||||
|         </shape>  | ||||
|     </item>  | ||||
| </layer-list> | ||||
							
								
								
									
										11
									
								
								winboll-shared/src/main/res/drawable/ic_dev_connected.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								winboll-shared/src/main/res/drawable/ic_dev_connected.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| <?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="M4,1C2.89,1 2,1.89 2,3V7C2,8.11 2.89,9 4,9H1V11H13V9H10C11.11,9 12,8.11 12,7V3C12,1.89 11.11,1 10,1H4M4,3H10V7H4V3M3,12V14H5V12H3M14,13C12.89,13 12,13.89 12,15V19C12,20.11 12.89,21 14,21H11V23H23V21H20C21.11,21 22,20.11 22,19V15C22,13.89 21.11,13 20,13H14M3,15V17H5V15H3M14,15H20V19H14V15M3,18V20H5V18H3M6,18V20H8V18H6M9,18V20H11V18H9Z"/> | ||||
|  | ||||
| </vector> | ||||
							
								
								
									
										11
									
								
								winboll-shared/src/main/res/drawable/ic_dev_disconnected.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								winboll-shared/src/main/res/drawable/ic_dev_disconnected.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| <?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="M4,1C2.89,1 2,1.89 2,3V7C2,8.11 2.89,9 4,9H1V11H13V9H10C11.11,9 12,8.11 12,7V3C12,1.89 11.11,1 10,1H4M4,3H10V7H4V3M14,13C12.89,13 12,13.89 12,15V19C12,20.11 12.89,21 14,21H11V23H23V21H20C21.11,21 22,20.11 22,19V15C22,13.89 21.11,13 20,13H14M3.88,13.46L2.46,14.88L4.59,17L2.46,19.12L3.88,20.54L6,18.41L8.12,20.54L9.54,19.12L7.41,17L9.54,14.88L8.12,13.46L6,15.59L3.88,13.46M14,15H20V19H14V15Z"/> | ||||
|  | ||||
| </vector> | ||||
							
								
								
									
										11
									
								
								winboll-shared/src/main/res/drawable/ic_email.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								winboll-shared/src/main/res/drawable/ic_email.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| <?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="M22,6C22,4.9 21.1,4 20,4H4C2.9,4 2,4.9 2,6V18C2,19.1 2.9,20 4,20H20C21.1,20 22,19.1 22,18V6M20,6L12,11L4,6H20M20,18H4V8L12,13L20,8V18Z"/> | ||||
|  | ||||
| </vector> | ||||
							
								
								
									
										11
									
								
								winboll-shared/src/main/res/drawable/ic_email_alert.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								winboll-shared/src/main/res/drawable/ic_email_alert.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| <?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="M24,7H22V13H24V7M24,15H22V17H24V15M20,6C20,4.9 19.1,4 18,4H2C0.9,4 0,4.9 0,6V18C0,19.1 0.9,20 2,20H18C19.1,20 20,19.1 20,18V6M18,6L10,11L2,6H18M18,18H2V8L10,13L18,8V18Z"/> | ||||
|  | ||||
| </vector> | ||||
							
								
								
									
										13
									
								
								winboll-shared/src/main/res/drawable/ic_launcher.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								winboll-shared/src/main/res/drawable/ic_launcher.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <layer-list xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:clickable="true"> | ||||
|     <item | ||||
|         android:width="256dp" | ||||
|         android:height="256dp" | ||||
|         android:left="0dp"  | ||||
|         android:top="0dp"  | ||||
|         android:right="0dp"  | ||||
|         android:bottom="0dp" | ||||
|         android:drawable="@drawable/winboll_logo"> | ||||
|     </item> | ||||
| </layer-list> | ||||
							
								
								
									
										170
									
								
								winboll-shared/src/main/res/drawable/ic_launcher_background.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								winboll-shared/src/main/res/drawable/ic_launcher_background.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,170 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:width="108dp" | ||||
|     android:height="108dp" | ||||
|     android:viewportWidth="108" | ||||
|     android:viewportHeight="108"> | ||||
|     <path | ||||
|         android:fillColor="@color/colorPrimary" | ||||
|         android:pathData="M0,0h108v108h-108z" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M9,0L9,108" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M19,0L19,108" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M29,0L29,108" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M39,0L39,108" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M49,0L49,108" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M59,0L59,108" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M69,0L69,108" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M79,0L79,108" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M89,0L89,108" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M99,0L99,108" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,9L108,9" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,19L108,19" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,29L108,29" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,39L108,39" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,49L108,49" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,59L108,59" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,69L108,69" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,79L108,79" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,89L108,89" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,99L108,99" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M19,29L89,29" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M19,39L89,39" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M19,49L89,49" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M19,59L89,59" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M19,69L89,69" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M19,79L89,79" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M29,19L29,89" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M39,19L39,89" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M49,19L49,89" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M59,19L59,89" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M69,19L69,89" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M79,19L79,89" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
| </vector> | ||||
| @@ -0,0 +1,10 @@ | ||||
| <?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="#FFFFFFFF" | ||||
|         android:pathData="M16.61,15.15C16.15,15.15 15.77,14.78 15.77,14.32S16.15,13.5 16.61,13.5H16.61C17.07,13.5 17.45,13.86 17.45,14.32C17.45,14.78 17.07,15.15 16.61,15.15M7.41,15.15C6.95,15.15 6.57,14.78 6.57,14.32C6.57,13.86 6.95,13.5 7.41,13.5H7.41C7.87,13.5 8.24,13.86 8.24,14.32C8.24,14.78 7.87,15.15 7.41,15.15M16.91,10.14L18.58,7.26C18.67,7.09 18.61,6.88 18.45,6.79C18.28,6.69 18.07,6.75 18,6.92L16.29,9.83C14.95,9.22 13.5,8.9 12,8.91C10.47,8.91 9,9.24 7.73,9.82L6.04,6.91C5.95,6.74 5.74,6.68 5.57,6.78C5.4,6.87 5.35,7.08 5.44,7.25L7.1,10.13C4.25,11.69 2.29,14.58 2,18H22C21.72,14.59 19.77,11.7 16.91,10.14H16.91Z"/> | ||||
| </vector> | ||||
							
								
								
									
										13
									
								
								winboll-shared/src/main/res/drawable/ic_winboll.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								winboll-shared/src/main/res/drawable/ic_winboll.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <layer-list xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:clickable="true"> | ||||
|     <item | ||||
|         android:width="256dp" | ||||
|         android:height="256dp" | ||||
|         android:left="0dp"  | ||||
|         android:top="0dp"  | ||||
|         android:right="0dp"  | ||||
|         android:bottom="0dp" | ||||
|         android:drawable="@drawable/winboll_logo"> | ||||
|     </item> | ||||
| </layer-list> | ||||
							
								
								
									
										11
									
								
								winboll-shared/src/main/res/drawable/ic_winbollbeta.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								winboll-shared/src/main/res/drawable/ic_winbollbeta.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <layer-list xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:clickable="true"> | ||||
|     <item android:drawable="@drawable/ic_launcher_background"/>  | ||||
|     <item  | ||||
|         android:left="0dp"  | ||||
|         android:top="0dp"  | ||||
|         android:right="0dp"  | ||||
|         android:bottom="0dp" | ||||
|         android:drawable="@drawable/winboll_logo"/> | ||||
| </layer-list> | ||||
							
								
								
									
										10
									
								
								winboll-shared/src/main/res/drawable/shape_gradient.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								winboll-shared/src/main/res/drawable/shape_gradient.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <shape xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
|     <gradient | ||||
|         android:angle="180" | ||||
|         android:endColor="#FFFFFFFF" | ||||
|         android:startColor="#FFFFFFFF" | ||||
|         android:type="linear" /> | ||||
|  | ||||
|     <corners android:radius="10dp" /> | ||||
| </shape> | ||||
							
								
								
									
										8
									
								
								winboll-shared/src/main/res/drawable/view_border.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								winboll-shared/src/main/res/drawable/view_border.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <shape xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:shape="rectangle"> | ||||
|     <stroke | ||||
|         android:width="1dp" | ||||
|         android:color="#000000" /> <!-- 这里可调整边框宽度和颜色 --> | ||||
|     <solid android:color="@android:color/transparent" /> | ||||
| </shape> | ||||
							
								
								
									
										27
									
								
								winboll-shared/src/main/res/drawable/winboll_help.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								winboll-shared/src/main/res/drawable/winboll_help.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:width="512dp" | ||||
|     android:height="512dp" | ||||
|     android:viewportWidth="512" | ||||
|     android:viewportHeight="512"> | ||||
|     <path | ||||
|         android:fillColor="#FF1E9B54" | ||||
|         android:strokeColor="#FFF8E733" | ||||
|         android:strokeWidth="20.0" | ||||
|         android:strokeLineCap="round" | ||||
|         android:strokeMiterLimit="10" | ||||
|         android:pathData="M254.63 35.45C374.95 35.45 473.38 133.89 473.38 254.2 473.38 374.51 374.95 472.95 254.63 472.95 134.32 472.95 35.88 374.51 35.88 254.2 35.88 133.89 134.32 35.45 254.63 35.45"/> | ||||
|     <path | ||||
|         android:fillColor="#FF000000" | ||||
|         android:strokeColor="#FF000000" | ||||
|         android:strokeWidth="1.0" | ||||
|         android:strokeLineCap="round" | ||||
|         android:strokeMiterLimit="10" | ||||
|         android:pathData="M257.28 361.25C266.56 361.25 274.14 368.84 274.14 378.11 274.14 387.39 266.56 394.98 257.28 394.98 248.01 394.98 240.42 387.39 240.42 378.11 240.42 368.84 248.01 361.25 257.28 361.25"/> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:strokeColor="#FF000000" | ||||
|         android:strokeWidth="30.0" | ||||
|         android:strokeLineCap="round" | ||||
|         android:strokeMiterLimit="10" | ||||
|         android:pathData="M182.16 214.09C181.42 199.71 182.42 177.87 207.64 155.49 213.64 150.16 220.13 146.12 226.28 143.08 238.64 136.97 249.62 134.91 252.55 134.56 252.7 134.54 252.83 134.53 252.94 134.52 253.05 134.51 253.14 134.5 253.2 134.5 255.01 134.48 294.9 136.66 313.05 160.43 332.29 185.63 344.82 221.3 300.07 263.56 263.08 298.49 258.36 318 258.54 317.72"/> | ||||
| </vector> | ||||
							
								
								
									
										48
									
								
								winboll-shared/src/main/res/drawable/winboll_logo.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								winboll-shared/src/main/res/drawable/winboll_logo.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:width="512dp" | ||||
|     android:height="512dp" | ||||
|     android:viewportWidth="512" | ||||
|     android:viewportHeight="512"> | ||||
|     <path | ||||
|         android:fillColor="#FF1E9B54" | ||||
|         android:strokeColor="#FFF8E733" | ||||
|         android:strokeWidth="20.0" | ||||
|         android:strokeLineCap="round" | ||||
|         android:strokeMiterLimit="10" | ||||
|         android:pathData="M254.63 35.45C374.95 35.45 473.38 133.89 473.38 254.2 473.38 374.51 374.95 472.95 254.63 472.95 134.32 472.95 35.88 374.51 35.88 254.2 35.88 133.89 134.32 35.45 254.63 35.45"/> | ||||
|     <path | ||||
|         android:fillColor="#FFFFFFFF" | ||||
|         android:strokeColor="#FFFFFFFF" | ||||
|         android:strokeWidth="1.0" | ||||
|         android:strokeLineCap="round" | ||||
|         android:strokeMiterLimit="10" | ||||
|         android:pathData="M257.28 361.25C266.56 361.25 274.14 368.84 274.14 378.11 274.14 387.39 266.56 394.98 257.28 394.98 248.01 394.98 240.42 387.39 240.42 378.11 240.42 368.84 248.01 361.25 257.28 361.25"/> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:strokeColor="#FF000000" | ||||
|         android:strokeWidth="30.0" | ||||
|         android:strokeLineCap="round" | ||||
|         android:strokeMiterLimit="10" | ||||
|         android:pathData="M182.16 214.09C181.42 199.71 182.42 177.87 207.64 155.49 213.64 150.16 220.13 146.12 226.28 143.08 238.64 136.97 249.62 134.91 252.55 134.56 252.7 134.54 252.83 134.53 252.94 134.52 253.05 134.51 253.14 134.5 253.2 134.5 255.01 134.48 294.9 136.66 313.05 160.43 332.29 185.63 344.82 221.3 300.07 263.56 263.08 298.49 258.36 318 258.54 317.72"/> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:strokeColor="#FFFFFFFF" | ||||
|         android:strokeWidth="30.0" | ||||
|         android:strokeLineCap="round" | ||||
|         android:strokeMiterLimit="10" | ||||
|         android:pathData="M103.77 307.45C103.02 293.07 104.03 271.24 129.24 248.85 135.25 243.52 141.74 239.48 147.89 236.44 160.24 230.34 171.23 228.28 174.15 227.92 174.31 227.9 174.44 227.89 174.55 227.88 174.66 227.87 174.75 227.86 174.81 227.86 176.62 227.85 216.5 230.02 234.65 253.79 253.9 278.99 266.43 314.66 221.67 356.93 184.69 391.85 179.97 411.36 180.15 411.08"/> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:strokeColor="#FFFFFFFF" | ||||
|         android:strokeWidth="30.0" | ||||
|         android:strokeLineCap="round" | ||||
|         android:strokeMiterLimit="10" | ||||
|         android:pathData="M248.17 309.83C247.43 295.45 248.43 273.62 273.64 251.23 279.65 245.9 286.14 241.86 292.29 238.82 304.65 232.72 315.63 230.65 318.55 230.3 318.71 230.28 318.84 230.27 318.95 230.26 319.06 230.25 319.15 230.24 319.21 230.24 321.02 230.22 360.9 232.4 379.06 256.17 398.3 281.37 410.83 317.04 366.08 359.31 329.09 394.23 324.37 413.74 324.55 413.46"/> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:strokeColor="#FFFFFFFF" | ||||
|         android:strokeWidth="30.0" | ||||
|         android:strokeLineCap="round" | ||||
|         android:strokeMiterLimit="10" | ||||
|         android:pathData="M182.16 214.09C181.42 199.71 182.42 177.87 207.64 155.49 213.64 150.16 220.13 146.12 226.28 143.08 238.64 136.97 249.62 134.91 252.55 134.56 252.7 134.54 252.83 134.53 252.94 134.52 253.05 134.51 253.14 134.5 253.2 134.5 255.01 134.48 294.9 136.66 313.05 160.43 332.29 185.63 344.82 221.3 300.07 263.56 263.08 298.49 258.36 318 258.54 317.72"/> | ||||
| </vector> | ||||
							
								
								
									
										25
									
								
								winboll-shared/src/main/res/layout/activity_about.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								winboll-shared/src/main/res/layout/activity_about.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| <?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"> | ||||
|  | ||||
|     <androidx.appcompat.widget.Toolbar | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:id="@+id/activityaboutToolbar1"/> | ||||
|  | ||||
|     <cc.winboll.studio.shared.view.AboutView | ||||
|         app:app_name= "APP" | ||||
|         app:app_apkfoldername="APP" | ||||
|         app:app_apkname="APP" | ||||
|         app:app_gitname="APP" | ||||
|         app:appdescription="WinBoll.CC 网站客户端。" | ||||
|         app:appicon="@drawable/ic_winboll" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="match_parent" | ||||
|         android:id="@+id/activityaboutAboutView1"/> | ||||
|  | ||||
| </LinearLayout> | ||||
							
								
								
									
										21
									
								
								winboll-shared/src/main/res/layout/activity_help.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								winboll-shared/src/main/res/layout/activity_help.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| <?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"> | ||||
|  | ||||
| 	<androidx.appcompat.widget.Toolbar | ||||
| 		android:layout_width="match_parent" | ||||
| 		android:layout_height="wrap_content" | ||||
| 		android:id="@+id/activityhelpToolbar1"/> | ||||
|  | ||||
| 	<cc.winboll.studio.shared.view.SimpleWebView | ||||
| 		android:layout_width="match_parent" | ||||
| 		android:layout_height="0dp" | ||||
| 		android:layout_weight="1.0" | ||||
| 		android:id="@+id/activityhelpSimpleWebView1"/> | ||||
|  | ||||
| </LinearLayout> | ||||
|  | ||||
							
								
								
									
										11
									
								
								winboll-shared/src/main/res/layout/activity_library.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								winboll-shared/src/main/res/layout/activity_library.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="match_parent" | ||||
|     android:gravity="center"> | ||||
|  | ||||
|     <TextView | ||||
|         android:text="@string/hello_world" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" /> | ||||
|  | ||||
| </LinearLayout> | ||||
							
								
								
									
										17
									
								
								winboll-shared/src/main/res/layout/activity_log.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								winboll-shared/src/main/res/layout/activity_log.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <LinearLayout | ||||
| 	xmlns:android="http://schemas.android.com/apk/res/android" | ||||
| 	xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
| 	android:orientation="vertical" | ||||
| 	android:layout_width="match_parent" | ||||
| 	android:layout_height="match_parent"> | ||||
|  | ||||
|     <cc.winboll.studio.shared.log.LogView | ||||
|         android:orientation="horizontal" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="0dp" | ||||
|         android:layout_weight="1.0" | ||||
| 		android:id="@+id/logview"/> | ||||
|      | ||||
| </LinearLayout> | ||||
|  | ||||
							
								
								
									
										31
									
								
								winboll-shared/src/main/res/layout/activity_unittest.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								winboll-shared/src/main/res/layout/activity_unittest.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| <?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:id="@+id/activityunittestLinearLayout1"> | ||||
|  | ||||
| 	<TextView | ||||
| 		android:layout_width="wrap_content" | ||||
| 		android:layout_height="wrap_content" | ||||
| 		android:text="UnitTest"/> | ||||
|  | ||||
| 	<ScrollView | ||||
| 		android:layout_width="match_parent" | ||||
| 		android:layout_height="0dp" | ||||
| 		android:layout_weight="1.0"> | ||||
|  | ||||
| 		<LinearLayout | ||||
| 			android:orientation="vertical" | ||||
| 			android:layout_width="match_parent" | ||||
| 			android:layout_height="wrap_content" | ||||
| 			android:id="@+id/activityunittestLinearLayout2"> | ||||
|  | ||||
| 		</LinearLayout> | ||||
|  | ||||
| 	</ScrollView> | ||||
|  | ||||
| </LinearLayout> | ||||
|  | ||||
							
								
								
									
										64
									
								
								winboll-shared/src/main/res/layout/view_about_dev.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								winboll-shared/src/main/res/layout/view_about_dev.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <LinearLayout | ||||
| 	xmlns:android="http://schemas.android.com/apk/res/android" | ||||
| 	android:orientation="vertical" | ||||
| 	android:layout_width="match_parent" | ||||
| 	android:layout_height="match_parent" | ||||
| 	android:id="@+id/viewaboutdevLinearLayout1"> | ||||
|  | ||||
| 	<LinearLayout | ||||
| 		android:orientation="horizontal" | ||||
| 		android:layout_width="match_parent" | ||||
| 		android:layout_height="wrap_content" | ||||
| 		android:gravity="center_vertical"> | ||||
|  | ||||
| 		<TextView | ||||
| 			android:layout_width="180dp" | ||||
| 			android:layout_height="wrap_content" | ||||
| 			android:text="DevelopUserName :"/> | ||||
|  | ||||
| 		<EditText | ||||
| 			android:layout_width="0dp" | ||||
| 			android:ems="10" | ||||
| 			android:layout_height="wrap_content" | ||||
| 			android:layout_weight="1.0" | ||||
| 			android:id="@+id/viewaboutdevEditText1"/> | ||||
|  | ||||
| 	</LinearLayout> | ||||
|  | ||||
| 	<LinearLayout | ||||
| 		android:orientation="horizontal" | ||||
| 		android:layout_width="match_parent" | ||||
| 		android:layout_height="wrap_content" | ||||
| 		android:gravity="center_vertical"> | ||||
|  | ||||
| 		<TextView | ||||
| 			android:layout_width="180dp" | ||||
| 			android:layout_height="wrap_content" | ||||
| 			android:text="DevelopUserPassword :"/> | ||||
|  | ||||
| 		<EditText | ||||
| 			android:layout_width="0dp" | ||||
| 			android:inputType="textPassword" | ||||
| 			android:layout_height="wrap_content" | ||||
| 			android:ems="10" | ||||
| 			android:layout_weight="1.0" | ||||
| 			android:id="@+id/viewaboutdevEditText2"/> | ||||
|  | ||||
| 	</LinearLayout> | ||||
|  | ||||
| 	<LinearLayout | ||||
| 		android:orientation="horizontal" | ||||
| 		android:layout_width="match_parent" | ||||
| 		android:layout_height="wrap_content" | ||||
| 		android:gravity="center_horizontal"> | ||||
|  | ||||
| 		<cc.winboll.studio.shared.view.WinBollServiceStatusView | ||||
| 			android:layout_width="wrap_content" | ||||
| 			android:layout_height="wrap_content" | ||||
| 			android:id="@+id/viewaboutdevWinBollServiceStatusView1"/> | ||||
|  | ||||
| 	</LinearLayout> | ||||
|  | ||||
| </LinearLayout> | ||||
|  | ||||
							
								
								
									
										23
									
								
								winboll-shared/src/main/res/layout/view_about_www.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								winboll-shared/src/main/res/layout/view_about_www.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <LinearLayout | ||||
| 	xmlns:android="http://schemas.android.com/apk/res/android" | ||||
| 	android:orientation="vertical" | ||||
| 	android:layout_width="match_parent" | ||||
| 	android:layout_height="match_parent" | ||||
| 	android:id="@+id/viewaboutwwwLinearLayout1"> | ||||
|  | ||||
| 	<LinearLayout | ||||
| 		android:orientation="horizontal" | ||||
| 		android:layout_width="match_parent" | ||||
| 		android:layout_height="wrap_content" | ||||
| 		android:gravity="center_horizontal"> | ||||
|  | ||||
| 		<cc.winboll.studio.shared.view.WinBollServiceStatusView | ||||
| 			android:layout_width="wrap_content" | ||||
| 			android:layout_height="wrap_content" | ||||
| 			android:id="@+id/viewaboutwwwWinBollServiceStatusView1"/> | ||||
|  | ||||
| 	</LinearLayout> | ||||
|  | ||||
| </LinearLayout> | ||||
|  | ||||
							
								
								
									
										14
									
								
								winboll-shared/src/main/res/layout/view_ads.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								winboll-shared/src/main/res/layout/view_ads.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <RelativeLayout | ||||
| 	xmlns:android="http://schemas.android.com/apk/res/android" | ||||
| 	android:orientation="vertical" | ||||
| 	android:layout_width="match_parent" | ||||
| 	android:layout_height="match_parent"> | ||||
|  | ||||
| 	<android.webkit.WebView | ||||
| 		android:layout_width="match_parent" | ||||
| 		android:layout_height="match_parent" | ||||
| 		android:id="@+id/viewadsWebView1"/> | ||||
|  | ||||
| </RelativeLayout> | ||||
|  | ||||
							
								
								
									
										121
									
								
								winboll-shared/src/main/res/layout/view_log.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								winboll-shared/src/main/res/layout/view_log.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,121 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <RelativeLayout | ||||
| 	xmlns:android="http://schemas.android.com/apk/res/android" | ||||
| 	android:orientation="vertical" | ||||
| 	android:layout_width="match_parent" | ||||
| 	android:layout_height="match_parent" | ||||
| 	android:background="#FF000000"> | ||||
|  | ||||
| 	<RelativeLayout | ||||
| 		xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
| 		android:orientation="horizontal" | ||||
| 		android:layout_width="match_parent" | ||||
| 		android:layout_height="40dp" | ||||
| 		android:layout_alignParentTop="true" | ||||
| 		android:background="@drawable/bg_shadow" | ||||
| 		android:id="@+id/viewlogRelativeLayoutToolbar"> | ||||
|  | ||||
| 		<Button | ||||
| 			android:layout_width="wrap_content" | ||||
| 			android:layout_height="36dp" | ||||
| 			android:text="Clean" | ||||
| 			android:textSize="14dp" | ||||
| 			android:layout_centerVertical="true" | ||||
| 			android:id="@+id/viewlogButtonClean" | ||||
| 			android:layout_marginLeft="5dp"/> | ||||
|  | ||||
| 		<TextView | ||||
| 			android:background="#FF000000" | ||||
| 			android:layout_width="wrap_content" | ||||
| 			android:layout_height="24dp" | ||||
| 			android:text="LV:" | ||||
| 			android:layout_toRightOf="@+id/viewlogButtonClean" | ||||
| 			android:layout_centerVertical="true" | ||||
| 			android:id="@+id/viewlogTextView1" | ||||
| 			android:textColor="#FFFFFFFF"/> | ||||
|  | ||||
| 		<Spinner | ||||
| 			android:background="#FFFFFFFF" | ||||
| 			android:layout_width="wrap_content" | ||||
| 			android:layout_height="24dp" | ||||
| 			android:layout_toRightOf="@+id/viewlogTextView1" | ||||
| 			android:layout_centerVertical="true" | ||||
| 			android:id="@+id/viewlogSpinner1"/> | ||||
|  | ||||
| 		<CheckBox | ||||
| 			android:layout_width="wrap_content" | ||||
| 			android:layout_height="24dp" | ||||
| 			android:layout_toLeftOf="@+id/viewlogButtonCopy" | ||||
| 			android:layout_centerVertical="true" | ||||
| 			android:text="Selectable" | ||||
| 			android:background="#FFFFFFFF" | ||||
| 			android:paddingRight="10dp" | ||||
| 			android:textSize="16dp" | ||||
| 			android:id="@+id/viewlogCheckBoxSelectable"/> | ||||
|  | ||||
| 		<Button | ||||
| 			android:layout_width="wrap_content" | ||||
| 			android:layout_height="36dp" | ||||
| 			android:text="Copy" | ||||
| 			android:layout_alignParentRight="true" | ||||
| 			android:textSize="14dp" | ||||
| 			android:layout_centerVertical="true" | ||||
| 			android:id="@+id/viewlogButtonCopy" | ||||
| 			android:layout_marginRight="5dp"/> | ||||
|  | ||||
| 	</RelativeLayout> | ||||
|  | ||||
| 	<LinearLayout | ||||
| 		android:orientation="horizontal" | ||||
| 		android:layout_width="match_parent" | ||||
| 		android:layout_height="40dp" | ||||
| 		android:background="@drawable/bg_shadow" | ||||
| 		android:layout_below="@+id/viewlogRelativeLayoutToolbar" | ||||
| 		android:id="@+id/viewlogLinearLayout1" | ||||
| 		android:gravity="center_vertical"> | ||||
|  | ||||
| 		<CheckBox | ||||
| 			android:layout_width="wrap_content" | ||||
| 			android:layout_height="wrap_content" | ||||
| 			android:text="ALL" | ||||
| 			android:id="@+id/viewlogCheckBox1"/> | ||||
|  | ||||
| 		<androidx.recyclerview.widget.RecyclerView | ||||
| 			android:layout_width="0dp" | ||||
| 			android:layout_height="wrap_content" | ||||
| 			android:background="@drawable/view_border" | ||||
| 			android:id="@+id/viewlogRecyclerView1" | ||||
| 			android:layout_weight="1.0" | ||||
| 			android:layout_marginRight="5dp" | ||||
| 			android:padding="2dp"/> | ||||
|  | ||||
| 	</LinearLayout> | ||||
|  | ||||
| 	<RelativeLayout | ||||
| 		android:orientation="vertical" | ||||
| 		android:layout_width="match_parent" | ||||
| 		android:layout_height="wrap_content" | ||||
| 		android:layout_weight="1.0" | ||||
| 		android:layout_alignParentBottom="true" | ||||
| 		android:layout_below="@+id/viewlogLinearLayout1"> | ||||
|  | ||||
| 		<ScrollView | ||||
| 			android:layout_width="match_parent" | ||||
| 			android:layout_height="match_parent" | ||||
| 			android:background="#FF000000" | ||||
| 			android:id="@+id/viewlogScrollViewLog"> | ||||
|  | ||||
| 			<TextView | ||||
| 				android:layout_width="match_parent" | ||||
| 				android:layout_height="match_parent" | ||||
| 				android:text="Text" | ||||
| 				android:textColor="#FF00FF00" | ||||
| 				android:textIsSelectable="true" | ||||
| 				android:id="@+id/viewlogTextViewLog"/> | ||||
|  | ||||
| 		</ScrollView> | ||||
|  | ||||
| 	</RelativeLayout> | ||||
|  | ||||
| </RelativeLayout> | ||||
|  | ||||
							
								
								
									
										32
									
								
								winboll-shared/src/main/res/layout/view_logtag.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								winboll-shared/src/main/res/layout/view_logtag.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <androidx.cardview.widget.CardView | ||||
| 	xmlns:android="http://schemas.android.com/apk/res/android" | ||||
| 	xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
| 	android:layout_width="wrap_content" | ||||
| 	android:layout_height="wrap_content" | ||||
| 	app:cardBackgroundColor="#F5F5F5" | ||||
| 	app:cardElevation="4dp" | ||||
| 	app:cardCornerRadius="4dp" | ||||
| 	android:id="@+id/listviewauthinfoCardView1" | ||||
| 	android:layout_marginLeft="10dp" | ||||
| 	android:layout_marginRight="10dp"> | ||||
|  | ||||
| 	<LinearLayout | ||||
| 		android:orientation="horizontal" | ||||
| 		android:layout_width="wrap_content" | ||||
| 		android:layout_height="wrap_content"> | ||||
|  | ||||
| 		<TextView | ||||
| 			android:layout_width="wrap_content" | ||||
| 			android:layout_height="24dp" | ||||
| 			android:id="@+id/viewlogtagTextView1"/> | ||||
|  | ||||
|         <CheckBox | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="24dp" | ||||
|             android:id="@+id/viewlogtagCheckBox1"/> | ||||
|  | ||||
| 	</LinearLayout> | ||||
|  | ||||
| </androidx.cardview.widget.CardView> | ||||
|  | ||||
							
								
								
									
										29
									
								
								winboll-shared/src/main/res/layout/view_string2qrcode.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								winboll-shared/src/main/res/layout/view_string2qrcode.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| <?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="wrap_content"> | ||||
|  | ||||
| 	<TextView | ||||
| 		android:layout_width="match_parent" | ||||
| 		android:layout_height="wrap_content" | ||||
| 		android:text="QRCode UnitTest"/> | ||||
|  | ||||
| 	<TextView | ||||
| 		android:layout_width="match_parent" | ||||
| 		android:layout_height="wrap_content" | ||||
| 		android:text="MessageText" | ||||
| 		android:textColor="#FFFF0000" | ||||
| 		android:id="@+id/viewstring2qrcodeTextView1"/> | ||||
|  | ||||
| 	<ImageView | ||||
| 		android:orientation="vertical" | ||||
| 		android:layout_width="200dp" | ||||
| 		android:layout_height="200dp" | ||||
| 		android:background="#FF099EF2" | ||||
| 		android:id="@+id/viewstring2qrcodeImageView1"/> | ||||
|  | ||||
| </LinearLayout> | ||||
|  | ||||
							
								
								
									
										32
									
								
								winboll-shared/src/main/res/layout/view_toast.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								winboll-shared/src/main/res/layout/view_toast.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| <?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="wrap_content" | ||||
|     android:layout_height="wrap_content" | ||||
|     android:background="@drawable/shape_gradient" | ||||
|     android:gravity="center" | ||||
|     android:orientation="vertical" | ||||
|     android:padding="10dp"> | ||||
|      | ||||
|     <LinearLayout | ||||
|         android:orientation="horizontal" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content"> | ||||
|  | ||||
|         <ImageView | ||||
|             android:layout_width="40dp" | ||||
|             android:layout_height="40dp" | ||||
|             android:src="@drawable/ic_launcher"/> | ||||
|  | ||||
|         <TextView | ||||
|             android:id="@android:id/message" | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_margin="10dp" | ||||
|             android:textColor="#FF000000" | ||||
|             android:textSize="16sp"/> | ||||
|  | ||||
|     </LinearLayout> | ||||
|  | ||||
| </LinearLayout> | ||||
							
								
								
									
										33
									
								
								winboll-shared/src/main/res/menu/toolbar_studio_debug.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								winboll-shared/src/main/res/menu/toolbar_studio_debug.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <menu xmlns:android="http://schemas.android.com/apk/res/android" | ||||
| 	xmlns:app="http://schemas.android.com/apk/res-auto"> | ||||
|     <!--<item android:title="@string/app_theme"> | ||||
|     <menu>--> | ||||
|     <!-- 定义一组单选按钮 --> | ||||
|     <!-- checkableBehavior的可选值由三个:single设置为单选,all为多选,none为普通选项 --> | ||||
|     <!--        <group android:checkableBehavior="single"> | ||||
|     <item android:id="@+id/app_defaulttheme" android:title="@string/app_defaulttheme"/> | ||||
|     <item android:id="@+id/app_skytheme" android:title="@string/app_skytheme"/> | ||||
|     <item android:id="@+id/app_goldentheme" android:title="@string/app_goldentheme"/> | ||||
|     </group> | ||||
|     </menu> | ||||
|     </item>--> | ||||
|      | ||||
|     <item android:title="DebugTools"> | ||||
|         <menu> | ||||
|             <item | ||||
|                 android:id="@+id/item_unittest" | ||||
|                 android:title="UnitTest"/> | ||||
|             <item | ||||
|                 android:id="@+id/item_log" | ||||
|                 android:title="APPLOG" | ||||
|                 app:showAsAction="always"/> | ||||
|             <item | ||||
|                 android:id="@+id/item_info" | ||||
|                 android:title="Info"/> | ||||
|             <item | ||||
|                 android:id="@+id/item_exitdebug" | ||||
|                 android:title="ExitDebug"/> | ||||
|         </menu> | ||||
|     </item> | ||||
| </menu> | ||||
| @@ -0,0 +1,8 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <menu xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:app="http://schemas.android.com/apk/res-auto"> | ||||
|     <item | ||||
|         android:id="@+id/item_help" | ||||
|         android:title="HELP" | ||||
|         app:showAsAction="always"/> | ||||
| </menu> | ||||
| @@ -0,0 +1,12 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <menu xmlns:android="http://schemas.android.com/apk/res/android" | ||||
| 	xmlns:app="http://schemas.android.com/apk/res-auto"> | ||||
|     <item | ||||
|         android:id="@+id/item_exit" | ||||
|         android:title="EXIT" | ||||
|         app:showAsAction="always"/> | ||||
|     <item | ||||
|         android:id="@+id/item_about" | ||||
|         android:title="About" | ||||
|         app:showAsAction="always"/> | ||||
| </menu> | ||||
							
								
								
									
										5
									
								
								winboll-shared/src/main/res/values-v21/styles.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								winboll-shared/src/main/res/values-v21/styles.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <style name="AppTheme" parent="@android:style/Theme.Material.Light"> | ||||
| 	</style> | ||||
| </resources> | ||||
							
								
								
									
										11
									
								
								winboll-shared/src/main/res/values/array.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								winboll-shared/src/main/res/values/array.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <array name="enum_loglevel_array"> | ||||
|         <item>Off</item> | ||||
|         <item>Error</item> | ||||
|         <item>Warn</item> | ||||
|         <item>Info</item> | ||||
|         <item>Debug</item> | ||||
|         <item>Verbose</item> | ||||
|     </array> | ||||
| </resources> | ||||
							
								
								
									
										11
									
								
								winboll-shared/src/main/res/values/attrs.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								winboll-shared/src/main/res/values/attrs.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <declare-styleable name="AboutView"> | ||||
|         <attr name="app_name" format="string" /> | ||||
|         <attr name="app_apkfoldername" format="string" /> | ||||
|         <attr name="app_apkname" format="string" /> | ||||
|         <attr name="app_gitname" format="string" /> | ||||
|         <attr name="appdescription" format="string" /> | ||||
|         <attr name="appicon" format="reference" /> | ||||
|     </declare-styleable> | ||||
| </resources> | ||||
							
								
								
									
										10
									
								
								winboll-shared/src/main/res/values/colors.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								winboll-shared/src/main/res/values/colors.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <!-- WinBoll 默认方案 --> | ||||
|     <color name="colorPrimary">#FF196ABC</color> | ||||
|     <color name="colorPrimaryDark">#FF002B57</color> | ||||
|     <color name="colorAccent">#FF80BFFF</color> | ||||
|     <color name="colorToastFrame">#FFA9A9A9</color> | ||||
|     <color name="colorToastShadow">#FF000000</color> | ||||
|     <color name="colorToastBackgroung">#FFFFFFFF</color> | ||||
| </resources> | ||||
							
								
								
									
										11
									
								
								winboll-shared/src/main/res/values/strings.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								winboll-shared/src/main/res/values/strings.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|  | ||||
|     <string name="lib_name">library</string> | ||||
|     <string name="app_normal">Click here is switch to Normal APP</string> | ||||
|     <string name="app_debug">Click here is switch to APP DEBUG</string> | ||||
|     <string name="gitea_home">GITEA HOME</string> | ||||
|     <string name="app_update">APP UPDATE</string> | ||||
|     <string name="hello_world">Hello world!</string> | ||||
|  | ||||
| </resources> | ||||
							
								
								
									
										17
									
								
								winboll-shared/src/main/res/values/styles.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								winboll-shared/src/main/res/values/styles.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <style name="AppTheme" parent="WinBoll.SupportThemeNoActionBar"/> | ||||
|          | ||||
|     <style name="WinBoll.Theme" parent="@android:style/Theme.DeviceDefault.Light"> | ||||
|         <!-- Customize your theme here. --> | ||||
|     </style> | ||||
|     <style name="WinBoll.SupportTheme" parent="Theme.AppCompat.Light"> | ||||
|         <!-- Customize your theme here. --> | ||||
|     </style> | ||||
|     <style name="WinBoll.ThemeNoActionBar" parent="@android:style/Theme.DeviceDefault.Light.NoActionBar"> | ||||
|         <!-- Customize your theme here. --> | ||||
|     </style> | ||||
|     <style name="WinBoll.SupportThemeNoActionBar" parent="Theme.AppCompat.Light.NoActionBar"> | ||||
|         <!-- Customize your theme here. --> | ||||
|     </style> | ||||
| </resources> | ||||
| @@ -0,0 +1,6 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <network-security-config> | ||||
|     <domain-config cleartextTrafficPermitted="true"> | ||||
|         <domain includeSubdomains="true">10.8.0.13</domain> | ||||
|     </domain-config> | ||||
| </network-security-config> | ||||
		Reference in New Issue
	
	Block a user
	 ZhanGSKen
					ZhanGSKen