This commit is contained in:
		
							
								
								
									
										3
									
								
								.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| # Default ignored files | ||||
| /shelf/ | ||||
| /workspace.xml | ||||
							
								
								
									
										1
									
								
								.idea/.name
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.idea/.name
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| appbase | ||||
							
								
								
									
										6
									
								
								.idea/compiler.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.idea/compiler.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="CompilerConfiguration"> | ||||
|     <bytecodeTargetLevel target="17" /> | ||||
|   </component> | ||||
| </project> | ||||
							
								
								
									
										10
									
								
								.idea/deploymentTargetDropDown.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								.idea/deploymentTargetDropDown.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="deploymentTargetDropDown"> | ||||
|     <value> | ||||
|       <entry key="appbase"> | ||||
|         <State /> | ||||
|       </entry> | ||||
|     </value> | ||||
|   </component> | ||||
| </project> | ||||
							
								
								
									
										10
									
								
								.idea/migrations.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								.idea/migrations.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="ProjectMigrations"> | ||||
|     <option name="MigrateToGradleLocalJavaHome"> | ||||
|       <set> | ||||
|         <option value="$PROJECT_DIR$" /> | ||||
|       </set> | ||||
|     </option> | ||||
|   </component> | ||||
| </project> | ||||
							
								
								
									
										10
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="ExternalStorageConfigurationManager" enabled="true" /> | ||||
|   <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK"> | ||||
|     <output url="file://$PROJECT_DIR$/build/classes" /> | ||||
|   </component> | ||||
|   <component name="ProjectType"> | ||||
|     <option name="id" value="Android" /> | ||||
|   </component> | ||||
| </project> | ||||
| @@ -18,18 +18,18 @@ def genVersionName(def versionName){ | ||||
| } | ||||
|  | ||||
| android { | ||||
|     compileSdkVersion 32 | ||||
|     buildToolsVersion "33.0.3" | ||||
|     compileSdkVersion 30 | ||||
|     buildToolsVersion "30.0.3" | ||||
|  | ||||
|     defaultConfig { | ||||
|         applicationId "cc.winboll.studio.appbase" | ||||
|         minSdkVersion 21 | ||||
|         targetSdkVersion 30 | ||||
|         minSdkVersion 26 | ||||
|         targetSdkVersion 29 | ||||
|         versionCode 1 | ||||
|         // versionName 更新后需要手动设置  | ||||
|         // .winboll/winbollBuildProps.properties 文件的 stageCount=0 | ||||
|         // Gradle编译环境下合起来的 versionName 就是 "${versionName}.0" | ||||
|         versionName "1.0"  | ||||
|         versionName "2.0"  | ||||
|         if(true) { | ||||
|             versionName = genVersionName("${versionName}") | ||||
|         } | ||||
| @@ -41,11 +41,6 @@ android { | ||||
|             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     compileOptions { | ||||
|         sourceCompatibility JavaVersion.VERSION_11 | ||||
|         targetCompatibility JavaVersion.VERSION_11 | ||||
|     } | ||||
| } | ||||
|  | ||||
| dependencies { | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| #Created by .winboll/winboll_app_build.gradle | ||||
| #Sun Jan 05 02:30:11 GMT 2025 | ||||
| stageCount=0 | ||||
| libraryProject= | ||||
| baseVersion=1.0 | ||||
| publishVersion=1.0.0 | ||||
| buildCount=2 | ||||
| baseBetaVersion=1.0.1 | ||||
| #Tue Feb 25 16:51:17 HKT 2025 | ||||
| stageCount=3 | ||||
| libraryProject=libappbase | ||||
| baseVersion=2.0 | ||||
| publishVersion=2.0.2 | ||||
| buildCount=0 | ||||
| baseBetaVersion=2.0.3 | ||||
|   | ||||
| @@ -21,15 +21,76 @@ | ||||
|  | ||||
|                 <category android:name="android.intent.category.LAUNCHER"/> | ||||
|  | ||||
|                 <action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES"/> | ||||
|  | ||||
|                 <category android:name="android.intent.category.DEFAULT"/> | ||||
|  | ||||
|             </intent-filter> | ||||
|  | ||||
|         </activity> | ||||
|  | ||||
|         <activity android:name=".GlobalApplication$CrashActivity"/> | ||||
|  | ||||
|         <service | ||||
|             android:name=".MyTileService" | ||||
|             android:label="@string/tileservice_name" | ||||
|             android:icon="@drawable/ic_launcher" | ||||
|             android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"> | ||||
|  | ||||
|             <intent-filter> | ||||
|  | ||||
|                 <action android:name="android.service.quicksettings.action.QS_TILE"/> | ||||
|  | ||||
|             </intent-filter> | ||||
|  | ||||
|         </service> | ||||
|  | ||||
|         <service | ||||
|             android:name=".services.MainService" | ||||
|             android:exported="true"/> | ||||
|  | ||||
|         <service android:name=".services.AssistantService"/> | ||||
|  | ||||
|         <receiver android:name="cc.winboll.studio.appbase.receivers.MainReceiver"> | ||||
|  | ||||
|             <intent-filter> | ||||
|                 <action android:name="cc.winboll.studio.appbase.receivers.MainReceiver"/> | ||||
|             </intent-filter> | ||||
|  | ||||
|         </receiver> | ||||
|  | ||||
|         <receiver | ||||
|             android:name=".widgets.SOSWidget" | ||||
|             android:exported="true"> | ||||
|  | ||||
|             <intent-filter> | ||||
|                 <action android:name="cc.winboll.studio.appbase.widgets.SOSWidget.ACTION_WAKEUP_SERVICE"/> | ||||
|                 <action android:name="cc.winboll.studio.appbase.widgets.SOSWidget.ACTION_RELOAD_REPORT"/> | ||||
|                 <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/> | ||||
|             </intent-filter> | ||||
|  | ||||
|             <meta-data | ||||
|                 android:name="android.appwidget.provider" | ||||
|                 android:resource="@xml/widget_provider_info_sos"/> | ||||
|  | ||||
|         </receiver> | ||||
|  | ||||
|         <receiver android:name=".widgets.SOSWidgetClickListener"> | ||||
|  | ||||
|             <intent-filter> | ||||
|  | ||||
|                 <action android:name="cc.winboll.studio.appbase.widgets.SOSWidgetClickListener.ACTION_PRE"/> | ||||
|  | ||||
|                 <action android:name="cc.winboll.studio.appbase.widgets.SOSWidgetClickListener.ACTION_NEXT"/> | ||||
|  | ||||
|             </intent-filter> | ||||
|  | ||||
|         </receiver> | ||||
|  | ||||
|         <meta-data | ||||
|             android:name="android.max_aspect" | ||||
|             android:value="4.0"/> | ||||
|  | ||||
|         <activity android:name=".GlobalApplication$CrashActivity"/> | ||||
|  | ||||
|     </application> | ||||
|  | ||||
|   | ||||
| @@ -5,12 +5,23 @@ package cc.winboll.studio.appbase; | ||||
|  * @Date 2025/01/05 09:54:42 | ||||
|  * @Describe APPbase 应用类 | ||||
|  */ | ||||
|  import cc.winboll.studio.GlobalApplication; | ||||
| import cc.winboll.studio.libappbase.GlobalApplication; | ||||
| import cc.winboll.studio.libappbase.SOSCSBroadcastReceiver; | ||||
| import android.content.IntentFilter; | ||||
|  | ||||
| public class App extends GlobalApplication { | ||||
|      | ||||
|  | ||||
|     public static final String TAG = "App"; | ||||
|      | ||||
|     SOSCSBroadcastReceiver mSOSCSBroadcastReceiver; | ||||
|      | ||||
|      | ||||
|     @Override | ||||
|     public void onCreate() { | ||||
|         super.onCreate(); | ||||
|         GlobalApplication.setIsDebuging(this, BuildConfig.DEBUG); | ||||
|         mSOSCSBroadcastReceiver = new SOSCSBroadcastReceiver(); | ||||
|         IntentFilter intentFilter = new IntentFilter(); | ||||
|         intentFilter.addAction(SOSCSBroadcastReceiver.ACTION_SOS); | ||||
|         registerReceiver(mSOSCSBroadcastReceiver, intentFilter); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,15 +1,116 @@ | ||||
| package cc.winboll.studio.appbase; | ||||
|   | ||||
| import android.app.Activity; | ||||
| import android.os.Bundle; | ||||
|  | ||||
| public class MainActivity extends Activity { | ||||
|       | ||||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         setContentView(R.layout.activity_main); | ||||
|          | ||||
|     } | ||||
| 	 | ||||
| } | ||||
| package cc.winboll.studio.appbase; | ||||
|  | ||||
| import android.content.ComponentName; | ||||
| import android.content.Intent; | ||||
| import android.os.Bundle; | ||||
| import android.view.View; | ||||
| import android.widget.CheckBox; | ||||
| import androidx.appcompat.app.AppCompatActivity; | ||||
| import androidx.appcompat.widget.Toolbar; | ||||
| import cc.winboll.studio.appbase.R; | ||||
| import cc.winboll.studio.appbase.services.MainService; | ||||
| import cc.winboll.studio.libappbase.GlobalApplication; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| import cc.winboll.studio.libappbase.LogView; | ||||
| import cc.winboll.studio.libappbase.SOS; | ||||
| import cc.winboll.studio.libappbase.SimpleOperateSignalCenterService; | ||||
| import cc.winboll.studio.libappbase.bean.APPSOSBean; | ||||
| import cc.winboll.studio.libappbase.services.TestService; | ||||
| import cc.winboll.studio.libappbase.widgets.StatusWidget; | ||||
| import com.hjq.toast.ToastUtils; | ||||
|  | ||||
| public class MainActivity extends AppCompatActivity { | ||||
|  | ||||
|     public static final String TAG = "MainActivity"; | ||||
|  | ||||
|     LogView mLogView; | ||||
|  | ||||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         ToastUtils.show("onCreate"); | ||||
|         setContentView(R.layout.activity_main); | ||||
|  | ||||
|         Toolbar toolbar = findViewById(R.id.activitymainToolbar1); | ||||
|         setSupportActionBar(toolbar); | ||||
|  | ||||
|         CheckBox cbIsDebugMode = findViewById(R.id.activitymainCheckBox1); | ||||
|         cbIsDebugMode.setChecked(GlobalApplication.isDebuging()); | ||||
|         mLogView = findViewById(R.id.activitymainLogView1); | ||||
|  | ||||
|         if (GlobalApplication.isDebuging()) { mLogView.start(); } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onDestroy() { | ||||
|         super.onDestroy(); | ||||
|         Intent intentAPPWidget = new Intent(this, StatusWidget.class); | ||||
|         intentAPPWidget.setAction(StatusWidget.ACTION_STATUS_UPDATE); | ||||
|         sendBroadcast(intentAPPWidget); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onResume() { | ||||
|         LogUtils.d(TAG, "onResume"); | ||||
|         super.onResume(); | ||||
|         mLogView.start(); | ||||
|     } | ||||
|  | ||||
| 	public void onSwitchDebugMode(View view) { | ||||
|         GlobalApplication.setIsDebuging(this, ((CheckBox)view).isChecked()); | ||||
|     } | ||||
|  | ||||
|     public void onStartCenter(View view) { | ||||
|         MainService.startMainService(this); | ||||
|     } | ||||
|  | ||||
|     public void onStopCenter(View view) { | ||||
|         MainService.stopMainService(this); | ||||
|     } | ||||
|  | ||||
|     public void onTestStopWithoutSettingEnable(View view) { | ||||
|         LogUtils.d(TAG, "onTestStopWithoutSettingEnable"); | ||||
|         stopService(new Intent(this, SimpleOperateSignalCenterService.class)); | ||||
|     } | ||||
|  | ||||
|     public void onTestStartWithString(View view) { | ||||
|         LogUtils.d(TAG, "onTestStartWithString"); | ||||
|  | ||||
|         // 目标服务的包名和类名 | ||||
|         String packageName = this.getPackageName(); | ||||
|         String serviceClassName = SimpleOperateSignalCenterService.class.getName(); | ||||
|  | ||||
|         // 构建Intent | ||||
|         Intent intentService = new Intent(); | ||||
|         intentService.setComponent(new ComponentName(packageName, serviceClassName)); | ||||
|  | ||||
|         startService(intentService); | ||||
|     } | ||||
|  | ||||
|     public void onSOS(View view) { | ||||
|         Intent intent = new Intent(this, TestService.class); | ||||
|         stopService(intent); | ||||
|         SOS.sosWinBollService(this, new APPSOSBean(getPackageName(), TestService.class.getName())); | ||||
|     } | ||||
|  | ||||
|     public void onStartTestService(View view) { | ||||
|         Intent intent = new Intent(this, TestService.class); | ||||
|         intent.setAction(SOS.ACTION_SERVICE_ENABLE); | ||||
|         startService(intent); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     public void onStopTestService(View view) { | ||||
|         Intent intent = new Intent(this, TestService.class); | ||||
|         intent.setAction(SOS.ACTION_SERVICE_DISABLE); | ||||
|         startService(intent); | ||||
|          | ||||
|         Intent intentStop = new Intent(this, TestService.class); | ||||
|         stopService(intentStop); | ||||
|     } | ||||
|  | ||||
|     public void onStopTestServiceNoSettings(View view) { | ||||
|         Intent intent = new Intent(this, TestService.class); | ||||
|         stopService(intent); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,79 @@ | ||||
| package cc.winboll.studio.appbase; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@AliYun.Com | ||||
|  * @Date 2025/02/13 19:30:10 | ||||
|  */ | ||||
| import android.content.Context; | ||||
| import android.service.quicksettings.Tile; | ||||
| import android.service.quicksettings.TileService; | ||||
| import cc.winboll.studio.appbase.beans.MainServiceBean; | ||||
| import cc.winboll.studio.appbase.services.MainService; | ||||
|  | ||||
| public class MyTileService extends TileService { | ||||
|     public static final String TAG = "MyTileService"; | ||||
|  | ||||
|     volatile static MyTileService _MyTileService; | ||||
|  | ||||
|     @Override | ||||
|     public void onStartListening() { | ||||
|         super.onStartListening(); | ||||
|         _MyTileService = this; | ||||
|         Tile tile = getQsTile(); | ||||
|         MainServiceBean bean = MainServiceBean.loadBean(this, MainServiceBean.class); | ||||
|         if (bean != null && bean.isEnable()) { | ||||
|             //MainService.startMainService(context); | ||||
|             tile.setState(Tile.STATE_ACTIVE); | ||||
|             tile.setIcon(android.graphics.drawable.Icon.createWithResource(this, R.drawable.ic_cloud)); | ||||
|         } else { | ||||
|             //MainService.stopMainService(context); | ||||
|             tile.setState(Tile.STATE_INACTIVE); | ||||
|             tile.setIcon(android.graphics.drawable.Icon.createWithResource(this, R.drawable.ic_cloud_outline)); | ||||
|         } | ||||
|         tile.updateTile(); | ||||
| //        Tile tile = getQsTile(); | ||||
| //        tile.setState(Tile.STATE_INACTIVE); | ||||
| //        tile.setLabel(getString(R.string.tileservice_name)); | ||||
| //        tile.setIcon(android.graphics.drawable.Icon.createWithResource(this, R.drawable.ic_cloud_outline)); | ||||
| //        tile.updateTile(); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onClick() { | ||||
|         super.onClick(); | ||||
|         Tile tile = getQsTile(); | ||||
|         MainServiceBean bean = MainServiceBean.loadBean(this, MainServiceBean.class); | ||||
|         if (bean == null) { | ||||
|             bean = new MainServiceBean(); | ||||
|         } | ||||
|  | ||||
|         if (tile.getState() == Tile.STATE_ACTIVE) { | ||||
|             bean.setIsEnable(false); | ||||
|             MainServiceBean.saveBean(this, bean); | ||||
|             MainService.stopMainService(this); | ||||
|         } else if (tile.getState() == Tile.STATE_INACTIVE) { | ||||
|             bean.setIsEnable(true); | ||||
|             MainServiceBean.saveBean(this, bean); | ||||
|             MainService.startMainService(this); | ||||
|         } | ||||
|         updateServiceIconStatus(this); | ||||
|     } | ||||
|  | ||||
|     public static void updateServiceIconStatus(Context context) { | ||||
|         if (_MyTileService == null) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         Tile tile = _MyTileService.getQsTile(); | ||||
|         MainServiceBean bean = MainServiceBean.loadBean(context, MainServiceBean.class); | ||||
|         if (bean != null && bean.isEnable()) { | ||||
|             tile.setState(Tile.STATE_ACTIVE); | ||||
|             tile.setIcon(android.graphics.drawable.Icon.createWithResource(context, R.drawable.ic_cloud)); | ||||
|         } else { | ||||
|             tile.setState(Tile.STATE_INACTIVE); | ||||
|             tile.setIcon(android.graphics.drawable.Icon.createWithResource(context, R.drawable.ic_cloud_outline)); | ||||
|         } | ||||
|         tile.updateTile(); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,68 @@ | ||||
| package cc.winboll.studio.appbase.beans; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@AliYun.Com | ||||
|  * @Date 2025/02/13 07:06:13 | ||||
|  */ | ||||
| import android.util.JsonReader; | ||||
| import android.util.JsonWriter; | ||||
| import cc.winboll.studio.libappbase.BaseBean; | ||||
| import java.io.IOException; | ||||
|  | ||||
| public class MainServiceBean extends BaseBean { | ||||
|  | ||||
|     public static final String TAG = "MainServiceBean"; | ||||
|  | ||||
|     boolean isEnable; | ||||
|  | ||||
|     public MainServiceBean() { | ||||
|         this.isEnable = false; | ||||
|     } | ||||
|  | ||||
|     public void setIsEnable(boolean isEnable) { | ||||
|         this.isEnable = isEnable; | ||||
|     } | ||||
|  | ||||
|     public boolean isEnable() { | ||||
|         return isEnable; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getName() { | ||||
|         return MainServiceBean.class.getName(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { | ||||
|         super.writeThisToJsonWriter(jsonWriter); | ||||
|         MainServiceBean 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; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,72 @@ | ||||
| package cc.winboll.studio.appbase.beans; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@AliYun.Com | ||||
|  * @Date 2025/02/17 10:05:09 | ||||
|  * @Describe APPSOSReportBean | ||||
|  */ | ||||
| import android.util.JsonReader; | ||||
| import android.util.JsonWriter; | ||||
| import cc.winboll.studio.libappbase.BaseBean; | ||||
| import java.io.IOException; | ||||
|  | ||||
| public class SOSReportBean extends BaseBean { | ||||
|      | ||||
|     public static final String TAG = "APPSOSReportBean"; | ||||
|      | ||||
|     protected String sosReport; | ||||
|      | ||||
|     public SOSReportBean() { | ||||
|         this.sosReport = ""; | ||||
|     } | ||||
|  | ||||
|     public SOSReportBean(String sosReport) { | ||||
|         this.sosReport = sosReport; | ||||
|     } | ||||
|  | ||||
|     public void setSosReport(String sosReport) { | ||||
|         this.sosReport = sosReport; | ||||
|     } | ||||
|  | ||||
|     public String getSosReport() { | ||||
|         return sosReport; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getName() { | ||||
|         return SOSReportBean.class.getName(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { | ||||
|         super.writeThisToJsonWriter(jsonWriter); | ||||
|         jsonWriter.name("sosReport").value(getSosReport()); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException { | ||||
|         if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else { | ||||
|             if (name.equals("sosReport")) { | ||||
|                 setSosReport(jsonReader.nextString()); | ||||
|             } else { | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException { | ||||
|         jsonReader.beginObject(); | ||||
|         while (jsonReader.hasNext()) { | ||||
|             String name = jsonReader.nextName(); | ||||
|             if (!initObjectsFromJsonReader(jsonReader, name)) { | ||||
|                 jsonReader.skipValue(); | ||||
|             } | ||||
|         } | ||||
|         // 结束 JSON 对象 | ||||
|         jsonReader.endObject(); | ||||
|         return this; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,38 @@ | ||||
| package cc.winboll.studio.appbase.handlers; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@AliYun.Com | ||||
|  * @Date 2025/02/14 03:51:40 | ||||
|  */ | ||||
| import android.os.Handler; | ||||
| import android.os.Message; | ||||
| import cc.winboll.studio.appbase.services.MainService; | ||||
| import java.lang.ref.WeakReference; | ||||
|  | ||||
| public class MainServiceHandler extends Handler { | ||||
|     public static final String TAG = "MainServiceHandler"; | ||||
|  | ||||
|     public static final int MSG_REMINDTHREAD = 0; | ||||
|  | ||||
|     WeakReference<MainService> serviceWeakReference; | ||||
|     public MainServiceHandler(MainService service) { | ||||
|         serviceWeakReference = new WeakReference<MainService>(service); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void handleMessage(Message msg) { | ||||
|         switch (msg.what) { | ||||
|             case MSG_REMINDTHREAD: // 处理下载完成消息,更新UI | ||||
|                 { | ||||
|                     // 显示提醒消息 | ||||
|                     // | ||||
|                     //LogUtils.d(TAG, "显示提醒消息"); | ||||
|                     MainService mainService = serviceWeakReference.get(); | ||||
|                     if (mainService != null) { | ||||
|                         mainService.appenMessage((String)msg.obj); | ||||
|                     } | ||||
|                     break; | ||||
|                 } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,132 @@ | ||||
| package cc.winboll.studio.appbase.receivers; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@AliYun.Com | ||||
|  * @Date 2025/02/13 06:58:04 | ||||
|  * @Describe 主要广播接收器 | ||||
|  */ | ||||
| import android.appwidget.AppWidgetManager; | ||||
| import android.content.BroadcastReceiver; | ||||
| import android.content.ComponentName; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.content.IntentFilter; | ||||
| import cc.winboll.studio.appbase.beans.SOSReportBean; | ||||
| import cc.winboll.studio.appbase.services.MainService; | ||||
| import cc.winboll.studio.appbase.widgets.SOSWidget; | ||||
| import cc.winboll.studio.libappbase.AppUtils; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| import cc.winboll.studio.libappbase.SOS; | ||||
| import cc.winboll.studio.libappbase.bean.APPSOSBean; | ||||
| import com.hjq.toast.ToastUtils; | ||||
| import java.io.IOException; | ||||
| import java.lang.ref.WeakReference; | ||||
| import java.text.SimpleDateFormat; | ||||
| import java.util.Date; | ||||
|  | ||||
| public class MainReceiver extends BroadcastReceiver { | ||||
|  | ||||
|     public static final String TAG = "MainReceiver"; | ||||
|     public static final String ACTION_BOOT_COMPLETED = "android.intent.action.BOOT_COMPLETED"; | ||||
|     WeakReference<MainService> mwrService; | ||||
|     // 存储电量指示值, | ||||
|     // 用于校验电量消息时的电量变化 | ||||
|     static volatile int _mnTheQuantityOfElectricityOld = -1; | ||||
|     static volatile boolean _mIsCharging = false; | ||||
|  | ||||
|     public MainReceiver(MainService service) { | ||||
|         mwrService = new WeakReference<MainService>(service); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onReceive(Context context, Intent intent) { | ||||
|         String szAction = intent.getAction(); | ||||
|         if (szAction.equals(ACTION_BOOT_COMPLETED)) { | ||||
|             ToastUtils.show("ACTION_BOOT_COMPLETED"); | ||||
|         } else if (szAction.equals(SOS.ACTION_BIND)) { | ||||
|             LogUtils.d(TAG, "ACTION_BIND"); | ||||
|             LogUtils.d(TAG, String.format("context.getPackageName() %s", context.getPackageName())); | ||||
|             LogUtils.d(TAG, String.format("intent.getAction() %s", intent.getAction())); | ||||
|             String SOS = intent.getStringExtra("SOS"); | ||||
|             LogUtils.d(TAG, String.format("SOS %s", SOS)); | ||||
|             if (SOS != null && SOS.equals("Service")) { | ||||
|                 String szAPPSOSBean = intent.getStringExtra("APPSOSBean"); | ||||
|                 LogUtils.d(TAG, String.format("szAPPSOSBean %s", szAPPSOSBean)); | ||||
|                 if (szAPPSOSBean != null && !szAPPSOSBean.equals("")) { | ||||
|                     try { | ||||
|                         APPSOSBean bean = APPSOSBean.parseStringToBean(szAPPSOSBean, APPSOSBean.class); | ||||
|                         if (bean != null) { | ||||
|                             String sosPackage = bean.getSosPackage(); | ||||
|                             LogUtils.d(TAG, String.format("sosPackage %s", sosPackage)); | ||||
|                             String sosClassName = bean.getSosClassName(); | ||||
|                             LogUtils.d(TAG, String.format("sosClassName %s", sosClassName)); | ||||
|                             mwrService.get().bindSOSConnection(bean); | ||||
|                         } | ||||
|                     } catch (IOException e) { | ||||
|                         LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } else if (intent.getAction().equals(SOS.ACTION_SOS)) { | ||||
|             LogUtils.d(TAG, "ACTION_SOS"); | ||||
|             LogUtils.d(TAG, String.format("context.getPackageName() %s", context.getPackageName())); | ||||
|             LogUtils.d(TAG, String.format("intent.getAction() %s", intent.getAction())); | ||||
|             String SOS = intent.getStringExtra("SOS"); | ||||
|             LogUtils.d(TAG, String.format("SOS %s", SOS)); | ||||
|             if (SOS != null && SOS.equals("Service")) { | ||||
|                 String szAPPSOSBean = intent.getStringExtra("APPSOSBean"); | ||||
|                 LogUtils.d(TAG, String.format("szAPPSOSBean %s", szAPPSOSBean)); | ||||
|                 if (szAPPSOSBean != null && !szAPPSOSBean.equals("")) { | ||||
|                     try { | ||||
|                         APPSOSBean bean = APPSOSBean.parseStringToBean(szAPPSOSBean, APPSOSBean.class); | ||||
|                         if (bean != null) { | ||||
|                             String sosPackage = bean.getSosPackage(); | ||||
|                             LogUtils.d(TAG, String.format("sosPackage %s", sosPackage)); | ||||
|                             String sosClassName = bean.getSosClassName(); | ||||
|                             LogUtils.d(TAG, String.format("sosClassName %s", sosClassName)); | ||||
|  | ||||
|                             Intent intentService = new Intent(); | ||||
|                             intentService.setComponent(new ComponentName(sosPackage, sosClassName)); | ||||
|                             context.startService(intentService); | ||||
|  | ||||
|                             String appName = AppUtils.getAppNameByPackageName(context, sosPackage); | ||||
|                             LogUtils.d(TAG, String.format("appName %s", appName)); | ||||
|                             SOSReportBean appSOSReportBean = new SOSReportBean(appName); | ||||
|                             SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); | ||||
|                             String currentTime = sdf.format(new Date()); | ||||
|                             StringBuilder sbLine = new StringBuilder(); | ||||
|                             sbLine.append("["); | ||||
|                             sbLine.append(currentTime); | ||||
|                             sbLine.append("] Power to "); | ||||
|                             sbLine.append(appName); | ||||
|                             appSOSReportBean.setSosReport(sbLine.toString()); | ||||
|  | ||||
|                             SOSWidget.addAPPSOSReportBean(context, appSOSReportBean); | ||||
|                              | ||||
|                             Intent intentWidget = new Intent(context, SOSWidget.class); | ||||
|                             intentWidget.setAction(SOSWidget.ACTION_RELOAD_REPORT); | ||||
|                             context.sendBroadcast(intentWidget); | ||||
|                         } | ||||
|                     } catch (IOException e) { | ||||
|                         LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } else { | ||||
|             ToastUtils.show(szAction); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // 注册 Receiver | ||||
|     // | ||||
|     public void registerAction(MainService service) { | ||||
|         IntentFilter filter=new IntentFilter(); | ||||
|         filter.addAction(ACTION_BOOT_COMPLETED); | ||||
|         filter.addAction(SOS.ACTION_SOS); | ||||
|         filter.addAction(SOS.ACTION_BIND); | ||||
|         filter.addAction(SOS.ACTION_SERVICE_ENABLE); | ||||
|         filter.addAction(SOS.ACTION_SERVICE_DISABLE); | ||||
|         //filter.addAction(Intent.ACTION_BATTERY_CHANGED); | ||||
|         service.registerReceiver(this, filter); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,138 @@ | ||||
| package cc.winboll.studio.appbase.services; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@AliYun.Com | ||||
|  * @Date 2025/02/14 03:38:31 | ||||
|  * @Describe 守护进程服务 | ||||
|  */ | ||||
| import android.app.Service; | ||||
| import android.content.ComponentName; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.content.ServiceConnection; | ||||
| import android.os.IBinder; | ||||
| import cc.winboll.studio.appbase.beans.MainServiceBean; | ||||
| import cc.winboll.studio.appbase.services.AssistantService; | ||||
| import cc.winboll.studio.appbase.services.MainService; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| import android.os.Binder; | ||||
|  | ||||
| public class AssistantService extends Service { | ||||
|  | ||||
|     public static final String TAG = "AssistantService"; | ||||
|  | ||||
|     MainServiceBean mMainServiceBean; | ||||
|     MyServiceConnection mMyServiceConnection; | ||||
|     MainService mMainService; | ||||
|     boolean isBound = false; | ||||
|     volatile boolean isThreadAlive = false; | ||||
|  | ||||
|     public synchronized void setIsThreadAlive(boolean isThreadAlive) { | ||||
|         LogUtils.d(TAG, "setIsThreadAlive(...)"); | ||||
|         LogUtils.d(TAG, String.format("isThreadAlive %s", isThreadAlive)); | ||||
|         this.isThreadAlive = isThreadAlive; | ||||
|     } | ||||
|  | ||||
|     public boolean isThreadAlive() { | ||||
|         return isThreadAlive; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public IBinder onBind(Intent intent) { | ||||
|         return new MyBinder(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onCreate() { | ||||
|         LogUtils.d(TAG, "onCreate"); | ||||
|         super.onCreate(); | ||||
|  | ||||
|         //mMyBinder = new MyBinder(); | ||||
|         if (mMyServiceConnection == null) { | ||||
|             mMyServiceConnection = new MyServiceConnection(); | ||||
|         } | ||||
|         // 设置运行参数 | ||||
|         setIsThreadAlive(false); | ||||
|         assistantService(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int onStartCommand(Intent intent, int flags, int startId) { | ||||
|         LogUtils.d(TAG, "call onStartCommand(...)"); | ||||
|         assistantService(); | ||||
|         return START_STICKY; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onDestroy() { | ||||
|         //LogUtils.d(TAG, "onDestroy"); | ||||
|         setIsThreadAlive(false); | ||||
|         // 解除绑定 | ||||
|         if (isBound) { | ||||
|             unbindService(mMyServiceConnection); | ||||
|             isBound = false; | ||||
|         } | ||||
|         super.onDestroy(); | ||||
|     } | ||||
|  | ||||
|     // 运行服务内容 | ||||
|     // | ||||
|     void assistantService() { | ||||
|         LogUtils.d(TAG, "assistantService()"); | ||||
|         mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class); | ||||
|         LogUtils.d(TAG, String.format("mMainServiceBean.isEnable() %s", mMainServiceBean.isEnable())); | ||||
|         if (mMainServiceBean.isEnable()) { | ||||
|             LogUtils.d(TAG, String.format("mIsThreadAlive %s", isThreadAlive())); | ||||
|             if (isThreadAlive() == false) { | ||||
|                 // 设置运行状态 | ||||
|                 setIsThreadAlive(true); | ||||
|                 // 唤醒和绑定主进程 | ||||
|                 wakeupAndBindMain(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // 唤醒和绑定主进程 | ||||
|     // | ||||
|     void wakeupAndBindMain() { | ||||
|         LogUtils.d(TAG, "wakeupAndBindMain()"); | ||||
|         // 绑定服务的Intent | ||||
|         Intent intent = new Intent(this, MainService.class); | ||||
|         startService(new Intent(this, MainService.class)); | ||||
|         bindService(intent, mMyServiceConnection, Context.BIND_IMPORTANT); | ||||
|      | ||||
| //        startService(new Intent(this, MainService.class)); | ||||
| //        bindService(new Intent(AssistantService.this, MainService.class), mMyServiceConnection, Context.BIND_IMPORTANT); | ||||
|     } | ||||
|  | ||||
|     // 主进程与守护进程连接时需要用到此类 | ||||
|     // | ||||
|     class MyServiceConnection implements ServiceConnection { | ||||
|         @Override | ||||
|         public void onServiceConnected(ComponentName name, IBinder service) { | ||||
|             LogUtils.d(TAG, "onServiceConnected(...)"); | ||||
|             MainService.MyBinder binder = (MainService.MyBinder) service; | ||||
|             mMainService = binder.getService(); | ||||
|             isBound = true; | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void onServiceDisconnected(ComponentName name) { | ||||
|             LogUtils.d(TAG, "onServiceDisconnected(...)"); | ||||
|             mMainServiceBean = MainServiceBean.loadBean(AssistantService.this, MainServiceBean.class); | ||||
|             if (mMainServiceBean.isEnable()) { | ||||
|                 wakeupAndBindMain(); | ||||
|             } | ||||
|             isBound = false; | ||||
|             mMainService = null; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     // 用于返回服务实例的Binder | ||||
|     public class MyBinder extends Binder { | ||||
|         AssistantService getService() { | ||||
|             LogUtils.d(TAG, "AssistantService MyBinder getService()"); | ||||
|             return AssistantService.this; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,326 @@ | ||||
| package cc.winboll.studio.appbase.services; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@AliYun.Com | ||||
|  * @Date 2025/02/13 06:56:41 | ||||
|  * @Describe 拨号主服务 | ||||
|  * 参考: | ||||
|  * 进程保活-双进程守护的正确姿势 | ||||
|  * https://blog.csdn.net/sinat_35159441/article/details/75267380 | ||||
|  * Android Service之onStartCommand方法研究 | ||||
|  * https://blog.csdn.net/cyp331203/article/details/38920491 | ||||
|  */ | ||||
| import android.app.Service; | ||||
| import android.content.ComponentName; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.content.ServiceConnection; | ||||
| import android.os.Binder; | ||||
| import android.os.IBinder; | ||||
| import cc.winboll.studio.appbase.MyTileService; | ||||
| import cc.winboll.studio.appbase.beans.MainServiceBean; | ||||
| import cc.winboll.studio.appbase.handlers.MainServiceHandler; | ||||
| import cc.winboll.studio.appbase.receivers.MainReceiver; | ||||
| import cc.winboll.studio.appbase.services.AssistantService; | ||||
| import cc.winboll.studio.appbase.threads.MainServiceThread; | ||||
| import cc.winboll.studio.appbase.widgets.SOSWidget; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| import cc.winboll.studio.libappbase.bean.APPSOSBean; | ||||
| import java.util.ArrayList; | ||||
|  | ||||
| public class MainService extends Service { | ||||
|  | ||||
|     public static final String TAG = "MainService"; | ||||
|  | ||||
|     public static final int MSG_UPDATE_STATUS = 0; | ||||
|  | ||||
|     static MainService _mControlCenterService; | ||||
|  | ||||
|     volatile boolean isServiceRunning; | ||||
|  | ||||
|     MainServiceBean mMainServiceBean; | ||||
|     MainServiceThread mMainServiceThread; | ||||
|     MainServiceHandler mMainServiceHandler; | ||||
|     MyServiceConnection mMyServiceConnection; | ||||
|     AssistantService mAssistantService; | ||||
|     boolean isBound = false; | ||||
|     MainReceiver mMainReceiver; | ||||
|     ArrayList<SOSConnection> mSOSConnectionList; | ||||
|  | ||||
|     @Override | ||||
|     public IBinder onBind(Intent intent) { | ||||
|         return new MyBinder(); | ||||
|     } | ||||
|  | ||||
|     public MainServiceThread getRemindThread() { | ||||
|         return mMainServiceThread; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onCreate() { | ||||
|         super.onCreate(); | ||||
|         LogUtils.d(TAG, "onCreate()"); | ||||
|         mSOSConnectionList = new ArrayList<SOSConnection>(); | ||||
|  | ||||
|         _mControlCenterService = MainService.this; | ||||
|         isServiceRunning = false; | ||||
|         mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class); | ||||
|  | ||||
|         if (mMyServiceConnection == null) { | ||||
|             mMyServiceConnection = new MyServiceConnection(); | ||||
|         } | ||||
|         mMainServiceHandler = new MainServiceHandler(this); | ||||
|  | ||||
|         // 运行服务内容 | ||||
|         mainService(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int onStartCommand(Intent intent, int flags, int startId) { | ||||
|         LogUtils.d(TAG, "onStartCommand(...)"); | ||||
|         // 运行服务内容 | ||||
|         mainService(); | ||||
|         return (mMainServiceBean.isEnable()) ? START_STICKY : super.onStartCommand(intent, flags, startId); | ||||
|     } | ||||
|  | ||||
|     // 运行服务内容 | ||||
|     // | ||||
|     void mainService() { | ||||
|         LogUtils.d(TAG, "mainService()"); | ||||
|         mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class); | ||||
|         if (mMainServiceBean.isEnable() && isServiceRunning == false) { | ||||
|             LogUtils.d(TAG, "mainService() start running"); | ||||
|             isServiceRunning = true; | ||||
|             // 唤醒守护进程 | ||||
|             wakeupAndBindAssistant(); | ||||
|  | ||||
|             if (mMainReceiver == null) { | ||||
|                 // 注册广播接收器 | ||||
|                 mMainReceiver = new MainReceiver(this); | ||||
|                 mMainReceiver.registerAction(this); | ||||
|             } | ||||
|  | ||||
|             // 启动小部件 | ||||
|             Intent intentTimeWidget = new Intent(this, SOSWidget.class); | ||||
|             intentTimeWidget.setAction(SOSWidget.ACTION_RELOAD_REPORT); | ||||
|             this.sendBroadcast(intentTimeWidget); | ||||
|  | ||||
|             startMainServiceThread(); | ||||
|  | ||||
|             MyTileService.updateServiceIconStatus(this); | ||||
|  | ||||
|             LogUtils.i(TAG, "Main Service Is Start."); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // 唤醒和绑定守护进程 | ||||
|     // | ||||
|     void wakeupAndBindAssistant() { | ||||
|         LogUtils.d(TAG, "wakeupAndBindAssistant()"); | ||||
| //        if (ServiceUtils.isServiceAlive(getApplicationContext(), AssistantService.class.getName()) == false) { | ||||
| //            startService(new Intent(MainService.this, AssistantService.class)); | ||||
| //            //LogUtils.d(TAG, "call wakeupAndBindAssistant() : Binding... AssistantService"); | ||||
| //            bindService(new Intent(MainService.this, AssistantService.class), mMyServiceConnection, Context.BIND_IMPORTANT); | ||||
| //        } | ||||
|         Intent intent = new Intent(this, AssistantService.class); | ||||
|         startService(intent); | ||||
|         // 绑定服务的Intent | ||||
|         //Intent intent = new Intent(this, AssistantService.class); | ||||
|         bindService(intent, mMyServiceConnection, Context.BIND_IMPORTANT); | ||||
|  | ||||
| //        Intent intent = new Intent(this, AssistantService.class); | ||||
| //        startService(intent); | ||||
| //        LogUtils.d(TAG, "startService(intent)"); | ||||
| //        bindService(new Intent(this, AssistantService.class), mMyServiceConnection, Context.BIND_IMPORTANT); | ||||
|     } | ||||
|  | ||||
|     // 开启提醒铃声线程 | ||||
|     // | ||||
|     public void startMainServiceThread() { | ||||
|         LogUtils.d(TAG, "startMainServiceThread"); | ||||
|         if (mMainServiceThread == null) { | ||||
|             mMainServiceThread = new MainServiceThread(this, mMainServiceHandler); | ||||
|             LogUtils.d(TAG, "new MainServiceThread"); | ||||
|         } else { | ||||
|             if (mMainServiceThread.isExist() == true) { | ||||
|                 mMainServiceThread = new MainServiceThread(this, mMainServiceHandler); | ||||
|                 LogUtils.d(TAG, "renew MainServiceThread"); | ||||
|             } else { | ||||
|                 // 提醒进程正在进行中就更新状态后退出 | ||||
|                 LogUtils.d(TAG, "A mMainServiceThread running."); | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|         mMainServiceThread.start(); | ||||
|     } | ||||
|  | ||||
|     public void stopRemindThread() { | ||||
|         if (mMainServiceThread != null) { | ||||
|             mMainServiceThread.setIsExist(true); | ||||
|             mMainServiceThread = null; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onDestroy() { | ||||
|         //LogUtils.d(TAG, "onDestroy"); | ||||
|         mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class); | ||||
|         if (mMainServiceBean.isEnable() == false) { | ||||
|             // 设置运行状态 | ||||
|             isServiceRunning = false;// 解除绑定 | ||||
|             if (isBound) { | ||||
|                 unbindService(mMyServiceConnection); | ||||
|                 isBound = false; | ||||
|             } | ||||
|             // 停止守护进程 | ||||
|             Intent intent = new Intent(this, AssistantService.class); | ||||
|             stopService(intent); | ||||
|             // 停止Receiver | ||||
|             if (mMainReceiver != null) { | ||||
|                 unregisterReceiver(mMainReceiver); | ||||
|                 mMainReceiver = null; | ||||
|             } | ||||
|             // 停止前台通知栏 | ||||
|             stopForeground(true); | ||||
|             // 停止消息提醒进程 | ||||
|             stopRemindThread(); | ||||
|  | ||||
|             MyTileService.updateServiceIconStatus(this); | ||||
|  | ||||
|             super.onDestroy(); | ||||
|             //LogUtils.d(TAG, "onDestroy done"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public void bindSOSConnection(APPSOSBean bean) { | ||||
|         LogUtils.d(TAG, "bindSOSConnection(...)"); | ||||
|         // 清理旧的绑定链接 | ||||
|         for (int i = mSOSConnectionList.size() - 1; i > -1; i--) { | ||||
|             SOSConnection item = mSOSConnectionList.get(i); | ||||
|             if (item.isBindToAPPSOSBean(bean)) { | ||||
|                 LogUtils.d(TAG, "Bind Servive exist."); | ||||
|                 unbindService(item); | ||||
|                 mSOSConnectionList.remove(i); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // 绑定服务 | ||||
|         SOSConnection sosConnection = new SOSConnection(); | ||||
|         Intent intentService = new Intent(); | ||||
|         intentService.setComponent(new ComponentName(bean.getSosPackage(), bean.getSosClassName())); | ||||
|         bindService(intentService, sosConnection, Context.BIND_IMPORTANT); | ||||
|         mSOSConnectionList.add(sosConnection); | ||||
|          | ||||
|         Intent intentWidget = new Intent(this, SOSWidget.class); | ||||
|         intentWidget.setAction(SOSWidget.ACTION_WAKEUP_SERVICE); | ||||
|         APPSOSBean appSOSBean = new APPSOSBean(bean.getSosPackage(), bean.getSosClassName()); | ||||
|         intentWidget.putExtra("APPSOSBean", appSOSBean.toString()); | ||||
|         sendBroadcast(intentWidget); | ||||
|     } | ||||
|  | ||||
|     public class SOSConnection implements ServiceConnection { | ||||
|  | ||||
|         ComponentName mComponentName; | ||||
|  | ||||
|         boolean isBindToAPPSOSBean(APPSOSBean bean) { | ||||
|             return mComponentName != null | ||||
|                 && mComponentName.getClassName().equals(bean.getSosClassName()) | ||||
|                 && mComponentName.getPackageName().equals(bean.getSosPackage()); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void onServiceConnected(ComponentName name, IBinder service) { | ||||
|             LogUtils.d(TAG, "onServiceConnected(...)"); | ||||
|             mComponentName = name; | ||||
|             LogUtils.d(TAG, String.format("onServiceConnected : \ngetClassName %s\ngetPackageName %s", name.getClassName(), name.getPackageName())); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void onServiceDisconnected(ComponentName name) { | ||||
|             LogUtils.d(TAG, "onServiceDisconnected(...)"); | ||||
|             LogUtils.d(TAG, String.format("onServiceDisconnected : \ngetClassName %s\ngetPackageName %s", name.getClassName(), name.getPackageName())); | ||||
|  | ||||
|             // 尝试无参数启动一下服务 | ||||
|             String sosPackage = mComponentName.getPackageName(); | ||||
|             LogUtils.d(TAG, String.format("sosPackage %s", sosPackage)); | ||||
|             String sosClassName = mComponentName.getClassName(); | ||||
|             LogUtils.d(TAG, String.format("sosClassName %s", sosClassName)); | ||||
|  | ||||
|             Intent intentService = new Intent(); | ||||
|             intentService.setComponent(new ComponentName(sosPackage, sosClassName)); | ||||
|             startService(intentService); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     // 主进程与守护进程连接时需要用到此类 | ||||
|     // | ||||
|     private class MyServiceConnection implements ServiceConnection { | ||||
|         @Override | ||||
|         public void onServiceConnected(ComponentName name, IBinder service) { | ||||
|             LogUtils.d(TAG, "onServiceConnected(...)"); | ||||
|             AssistantService.MyBinder binder = (AssistantService.MyBinder) service; | ||||
|             mAssistantService = binder.getService(); | ||||
|             isBound = true; | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void onServiceDisconnected(ComponentName name) { | ||||
|             LogUtils.d(TAG, "onServiceDisconnected(...)"); | ||||
|  | ||||
|             if (mMainServiceBean.isEnable()) { | ||||
|                 // 唤醒守护进程 | ||||
|                 wakeupAndBindAssistant(); | ||||
|             } | ||||
|             isBound = false; | ||||
|             mAssistantService = null; | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|  | ||||
|     // 用于返回服务实例的Binder | ||||
|     public class MyBinder extends Binder { | ||||
|         MainService getService() { | ||||
|             LogUtils.d(TAG, "MainService MyBinder getService()"); | ||||
|             return MainService.this; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| //    // | ||||
| //    // 启动服务 | ||||
| //    // | ||||
| //    public static void startControlCenterService(Context context) { | ||||
| //        Intent intent = new Intent(context, MainService.class); | ||||
| //        context.startForegroundService(intent); | ||||
| //    } | ||||
| // | ||||
| //    // | ||||
| //    // 停止服务 | ||||
| //    // | ||||
| //    public static void stopControlCenterService(Context context) { | ||||
| //        Intent intent = new Intent(context, MainService.class); | ||||
| //        context.stopService(intent); | ||||
| //    } | ||||
|  | ||||
|     public void appenMessage(String message) { | ||||
|         LogUtils.d(TAG, String.format("Message : %s", message)); | ||||
|     } | ||||
|  | ||||
|     public static void stopMainService(Context context) { | ||||
|         LogUtils.d(TAG, "stopMainService"); | ||||
|         MainServiceBean bean = new MainServiceBean(); | ||||
|         bean.setIsEnable(false); | ||||
|         MainServiceBean.saveBean(context, bean); | ||||
|         context.stopService(new Intent(context, MainService.class)); | ||||
|     } | ||||
|  | ||||
|     public static void startMainService(Context context) { | ||||
|         LogUtils.d(TAG, "startMainService"); | ||||
|         MainServiceBean bean = new MainServiceBean(); | ||||
|         bean.setIsEnable(true); | ||||
|         MainServiceBean.saveBean(context, bean); | ||||
|         context.startService(new Intent(context, MainService.class)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -0,0 +1,54 @@ | ||||
| package cc.winboll.studio.appbase.threads; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@AliYun.Com | ||||
|  * @Date 2025/02/14 03:46:44 | ||||
|  */ | ||||
| import android.content.Context; | ||||
| import cc.winboll.studio.appbase.handlers.MainServiceHandler; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| import java.lang.ref.WeakReference; | ||||
|  | ||||
| public class MainServiceThread extends Thread { | ||||
|  | ||||
|     public static final String TAG = "MainServiceThread"; | ||||
|  | ||||
|     Context mContext; | ||||
|  | ||||
|     // 控制线程是否退出的标志 | ||||
|     volatile boolean isExist = false; | ||||
|  | ||||
|     // 服务Handler, 用于线程发送消息使用 | ||||
|     WeakReference<MainServiceHandler> mwrMainServiceHandler; | ||||
|  | ||||
|     public void setIsExist(boolean isExist) { | ||||
|         this.isExist = isExist; | ||||
|     } | ||||
|  | ||||
|     public boolean isExist() { | ||||
|         return isExist; | ||||
|     } | ||||
|  | ||||
|     public MainServiceThread(Context context, MainServiceHandler handler) { | ||||
|         mContext = context; | ||||
|         mwrMainServiceHandler = new WeakReference<MainServiceHandler>(handler); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void run() { | ||||
|         LogUtils.d(TAG, "run()"); | ||||
|  | ||||
|         while (!isExist()) { | ||||
|             //ToastUtils.show("run()"); | ||||
|             //LogUtils.d(TAG, "run()"); | ||||
|  | ||||
|             try { | ||||
|                 Thread.sleep(1000); | ||||
|             } catch (InterruptedException e) { | ||||
|                 LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|             } | ||||
|         } | ||||
|         LogUtils.d(TAG, "run() exit."); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,184 @@ | ||||
| package cc.winboll.studio.appbase.widgets; | ||||
| /** | ||||
|  * @Author ZhanGSKen@AliYun.Com | ||||
|  * @Date 2025/02/15 14:41:25 | ||||
|  * @Describe TimeWidget | ||||
|  */ | ||||
| import android.app.PendingIntent; | ||||
| import android.appwidget.AppWidgetManager; | ||||
| import android.appwidget.AppWidgetProvider; | ||||
| import android.content.ComponentName; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.widget.RemoteViews; | ||||
| import cc.winboll.studio.appbase.R; | ||||
| import cc.winboll.studio.appbase.beans.SOSReportBean; | ||||
| import cc.winboll.studio.libappbase.AppUtils; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| import cc.winboll.studio.libappbase.bean.APPSOSBean; | ||||
| import java.io.IOException; | ||||
| import java.text.SimpleDateFormat; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Date; | ||||
|  | ||||
| public class SOSWidget extends AppWidgetProvider { | ||||
|  | ||||
|     public static final String TAG = "SOSWidget"; | ||||
|      | ||||
|     public static final String ACTION_WAKEUP_SERVICE = "cc.winboll.studio.appbase.widgets.SOSWidget.ACTION_WAKEUP_SERVICE"; | ||||
|     public static final String ACTION_RELOAD_REPORT = "cc.winboll.studio.appbase.widgets.SOSWidget.ACTION_RELOAD_REPORT"; | ||||
|  | ||||
|  | ||||
|     volatile static ArrayList<SOSReportBean> _SOSReportBeanList; | ||||
|     final static int _MAX_PAGES = 10; | ||||
|     final static int _OnePageLinesCount = 5; | ||||
|     volatile static int _CurrentPageIndex = 0; | ||||
|  | ||||
|     @Override | ||||
|     public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { | ||||
|         initAPPSOSReportBeanList(context); | ||||
|         for (int appWidgetId : appWidgetIds) { | ||||
|             updateAppWidget(context, appWidgetManager, appWidgetId); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onReceive(Context context, Intent intent) { | ||||
|         super.onReceive(context, intent); | ||||
|         initAPPSOSReportBeanList(context); | ||||
|         if (intent.getAction().equals(ACTION_RELOAD_REPORT)) { | ||||
|             LogUtils.d(TAG, "ACTION_RELOAD_REPORT"); | ||||
|             AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); | ||||
|             int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, SOSWidget.class)); | ||||
|             for (int appWidgetId : appWidgetIds) { | ||||
|                 updateAppWidget(context, appWidgetManager, appWidgetId); | ||||
|             } | ||||
|         }else if (intent.getAction().equals(ACTION_WAKEUP_SERVICE)) { | ||||
|             LogUtils.d(TAG, "ACTION_WAKEUP_SERVICE"); | ||||
|             String szAPPSOSBean = intent.getStringExtra("APPSOSBean"); | ||||
|             LogUtils.d(TAG, String.format("szAPPSOSBean %s", szAPPSOSBean)); | ||||
|             if (szAPPSOSBean != null && !szAPPSOSBean.equals("")) { | ||||
|                 try { | ||||
|                     APPSOSBean bean = APPSOSBean.parseStringToBean(szAPPSOSBean, APPSOSBean.class); | ||||
|                     if (bean != null) { | ||||
|                         String sosPackage = bean.getSosPackage(); | ||||
|                         LogUtils.d(TAG, String.format("sosPackage %s", sosPackage)); | ||||
|                         String sosClassName = bean.getSosClassName(); | ||||
|                         LogUtils.d(TAG, String.format("sosClassName %s", sosClassName)); | ||||
|  | ||||
|                          | ||||
|                         String appName = AppUtils.getAppNameByPackageName(context, sosPackage); | ||||
|                         LogUtils.d(TAG, String.format("appName %s", appName)); | ||||
|                         SOSReportBean appSOSReportBean = new SOSReportBean(appName); | ||||
|                         SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); | ||||
|                         String currentTime = sdf.format(new Date()); | ||||
|                         StringBuilder sbLine = new StringBuilder(); | ||||
|                         sbLine.append("["); | ||||
|                         sbLine.append(currentTime); | ||||
|                         sbLine.append("] Wake up "); | ||||
|                         sbLine.append(appName); | ||||
|                         appSOSReportBean.setSosReport(sbLine.toString()); | ||||
|                          | ||||
|                         addAPPSOSReportBean(context, appSOSReportBean); | ||||
|  | ||||
|                         AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); | ||||
|                         int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, SOSWidget.class)); | ||||
|                         for (int appWidgetId : appWidgetIds) { | ||||
|                             updateAppWidget(context, appWidgetManager, appWidgetId); | ||||
|                         } | ||||
|                     } | ||||
|                 } catch (IOException e) { | ||||
|                     LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     // 加入新报告信息 | ||||
|     // | ||||
|     public synchronized static void addAPPSOSReportBean(Context context, SOSReportBean bean) { | ||||
|         initAPPSOSReportBeanList(context); | ||||
|         _SOSReportBeanList.add(0, bean); | ||||
|         // 控制记录总数 | ||||
|         while (_SOSReportBeanList.size() > _MAX_PAGES * _OnePageLinesCount) { | ||||
|             _SOSReportBeanList.remove(_SOSReportBeanList.size() - 1); | ||||
|         } | ||||
|         SOSReportBean.saveBeanList(context, _SOSReportBeanList, SOSReportBean.class); | ||||
|     } | ||||
|  | ||||
|     synchronized static void initAPPSOSReportBeanList(Context context) { | ||||
|         if (_SOSReportBeanList == null) { | ||||
|             _SOSReportBeanList = new ArrayList<SOSReportBean>(); | ||||
|             SOSReportBean.loadBeanList(context, _SOSReportBeanList, SOSReportBean.class); | ||||
|         } | ||||
|         if (_SOSReportBeanList == null) { | ||||
|             _SOSReportBeanList = new ArrayList<SOSReportBean>(); | ||||
|             SOSReportBean.saveBeanList(context, _SOSReportBeanList, SOSReportBean.class); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) { | ||||
|         LogUtils.d(TAG, "updateAppWidget(...)"); | ||||
|  | ||||
|         RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_sos); | ||||
|         //设置按钮点击事件 | ||||
|         Intent intentPre = new Intent(context, SOSWidgetClickListener.class); | ||||
|         intentPre.setAction(SOSWidgetClickListener.ACTION_PRE); | ||||
|         PendingIntent pendingIntentPre = PendingIntent.getBroadcast(context, 0, intentPre, PendingIntent.FLAG_UPDATE_CURRENT); | ||||
|         views.setOnClickPendingIntent(R.id.widget_button_pre, pendingIntentPre); | ||||
|         Intent intentNext = new Intent(context, SOSWidgetClickListener.class); | ||||
|         intentNext.setAction(SOSWidgetClickListener.ACTION_NEXT); | ||||
|         PendingIntent pendingIntentNext = PendingIntent.getBroadcast(context, 0, intentNext, PendingIntent.FLAG_UPDATE_CURRENT); | ||||
|         views.setOnClickPendingIntent(R.id.widget_button_next, pendingIntentNext); | ||||
|  | ||||
|         views.setTextViewText(R.id.infoTextView, getPageInfo()); | ||||
|         views.setTextViewText(R.id.sosReportTextView, getMessage()); | ||||
|         appWidgetManager.updateAppWidget(appWidgetId, views); | ||||
|     } | ||||
|  | ||||
|     public static String getMessage() { | ||||
|         ArrayList<String> msgTemp = new ArrayList<String>(); | ||||
|         if (_SOSReportBeanList != null) { | ||||
|             int start = _OnePageLinesCount * _CurrentPageIndex; | ||||
|             start = _SOSReportBeanList.size() > start ? start : _SOSReportBeanList.size() - 1; | ||||
|             for (int i = start, j = 0; i < _SOSReportBeanList.size() && j < _OnePageLinesCount && start > -1; i++, j++) { | ||||
|                 msgTemp.add(_SOSReportBeanList.get(i).getSosReport()); | ||||
|             } | ||||
|             String message = String.join("\n", msgTemp); | ||||
|             return message; | ||||
|         } | ||||
|         return ""; | ||||
|     } | ||||
|  | ||||
|     public static void prePage(Context context) { | ||||
|         if (_SOSReportBeanList != null) { | ||||
|             if (_CurrentPageIndex > 0) { | ||||
|                 _CurrentPageIndex = _CurrentPageIndex - 1; | ||||
|             } | ||||
|             Intent intentWidget = new Intent(context, SOSWidget.class); | ||||
|             intentWidget.setAction(SOSWidget.ACTION_RELOAD_REPORT); | ||||
|             context.sendBroadcast(intentWidget); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static void nextPage(Context context) { | ||||
|         if (_SOSReportBeanList != null) { | ||||
|             if ((_CurrentPageIndex + 1) * _OnePageLinesCount < _SOSReportBeanList.size()) { | ||||
|                 _CurrentPageIndex = _CurrentPageIndex + 1; | ||||
|             } | ||||
|             Intent intentWidget = new Intent(context, SOSWidget.class); | ||||
|             intentWidget.setAction(SOSWidget.ACTION_RELOAD_REPORT); | ||||
|             context.sendBroadcast(intentWidget); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     String getPageInfo() { | ||||
|         if (_SOSReportBeanList == null) { | ||||
|             return "0/0"; | ||||
|         } | ||||
|         int leftCount = _SOSReportBeanList.size() % _OnePageLinesCount; | ||||
|         int currentPageCount = _SOSReportBeanList.size() / _OnePageLinesCount + (leftCount == 0 ?0: 1); | ||||
|         return String.format("%d/%d", _CurrentPageIndex + 1, currentPageCount); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,36 @@ | ||||
| package cc.winboll.studio.appbase.widgets; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@AliYun.Com | ||||
|  * @Date 2025/02/15 17:20:46 | ||||
|  * @Describe WidgetButtonClickListener | ||||
|  */ | ||||
| import android.content.BroadcastReceiver; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
|  | ||||
| public class SOSWidgetClickListener extends BroadcastReceiver { | ||||
|  | ||||
|     public static final String TAG = "SOSWidgetClickListener"; | ||||
|     public static final String ACTION_PRE = "cc.winboll.studio.appbase.widgets.SOSWidgetClickListener.ACTION_PRE"; | ||||
|     public static final String ACTION_NEXT = "cc.winboll.studio.appbase.widgets.SOSWidgetClickListener.ACTION_NEXT"; | ||||
|  | ||||
|     @Override | ||||
|     public void onReceive(Context context, Intent intent) { | ||||
|         String action = intent.getAction(); | ||||
|         if (action == null) { | ||||
|             LogUtils.d(TAG, String.format("action %s", action)); | ||||
|             return; | ||||
|         } | ||||
|         if (action.equals(ACTION_PRE)) { | ||||
|             LogUtils.d(TAG, "ACTION_PRE"); | ||||
|             SOSWidget.prePage(context); | ||||
|         } else if (action.equals(ACTION_NEXT)) { | ||||
|             LogUtils.d(TAG, "ACTION_NEXT"); | ||||
|             SOSWidget.nextPage(context); | ||||
|         } else { | ||||
|             LogUtils.d(TAG, String.format("action %s", action)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										11
									
								
								appbase/src/main/res/drawable/ic_cloud.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								appbase/src/main/res/drawable/ic_cloud.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="M6.5,20Q4.22,20 2.61,18.43 1,16.85 1,14.58 1,12.63 2.17,11.1 3.35,9.57 5.25,9.15 5.88,6.85 7.75,5.43 9.63,4 12,4 14.93,4 16.96,6.04 19,8.07 19,11 20.73,11.2 21.86,12.5 23,13.78 23,15.5 23,17.38 21.69,18.69 20.38,20 18.5,20Z"/> | ||||
|  | ||||
| </vector> | ||||
							
								
								
									
										11
									
								
								appbase/src/main/res/drawable/ic_cloud_outline.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								appbase/src/main/res/drawable/ic_cloud_outline.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="M6.5,20Q4.22,20 2.61,18.43 1,16.85 1,14.58 1,12.63 2.17,11.1 3.35,9.57 5.25,9.15 5.88,6.85 7.75,5.43 9.63,4 12,4 14.93,4 16.96,6.04 19,8.07 19,11 20.73,11.2 21.86,12.5 23,13.78 23,15.5 23,17.38 21.69,18.69 20.38,20 18.5,20M6.5,18H18.5Q19.55,18 20.27,17.27 21,16.55 21,15.5 21,14.45 20.27,13.73 19.55,13 18.5,13H17V11Q17,8.93 15.54,7.46 14.08,6 12,6 9.93,6 8.46,7.46 7,8.93 7,11H6.5Q5.05,11 4.03,12.03 3,13.05 3,14.5 3,15.95 4.03,17 5.05,18 6.5,18M12,12Z"/> | ||||
|  | ||||
| </vector> | ||||
| @@ -4,13 +4,146 @@ | ||||
| 	xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
| 	android:layout_width="match_parent" | ||||
| 	android:layout_height="match_parent" | ||||
| 	android:orientation="vertical" | ||||
| 	android:gravity="center"> | ||||
|  | ||||
| 	<TextView | ||||
| 		android:layout_width="wrap_content" | ||||
| 	<androidx.appcompat.widget.Toolbar | ||||
| 		android:layout_width="match_parent" | ||||
| 		android:layout_height="wrap_content" | ||||
| 		android:text="Hello, WinBoll!" | ||||
| 		android:onClick="onHello"/> | ||||
| 		android:id="@+id/activitymainToolbar1"/> | ||||
|  | ||||
| 	<LinearLayout | ||||
| 		android:orientation="vertical" | ||||
| 		android:layout_width="match_parent" | ||||
| 		android:layout_height="0dp" | ||||
| 		android:layout_weight="1.0" | ||||
| 		android:gravity="center_horizontal"> | ||||
|  | ||||
| 		<TextView | ||||
| 			android:layout_width="wrap_content" | ||||
| 			android:layout_height="wrap_content" | ||||
| 			android:text="Hello, WinBoll!"/> | ||||
|  | ||||
| 		<TextView | ||||
| 			android:layout_width="wrap_content" | ||||
| 			android:layout_height="wrap_content" | ||||
| 			android:text="Android版本10的代号是“Q”,API级别是29。   Android 10开始谷歌不再公开使用甜品作为版本代号,但内部仍保留了大量与“Q”相关的元素。Android 10本身并没有严格对应某个特定的Java版本,但在开发Android 10应用时,通常可以使用Java 8或更高版本。 | ||||
|   | ||||
| Java 8为Android开发带来了诸如Lambda表达式、方法引用等新特性,能提高开发效率和代码可读性,与Android 10开发适配良好。Java 9及更高版本也可用于Android 10开发,能使用一些新的语言特性和API,但可能需要注意兼容性和配置问题。"/> | ||||
|  | ||||
| 		<LinearLayout | ||||
| 			android:orientation="horizontal" | ||||
| 			android:layout_width="match_parent" | ||||
| 			android:layout_height="wrap_content" | ||||
| 			android:gravity="right|center_vertical"> | ||||
|  | ||||
| 			<CheckBox | ||||
| 				android:layout_width="wrap_content" | ||||
| 				android:layout_height="wrap_content" | ||||
| 				android:text="Debug Mode" | ||||
| 				android:layout_weight="1.0" | ||||
| 				android:onClick="onSwitchDebugMode" | ||||
| 				android:id="@+id/activitymainCheckBox1"/> | ||||
|  | ||||
| 			<Button | ||||
| 				android:layout_width="wrap_content" | ||||
| 				android:layout_height="wrap_content" | ||||
| 				android:text="Test Application CrashReport" | ||||
| 				android:textAllCaps="false" | ||||
| 				android:onClick="onTestApplicationCrashReport"/> | ||||
|  | ||||
| 		</LinearLayout> | ||||
|  | ||||
| 		<ScrollView | ||||
| 			android:layout_width="match_parent" | ||||
| 			android:layout_height="400dp"> | ||||
|  | ||||
| 			<LinearLayout | ||||
| 				android:orientation="vertical" | ||||
| 				android:layout_width="match_parent" | ||||
| 				android:layout_height="wrap_content" | ||||
| 				android:gravity="right"> | ||||
|  | ||||
| 				<HorizontalScrollView | ||||
| 					android:layout_width="match_parent" | ||||
| 					android:layout_height="wrap_content"> | ||||
|  | ||||
| 					<LinearLayout | ||||
| 						android:orientation="horizontal" | ||||
| 						android:layout_width="wrap_content" | ||||
| 						android:layout_height="wrap_content"> | ||||
|  | ||||
| 						<Button | ||||
| 							android:layout_width="wrap_content" | ||||
| 							android:layout_height="wrap_content" | ||||
| 							android:text="StartTestService" | ||||
| 							android:textAllCaps="false" | ||||
| 							android:onClick="onStartTestService"/> | ||||
|  | ||||
| 						<Button | ||||
| 							android:layout_width="wrap_content" | ||||
| 							android:layout_height="wrap_content" | ||||
| 							android:text="StopTestService" | ||||
| 							android:textAllCaps="false" | ||||
| 							android:onClick="onStopTestService"/> | ||||
|  | ||||
| 						<Button | ||||
| 							android:layout_width="wrap_content" | ||||
| 							android:layout_height="wrap_content" | ||||
| 							android:text="StopTestServiceNoSettings" | ||||
| 							android:textAllCaps="false" | ||||
| 							android:onClick="onStopTestServiceNoSettings"/> | ||||
|  | ||||
| 					</LinearLayout> | ||||
|  | ||||
| 				</HorizontalScrollView> | ||||
|  | ||||
| 				<Button | ||||
| 					android:layout_width="wrap_content" | ||||
| 					android:layout_height="wrap_content" | ||||
| 					android:text="StartCenter" | ||||
| 					android:textAllCaps="false" | ||||
| 					android:onClick="onStartCenter"/> | ||||
|  | ||||
| 				<Button | ||||
| 					android:layout_width="wrap_content" | ||||
| 					android:layout_height="wrap_content" | ||||
| 					android:text="StopCenter" | ||||
| 					android:textAllCaps="false" | ||||
| 					android:onClick="onStopCenter"/> | ||||
|  | ||||
| 				<Button | ||||
| 					android:layout_width="wrap_content" | ||||
| 					android:layout_height="wrap_content" | ||||
| 					android:text="TestStopWithoutSettingEnable" | ||||
| 					android:textAllCaps="false" | ||||
| 					android:onClick="onTestStopWithoutSettingEnable"/> | ||||
|  | ||||
| 				<Button | ||||
| 					android:layout_width="wrap_content" | ||||
| 					android:layout_height="wrap_content" | ||||
| 					android:text="TestStartWithString" | ||||
| 					android:textAllCaps="false" | ||||
| 					android:onClick="onTestStartWithString"/> | ||||
|  | ||||
| 				<Button | ||||
| 					android:layout_width="wrap_content" | ||||
| 					android:layout_height="wrap_content" | ||||
| 					android:text="SOS" | ||||
| 					android:textAllCaps="false" | ||||
| 					android:onClick="onSOS"/> | ||||
|  | ||||
| 			</LinearLayout> | ||||
|  | ||||
| 		</ScrollView> | ||||
|  | ||||
| 		<cc.winboll.studio.libappbase.LogView | ||||
| 			android:layout_weight="1.0" | ||||
| 			android:layout_height="0dp" | ||||
| 			android:layout_width="match_parent" | ||||
| 			android:id="@+id/activitymainLogView1"/> | ||||
|  | ||||
| 	</LinearLayout> | ||||
|  | ||||
| </LinearLayout> | ||||
|  | ||||
|   | ||||
							
								
								
									
										42
									
								
								appbase/src/main/res/layout/widget_sos.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								appbase/src/main/res/layout/widget_sos.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <LinearLayout | ||||
| 	xmlns:android="http://schemas.android.com/apk/res/android" | ||||
| 	xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
| 	android:layout_width="match_parent" | ||||
| 	android:layout_height="match_parent" | ||||
| 	android:orientation="vertical" | ||||
| 	android:background="#FFFFFFFF"> | ||||
|  | ||||
| 	<LinearLayout | ||||
| 		android:orientation="horizontal" | ||||
| 		android:layout_width="match_parent" | ||||
| 		android:layout_height="wrap_content" | ||||
| 		android:gravity="right"> | ||||
|  | ||||
|         <TextView | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:id="@+id/infoTextView"/> | ||||
|          | ||||
|         <Button | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:text="⇦" | ||||
|             android:id="@+id/widget_button_pre"/> | ||||
|          | ||||
| 		<Button | ||||
| 			android:layout_width="wrap_content" | ||||
| 			android:layout_height="wrap_content" | ||||
| 			android:text="⇨" | ||||
| 			android:id="@+id/widget_button_next"/> | ||||
|  | ||||
| 	</LinearLayout> | ||||
|  | ||||
| 	<TextView | ||||
| 		android:layout_width="match_parent" | ||||
| 		android:layout_height="0dp" | ||||
| 		android:id="@+id/sosReportTextView" | ||||
| 		android:layout_weight="1.0"/> | ||||
|  | ||||
| </LinearLayout> | ||||
|  | ||||
| @@ -1,9 +0,0 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <style name="AppTheme" parent="@android:style/Theme.Material.Light.DarkActionBar"> | ||||
|         <item name="android:colorPrimary">@color/colorPrimary</item> | ||||
|         <item name="android:colorPrimaryDark">@color/colorPrimaryDark</item> | ||||
|         <item name="android:colorAccent">@color/colorAccent</item> | ||||
|         <item name="android:navigationBarColor">?android:colorPrimary</item> | ||||
| 	</style> | ||||
| </resources> | ||||
| @@ -1,6 +1,6 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <color name="colorPrimary">#009688</color> | ||||
|     <color name="colorPrimaryDark">#00796B</color> | ||||
|     <color name="colorAccent">#FF9800</color> | ||||
| </resources> | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <color name="colorPrimary">#005800FF</color> | ||||
|     <color name="colorPrimaryDark">#005800FF</color> | ||||
|     <color name="colorAccent">#005800FF</color> | ||||
| </resources> | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <string name="app_name">AppBase</string> | ||||
| </resources> | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <string name="app_name">AppBase</string> | ||||
|     <string name="tileservice_name">WinBoll</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -1,5 +1,14 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <style name="AppTheme" parent="@android:style/Theme.Holo.Light.DarkActionBar"> | ||||
| 	</style> | ||||
| </resources> | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <style name="AppTheme" parent="APPBaseTheme"> | ||||
|         <item name="attrColorPrimary">@color/colorPrimary</item> | ||||
|         <item name="themeGlobalCrashActivity">@style/MyGlobalCrashActivityTheme</item> | ||||
|     </style> | ||||
|  | ||||
|     <style name="MyGlobalCrashActivityTheme" parent="GlobalCrashActivityTheme"> | ||||
|         <item name="colorTittle">#FFFFFFFF</item> | ||||
|         <item name="colorTittleBackgound">#FF00A4B3</item> | ||||
|         <item name="colorText">#FFFFFFFF</item> | ||||
|         <item name="colorTextBackgound">#FF000000</item> | ||||
| 	</style> | ||||
| </resources> | ||||
|   | ||||
							
								
								
									
										7
									
								
								appbase/src/main/res/xml/widget_provider_info_sos.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								appbase/src/main/res/xml/widget_provider_info_sos.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:minWidth="200dp" | ||||
|     android:minHeight="100dp" | ||||
|     android:updatePeriodMillis="1000" | ||||
|     android:initialLayout="@layout/widget_sos"> | ||||
| </appwidget-provider> | ||||
							
								
								
									
										40
									
								
								build.gradle
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								build.gradle
									
									
									
									
									
								
							| @@ -1,8 +1,21 @@ | ||||
| // Top-level build file where you can add configuration options common to all sub-projects/modules. | ||||
| buildscript { | ||||
|     repositories { | ||||
|         google() | ||||
|         maven { url 'https://maven.aliyun.com/repository/public/' }  | ||||
|         maven { url 'https://maven.aliyun.com/repository/google/' } | ||||
|         maven { url 'https://maven.aliyun.com/repository/gradle-plugin/' } | ||||
|         maven { url 'https://dl.bintray.com/ppartisan/maven/' } | ||||
|         maven { url "https://clojars.org/repo/" } | ||||
|         maven { url "https://jitpack.io" } | ||||
|         mavenCentral() | ||||
|         google() | ||||
|         mavenLocal() | ||||
|         // Nexus Maven 库地址 | ||||
|         // "WinBoll Release" | ||||
|         maven { url "https://nexus.winboll.cc/repository/maven-public/" } | ||||
|         // "WinBoll Snapshot" | ||||
|         maven { url "https://nexus.winboll.cc/repository/maven-snapshots/" } | ||||
|          | ||||
|     } | ||||
|     dependencies { | ||||
|         classpath 'com.android.tools.build:gradle:7.2.1' | ||||
| @@ -13,11 +26,12 @@ buildscript { | ||||
|  | ||||
| allprojects { | ||||
|     repositories { | ||||
|         // Nexus Maven 库地址 | ||||
|         // "WinBoll Release" | ||||
|         maven { url "https://nexus.winboll.cc/repository/maven-public/" } | ||||
|         // "WinBoll Snapshot" | ||||
|         maven { url "https://nexus.winboll.cc/repository/maven-snapshots/" } | ||||
|         maven { | ||||
|             url "https://mirrors.tencent.com/repository/maven/tencent_public/" | ||||
|         } | ||||
|         maven { | ||||
|             url "https://mirrors.tencent.com/repository/maven/tencent_public_snapshots" | ||||
|         } | ||||
|          | ||||
|         maven { url 'https://maven.aliyun.com/repository/public/' }  | ||||
|         maven { url 'https://maven.aliyun.com/repository/google/' } | ||||
| @@ -28,6 +42,12 @@ allprojects { | ||||
|         mavenCentral() | ||||
|         google() | ||||
|         mavenLocal() | ||||
|         // Nexus Maven 库地址 | ||||
|         // "WinBoll Release" | ||||
|         maven { url "https://nexus.winboll.cc/repository/maven-public/" } | ||||
|         // "WinBoll Snapshot" | ||||
|         maven { url "https://nexus.winboll.cc/repository/maven-snapshots/" } | ||||
|          | ||||
|     } | ||||
|     ext { | ||||
|         // 定义全局变量,常用于版本管理 | ||||
| @@ -69,6 +89,14 @@ allprojects { | ||||
|             } | ||||
| 	    } | ||||
|     } | ||||
|      | ||||
|     subprojects { | ||||
|         tasks.withType(JavaCompile) { | ||||
|             options.compilerArgs << "-parameters" | ||||
|             sourceCompatibility = JavaVersion.VERSION_1_8 | ||||
|             targetCompatibility = JavaVersion.VERSION_1_8 | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| task clean(type: Delete) { | ||||
|   | ||||
| @@ -6,7 +6,7 @@ | ||||
| # http://www.gradle.org/docs/current/userguide/build_environment.html | ||||
| # Specifies the JVM arguments used for the daemon process. | ||||
| # The setting is particularly useful for tweaking memory settings. | ||||
| org.gradle.jvmargs=-Xmx2048m | ||||
| org.gradle.jvmargs=-Xmx4096m | ||||
| # When configured, Gradle will run in incubating parallel mode. | ||||
| # This option should only be used with decoupled projects. More details, visit | ||||
| # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects | ||||
| @@ -18,7 +18,9 @@ android.useAndroidX=true | ||||
| # Automatically convert third-party libraries to use AndroidX | ||||
| android.enableJetifier=true | ||||
|  | ||||
| #org.gradle.caching=true | ||||
| org.gradle.caching=true | ||||
|  | ||||
| android.disableAutomaticComponentCreation=true | ||||
|  | ||||
| android.injected.testOnly=false | ||||
|  | ||||
|   | ||||
							
								
								
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| distributionBase=GRADLE_USER_HOME | ||||
| distributionPath=wrapper/dists | ||||
| distributionUrl = https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip | ||||
| distributionUrl = https\://services.gradle.org/distributions/gradle-7.5.1-all.zip | ||||
| zipStoreBase=GRADLE_USER_HOME | ||||
| zipStorePath=wrapper/dists | ||||
|   | ||||
| @@ -4,14 +4,12 @@ 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" | ||||
|     compileSdkVersion 30 | ||||
|     buildToolsVersion "30.0.3" | ||||
|  | ||||
|     defaultConfig { | ||||
|         minSdkVersion 21 | ||||
|         targetSdkVersion 30 | ||||
|         minSdkVersion 26 | ||||
|         targetSdkVersion 29 | ||||
|     } | ||||
|     buildTypes { | ||||
|         release { | ||||
| @@ -19,12 +17,16 @@ android { | ||||
|             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | ||||
|         } | ||||
|     } | ||||
|     compileOptions { | ||||
|         sourceCompatibility JavaVersion.VERSION_11 | ||||
|         targetCompatibility JavaVersion.VERSION_11 | ||||
|     } | ||||
| } | ||||
|  | ||||
| dependencies { | ||||
|     api 'com.github.getActivity:ToastUtils:10.5' | ||||
|      | ||||
|     api 'androidx.appcompat:appcompat:1.3.1' | ||||
|     api 'androidx.vectordrawable:vectordrawable:1.1.0' | ||||
|     api 'androidx.vectordrawable:vectordrawable-animated:1.1.0' | ||||
|     api 'androidx.fragment:fragment:1.1.0' | ||||
|     api 'com.google.android.material:material:1.1.0' | ||||
|      | ||||
|     api fileTree(dir: 'libs', include: ['*.jar']) | ||||
| } | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| #Created by .winboll/winboll_app_build.gradle | ||||
| #Sun Jan 05 07:55:11 HKT 2025 | ||||
| stageCount=17 | ||||
| libraryProject=winboll-shared | ||||
| baseVersion=1.8 | ||||
| publishVersion=1.8.16 | ||||
| #Tue Feb 25 16:51:09 HKT 2025 | ||||
| stageCount=3 | ||||
| libraryProject=libappbase | ||||
| baseVersion=2.0 | ||||
| publishVersion=2.0.2 | ||||
| buildCount=0 | ||||
| baseBetaVersion=1.8.17 | ||||
| baseBetaVersion=2.0.3 | ||||
|   | ||||
| @@ -1,18 +1,78 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     package="cc.winboll.studio" > | ||||
| <?xml version='1.0' encoding='utf-8'?> | ||||
| <manifest | ||||
|     xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     package="cc.winboll.studio.libappbase"> | ||||
|  | ||||
|     <!-- 拥有完全的网络访问权限 --> | ||||
|     <uses-permission android:name="android.permission.INTERNET"/> | ||||
|  | ||||
|     <!-- 发送持久广播 --> | ||||
|     <uses-permission android:name="android.permission.BROADCAST_STICKY"/> | ||||
|  | ||||
|     <application> | ||||
|  | ||||
|         <activity | ||||
|             android:name=".CrashHandler$CrashActiviy" | ||||
|             android:label="CrashActiviy" | ||||
|             android:name=".CrashHandler$CrashActivity" | ||||
|             android:label="CrashActivity" | ||||
|             android:launchMode="standard"/> | ||||
|          | ||||
|  | ||||
|         <activity | ||||
|             android:name=".libappbase.LibraryActivity" | ||||
|             android:label="@string/lib_name" > | ||||
|         </activity> | ||||
|             android:name=".GlobalCrashActivity" | ||||
|             android:label="GlobalCrashActivity" | ||||
|             android:launchMode="standard"/> | ||||
|  | ||||
|         <activity android:name=".LogActivity"/> | ||||
|  | ||||
|         <service | ||||
|             android:name=".SimpleOperateSignalCenterService" | ||||
|             android:exported="true"> | ||||
|  | ||||
|         </service> | ||||
|  | ||||
|         <service | ||||
|             android:name=".services.TestService" | ||||
|             android:exported="true"/> | ||||
|  | ||||
|         <receiver android:name=".receiver.MyBroadcastReceiver"> | ||||
|  | ||||
|             <intent-filter> | ||||
|  | ||||
|                 <action android:name="cc.winboll.studio.libappbase.action.SOS"/> | ||||
|  | ||||
|             </intent-filter> | ||||
|  | ||||
|         </receiver> | ||||
|  | ||||
|         <receiver | ||||
|             android:name=".widgets.StatusWidget" | ||||
|             android:exported="true"> | ||||
|  | ||||
|             <intent-filter> | ||||
|  | ||||
|                 <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/> | ||||
|  | ||||
|                 <action android:name="cc.winboll.studio.libappbase.widgets.StatusWidget.ACTION_STATUS_UPDATE"/> | ||||
|  | ||||
|             </intent-filter> | ||||
|  | ||||
|             <meta-data | ||||
|                 android:name="android.appwidget.provider" | ||||
|                 android:resource="@xml/widget_provider_info_status"/> | ||||
|  | ||||
|         </receiver> | ||||
|  | ||||
|         <receiver | ||||
|             android:name=".widgets.StatusWidgetClickListener" | ||||
|             android:exported="true"> | ||||
|  | ||||
|             <intent-filter> | ||||
|  | ||||
|                 <action android:name="cc.winboll.studio.libappbase.widgets.StatusWidgetClickListener.ACTION_IVAPP"/> | ||||
|  | ||||
|             </intent-filter> | ||||
|  | ||||
|         </receiver> | ||||
|  | ||||
|     </application> | ||||
|  | ||||
| </manifest> | ||||
|  | ||||
|   | ||||
| @@ -1,215 +0,0 @@ | ||||
| package cc.winboll.studio; | ||||
|  | ||||
| /** | ||||
|  * @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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -1,95 +0,0 @@ | ||||
| package cc.winboll.studio; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2025/01/05 10:10:23 | ||||
|  * @Describe 全局应用类 | ||||
|  */ | ||||
| import android.app.Activity; | ||||
| import android.app.Application; | ||||
| import android.content.ClipData; | ||||
| import android.content.ClipboardManager; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.content.pm.PackageInfo; | ||||
| import android.content.res.Resources; | ||||
| import android.graphics.Typeface; | ||||
| import android.os.Build; | ||||
| import android.os.Bundle; | ||||
| import android.os.Handler; | ||||
| import android.os.Looper; | ||||
| import android.text.TextUtils; | ||||
| import android.util.Log; | ||||
| import android.view.Menu; | ||||
| import android.view.MenuItem; | ||||
| import android.view.ViewGroup; | ||||
| import android.widget.HorizontalScrollView; | ||||
| import android.widget.ScrollView; | ||||
| import android.widget.TextView; | ||||
| import android.widget.Toast; | ||||
| import java.io.ByteArrayInputStream; | ||||
| import java.io.ByteArrayOutputStream; | ||||
| import java.io.Closeable; | ||||
| import java.io.File; | ||||
| import java.io.FileInputStream; | ||||
| import java.io.FileOutputStream; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.io.OutputStream; | ||||
| import java.lang.Thread.UncaughtExceptionHandler; | ||||
| import java.text.DateFormat; | ||||
| import java.text.SimpleDateFormat; | ||||
| import java.util.Arrays; | ||||
| import java.util.Date; | ||||
| import java.util.LinkedHashMap; | ||||
| import java.util.concurrent.atomic.AtomicBoolean; | ||||
|  | ||||
| public class GlobalApplication extends Application { | ||||
|  | ||||
|     private static Handler MAIN_HANDLER = new Handler(Looper.getMainLooper()); | ||||
|  | ||||
|     @Override | ||||
|     public void onCreate() { | ||||
|         super.onCreate(); | ||||
|         CrashHandler.init(this); | ||||
|     } | ||||
|  | ||||
|     public static void write(InputStream input, OutputStream output) throws IOException { | ||||
|         byte[] buf = new byte[1024 * 8]; | ||||
|         int len; | ||||
|         while ((len = input.read(buf)) != -1) { | ||||
|             output.write(buf, 0, len); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static void write(File file, byte[] data) throws IOException { | ||||
|         File parent = file.getParentFile(); | ||||
|         if (parent != null && !parent.exists()) parent.mkdirs(); | ||||
|  | ||||
|         ByteArrayInputStream input = new ByteArrayInputStream(data); | ||||
|         FileOutputStream output = new FileOutputStream(file); | ||||
|         try { | ||||
|             write(input, output); | ||||
|         } finally { | ||||
|             closeIO(input, output); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static String toString(InputStream input) throws IOException { | ||||
|         ByteArrayOutputStream output = new ByteArrayOutputStream(); | ||||
|         write(input, output); | ||||
|         try { | ||||
|             return output.toString("UTF-8"); | ||||
|         } finally { | ||||
|             closeIO(input, output); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static void closeIO(Closeable... closeables) { | ||||
|         for (Closeable closeable : closeables) { | ||||
|             try { | ||||
|                 if (closeable != null) closeable.close(); | ||||
|             } catch (IOException ignored) {} | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,28 @@ | ||||
| package cc.winboll.studio.libappbase; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@AliYun.Com | ||||
|  * @Date 2025/02/15 20:05:03 | ||||
|  * @Describe AppUtils | ||||
|  */ | ||||
| import android.content.Context; | ||||
| import android.content.pm.ApplicationInfo; | ||||
| import android.content.pm.PackageManager; | ||||
| import android.content.pm.PackageManager.NameNotFoundException; | ||||
|  | ||||
| public class AppUtils { | ||||
|      | ||||
|     public static final String TAG = "AppUtils"; | ||||
|      | ||||
|     public static String getAppNameByPackageName(Context context, String packageName) { | ||||
|         PackageManager packageManager = context.getPackageManager(); | ||||
|         try { | ||||
|             ApplicationInfo applicationInfo = packageManager.getApplicationInfo(packageName, 0); | ||||
|             return (String) packageManager.getApplicationLabel(applicationInfo); | ||||
|         } catch (NameNotFoundException e) { | ||||
|             LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|             return ""; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -0,0 +1,287 @@ | ||||
| package cc.winboll.studio.libappbase; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2025/01/15 11:11:52 | ||||
|  * @Describe Json Bean 基础类。 | ||||
|  */ | ||||
| import android.content.Context; | ||||
| import android.util.JsonReader; | ||||
| import android.util.JsonWriter; | ||||
| 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 = UTF8FileUtils.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 { | ||||
|             if(beanList == null) { | ||||
|                 beanList = new ArrayList<T>(); | ||||
|             } else { | ||||
|                 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 = UTF8FileUtils.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(); | ||||
|             UTF8FileUtils.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 = UTF8FileUtils.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); | ||||
|             UTF8FileUtils.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,404 @@ | ||||
| package cc.winboll.studio.libappbase; | ||||
|  | ||||
| /** | ||||
|  * @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.ApplicationInfo; | ||||
| import android.content.pm.PackageInfo; | ||||
| import android.content.pm.PackageManager; | ||||
| import android.content.res.Resources; | ||||
| import android.graphics.Color; | ||||
| import android.net.Uri; | ||||
| import android.os.Build; | ||||
| import android.os.Bundle; | ||||
| import android.os.Handler; | ||||
| import android.os.Looper; | ||||
| import android.text.SpannableString; | ||||
| import android.text.TextUtils; | ||||
| import android.text.style.ForegroundColorSpan; | ||||
| import android.view.Menu; | ||||
| import android.view.MenuItem; | ||||
| import android.view.ViewGroup; | ||||
| import android.widget.HorizontalScrollView; | ||||
| import android.widget.LinearLayout; | ||||
| import android.widget.ScrollView; | ||||
| import android.widget.TextView; | ||||
| import android.widget.Toast; | ||||
| import android.widget.Toolbar; | ||||
| import cc.winboll.studio.libappbase.R; | ||||
| import java.io.File; | ||||
| import java.io.FileInputStream; | ||||
| import java.io.FileOutputStream; | ||||
| import java.io.IOException; | ||||
| import java.io.ObjectInputStream; | ||||
| import java.io.ObjectOutputStream; | ||||
| 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 String TAG = "CrashHandler"; | ||||
|  | ||||
|     public static final String TITTLE = "CrashReport"; | ||||
|  | ||||
|     public static final String EXTRA_CRASH_INFO = "crashInfo"; | ||||
|  | ||||
|     final static String PREFS = CrashHandler.class.getName() + "PREFS"; | ||||
|     final static String PREFS_CRASHHANDLER_ISCRASHHAPPEN = "PREFS_CRASHHANDLER_ISCRASHHAPPEN"; | ||||
|  | ||||
|     public static String _CrashCountFilePath; | ||||
|  | ||||
|     public static final UncaughtExceptionHandler DEFAULT_UNCAUGHT_EXCEPTION_HANDLER = Thread.getDefaultUncaughtExceptionHandler(); | ||||
|  | ||||
|     public static void init(Application app) { | ||||
|         _CrashCountFilePath = app.getExternalFilesDir("CrashHandler") + "/IsCrashHandlerCrashHappen.dat"; | ||||
|         LogUtils.d(TAG, String.format("_CrashCountFilePath %s", _CrashCountFilePath)); | ||||
|         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) { | ||||
|                     // 每到这里就燃烧一次保险丝 | ||||
|                     AppCrashSafetyWire.getInstance().burnSafetyWire(); | ||||
|  | ||||
|                     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(); | ||||
|                         LogUtils.d(TAG, "gotoCrashActiviy: "); | ||||
|                         if (AppCrashSafetyWire.getInstance().isAppCrashSafetyWireOK()) { | ||||
|                             LogUtils.d(TAG, "gotoCrashActiviy: isAppCrashSafetyWireOK"); | ||||
|                             intent.setClass(app, GlobalCrashActivity.class); | ||||
|                             intent.putExtra(EXTRA_CRASH_INFO, errorLog); | ||||
|                             // 如果发生了 CrashHandler 内部崩溃, 就调用基础的应用崩溃显示类 | ||||
| //                            intent.setClass(app, GlobalCrashActiviy.class); | ||||
| //                            intent.putExtra(GlobalCrashActiviy.EXTRA_CRASH_INFO, errorLog); | ||||
|                         } else { | ||||
|                             LogUtils.d(TAG, "gotoCrashActiviy: else"); | ||||
|                             // 正常状态调用进阶的应用崩溃显示页 | ||||
|                             intent.setClass(app, CrashActivity.class); | ||||
|                             intent.putExtra(EXTRA_CRASH_INFO, errorLog); | ||||
|                         } | ||||
|  | ||||
|  | ||||
|  | ||||
| //                        intent.setClass(app, CrashActiviy.class); | ||||
| //                        intent.putExtra(CrashActiviy.EXTRA_CRASH_INFO, errorLog); | ||||
|  | ||||
|  | ||||
|                         intent.addFlags( | ||||
|                             Intent.FLAG_ACTIVITY_NEW_TASK | ||||
|                             | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK | ||||
|                         ); | ||||
|  | ||||
|                         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); | ||||
|                         } catch (Exception 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 AppCrashSafetyWire { | ||||
|  | ||||
|         volatile static AppCrashSafetyWire _AppCrashSafetyWire; | ||||
|  | ||||
|         volatile Integer currentSafetyLevel; // 熔断值,为 0 表示熔断了。 | ||||
|         private static final int _MINI = 1; | ||||
|         private static final int _MAX = 2; | ||||
|  | ||||
|         AppCrashSafetyWire() { | ||||
|             LogUtils.d(TAG, "AppCrashSafetyWire()"); | ||||
|             currentSafetyLevel = loadCurrentSafetyLevel(); | ||||
|         } | ||||
|  | ||||
|         public static synchronized AppCrashSafetyWire getInstance() { | ||||
|             if (_AppCrashSafetyWire == null) { | ||||
|                 _AppCrashSafetyWire = new AppCrashSafetyWire(); | ||||
|             } | ||||
|             return _AppCrashSafetyWire; | ||||
|         } | ||||
|  | ||||
|         public void setCurrentSafetyLevel(int currentSafetyLevel) { | ||||
|             this.currentSafetyLevel = currentSafetyLevel; | ||||
|         } | ||||
|  | ||||
|         public int getCurrentSafetyLevel() { | ||||
|             return currentSafetyLevel; | ||||
|         } | ||||
|  | ||||
|         public void saveCurrentSafetyLevel(int currentSafetyLevel) { | ||||
|             LogUtils.d(TAG, "saveCurrentSafetyLevel()"); | ||||
|             this.currentSafetyLevel = currentSafetyLevel; | ||||
|             try { | ||||
|                 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(_CrashCountFilePath)); | ||||
|                 oos.writeInt(currentSafetyLevel); | ||||
|                 oos.flush(); | ||||
|                 oos.close(); | ||||
|                 LogUtils.d(TAG, String.format("saveCurrentSafetyLevel writeInt currentSafetyLevel %d", currentSafetyLevel)); | ||||
|             } catch (IOException e) { | ||||
|                 LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public int loadCurrentSafetyLevel() { | ||||
|             LogUtils.d(TAG, "loadCurrentSafetyLevel()"); | ||||
|             try { | ||||
|                 File f = new File(_CrashCountFilePath); | ||||
|                 if (f.exists()) { | ||||
|                     ObjectInputStream ois = new ObjectInputStream(new FileInputStream(_CrashCountFilePath)); | ||||
|                     currentSafetyLevel = ois.readInt(); | ||||
|                     LogUtils.d(TAG, String.format("loadCurrentSafetyLevel() readInt currentSafetyLevel %d", currentSafetyLevel)); | ||||
|                 } else { | ||||
|                     currentSafetyLevel = _MAX; | ||||
|                     LogUtils.d(TAG, String.format("loadCurrentSafetyLevel() currentSafetyLevel init to _MAX->%d", _MAX)); | ||||
|                     saveCurrentSafetyLevel(currentSafetyLevel); | ||||
|                 } | ||||
|             } catch (IOException e) { | ||||
|                 LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|             } | ||||
|             return currentSafetyLevel; | ||||
|         } | ||||
|  | ||||
|         boolean burnSafetyWire() { | ||||
|             LogUtils.d(TAG, "burnSafetyWire()"); | ||||
|             // 崩溃计数进入崩溃保险值 | ||||
|             int safeLevel = loadCurrentSafetyLevel(); | ||||
|             if (isSafetyWireWorking(safeLevel)) { | ||||
|                 // 如果保险丝未熔断, 就增加一次熔断值 | ||||
|                 LogUtils.d(TAG, "burnSafetyWire() use"); | ||||
|                 saveCurrentSafetyLevel(safeLevel - 1); | ||||
|                 return isSafetyWireWorking(safeLevel - 1); | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         boolean isSafetyWireWorking(int safetyLevel) { | ||||
|             LogUtils.d(TAG, "isSafetyWireOK()"); | ||||
|             //safetyLevel = _MINI; | ||||
|             //safetyLevel = _MINI - 1; | ||||
|             //safetyLevel = _MINI + 1; | ||||
|             //safetyLevel = _MAX; | ||||
|             //safetyLevel = _MAX + 1; | ||||
|             LogUtils.d(TAG, String.format("SafetyLevel %d", safetyLevel)); | ||||
|  | ||||
|             if (safetyLevel >= _MINI && safetyLevel <= _MAX) { | ||||
|                 // 如果在保险值之内 | ||||
|                 LogUtils.d(TAG, String.format("In Safety Level")); | ||||
|                 return true; | ||||
|             } | ||||
|             LogUtils.d(TAG, String.format("Out of Safety Level")); | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         void resumeToMaximumImmediately() { | ||||
|             LogUtils.d(TAG, "resumeToMaximumImmediately() call saveCurrentSafetyLevel(_MAX)"); | ||||
|             AppCrashSafetyWire.getInstance().saveCurrentSafetyLevel(_MAX); | ||||
|         } | ||||
|  | ||||
|         void off() { | ||||
|             LogUtils.d(TAG, "off()"); | ||||
|             saveCurrentSafetyLevel(_MINI); | ||||
|         } | ||||
|  | ||||
|         boolean isAppCrashSafetyWireOK() { | ||||
|             LogUtils.d(TAG, "isAppCrashSafetyWireOK()"); | ||||
|             currentSafetyLevel = loadCurrentSafetyLevel(); | ||||
|             return isSafetyWireWorking(currentSafetyLevel); | ||||
|         } | ||||
|  | ||||
|         // 调用函数以启用持续崩溃保险,从而调用 CrashHandler 内部崩溃处理窗口 | ||||
|         void postResumeCrashSafetyWireHandler(final Context context) { | ||||
|             new Handler(Looper.getMainLooper()).postDelayed(new Runnable(){ | ||||
|                     @Override | ||||
|                     public void run() { | ||||
|                         LogUtils.d(TAG, "Handler run()"); | ||||
|                         if (!AppCrashSafetyWire.getInstance().isSafetyWireWorking(currentSafetyLevel - 1)) { | ||||
|                             // 如果下一次应用崩溃时,保险丝熔断,则先恢复保险丝满能状态 | ||||
|                             // 进程持续运行时,恢复保险丝熔断值 | ||||
|                             //Resume to maximum | ||||
|                             AppCrashSafetyWire.getInstance().resumeToMaximumImmediately(); | ||||
|                             LogUtils.d(TAG, "postResumeCrashSafetyWireHandler"); | ||||
|                         }          | ||||
|                     } | ||||
|                 }, 500); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static final class CrashActivity extends Activity implements MenuItem.OnMenuItemClickListener { | ||||
|         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); | ||||
|             AppCrashSafetyWire.getInstance().postResumeCrashSafetyWireHandler(getApplicationContext()); | ||||
|  | ||||
|             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); | ||||
|                 getActionBar().setTitle(TITTLE); | ||||
|                 getActionBar().setSubtitle(GlobalApplication.class.getSimpleName() + " Error"); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         @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)); | ||||
|                     Toast.makeText(getApplication(), "The text is copied.", Toast.LENGTH_SHORT).show(); | ||||
|                     break; | ||||
|                 case MENUITEM_RESTART:  | ||||
|                     AppCrashSafetyWire.getInstance().resumeToMaximumImmediately(); | ||||
|                     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,119 @@ | ||||
| package cc.winboll.studio.libappbase; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2025/01/05 10:10:23 | ||||
|  * @Describe 全局应用类 | ||||
|  */ | ||||
| import android.app.Application; | ||||
| import android.content.ComponentName; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.content.SharedPreferences; | ||||
| import android.content.pm.ApplicationInfo; | ||||
| import android.content.pm.PackageManager; | ||||
| import android.os.Handler; | ||||
| import android.os.Looper; | ||||
| import android.view.Gravity; | ||||
| import com.hjq.toast.ToastUtils; | ||||
| import com.hjq.toast.style.WhiteToastStyle; | ||||
|  | ||||
| public class GlobalApplication extends Application { | ||||
|  | ||||
|     public static final String TAG = "GlobalApplication"; | ||||
|  | ||||
|     final static String PREFS = GlobalApplication.class.getName() + "PREFS"; | ||||
|     final static String PREFS_ISDEBUGING = "PREFS_ISDEBUGING"; | ||||
|  | ||||
|  | ||||
|     private static Handler MAIN_HANDLER = new Handler(Looper.getMainLooper()); | ||||
|  | ||||
|     // 是否处于调试状态 | ||||
|     volatile static boolean isDebuging = false; | ||||
|  | ||||
|     public static void setIsDebuging(Context context, boolean isDebuging) { | ||||
|         GlobalApplication.isDebuging = isDebuging; | ||||
|         // 获取SharedPreferences实例 | ||||
|         SharedPreferences sharedPreferences = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE); | ||||
|         // 获取编辑器 | ||||
|         SharedPreferences.Editor editor = sharedPreferences.edit(); | ||||
|         // 保存数据 | ||||
|         editor.putBoolean(PREFS_ISDEBUGING, GlobalApplication.isDebuging); | ||||
|         // 提交更改 | ||||
|         editor.apply(); | ||||
|     } | ||||
|  | ||||
|     public static boolean isDebuging() { | ||||
|         return isDebuging; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Context getApplicationContext() { | ||||
|         return super.getApplicationContext(); | ||||
|     } | ||||
|  | ||||
|     public Application getApplication() { | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onCreate() { | ||||
|         super.onCreate(); | ||||
|         //GlobalApplication.isDebuging = true; | ||||
|         //GlobalApplication.setIsDebuging(this, true); | ||||
|         LogUtils.init(this); | ||||
|         //LogUtils.setLogLevel(LogUtils.LOG_LEVEL.Debug); | ||||
|         //LogUtils.setTAGListEnable(GlobalApplication.TAG, true); | ||||
|         //LogUtils.setALlTAGListEnable(true); | ||||
|         //LogUtils.d(TAG, "LogUtils init"); | ||||
|  | ||||
|         // 设置应用异常处理窗口 | ||||
|         CrashHandler.init(this); | ||||
|  | ||||
|         // 设置应用调试状态 | ||||
|         //SharedPreferences sharedPreferences = getSharedPreferences(PREFS, Context.MODE_PRIVATE); | ||||
|         //GlobalApplication.isDebuging = sharedPreferences.getBoolean(PREFS_ISDEBUGING, GlobalApplication.isDebuging); | ||||
|  | ||||
|         // 初始化 Toast 框架 | ||||
|         ToastUtils.init(this); | ||||
|         // 设置 Toast 布局样式 | ||||
|         //ToastUtils.setView(R.layout.toast_custom_view); | ||||
|         ToastUtils.setStyle(new WhiteToastStyle()); | ||||
|         ToastUtils.setGravity(Gravity.BOTTOM, 0, 200); | ||||
|     } | ||||
|  | ||||
|     public static String getAppName(Context context) { | ||||
|         PackageManager packageManager = context.getPackageManager(); | ||||
|         try { | ||||
|             ApplicationInfo applicationInfo = packageManager.getApplicationInfo( | ||||
|                 context.getPackageName(), 0); | ||||
|             return (String) packageManager.getApplicationLabel(applicationInfo); | ||||
|         } catch (PackageManager.NameNotFoundException e) { | ||||
|             e.printStackTrace(); | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
| // | ||||
| //    @Override | ||||
| //    public void helpISOSService(Intent intent) { | ||||
| //        String szServiceName = intent.getStringExtra(EXTRA_SERVICE); | ||||
| //        String szPackageName = intent.getStringExtra(EXTRA_PACKAGE); | ||||
| //        if (szServiceName != null && !szServiceName.equals("") | ||||
| //            && szPackageName != null && !szPackageName.equals("")) { | ||||
| //            LogUtils.d(TAG, "szPackageName " + szPackageName); | ||||
| //            LogUtils.d(TAG, "szServiceName " + szServiceName); | ||||
| // | ||||
| //            // 目标服务的包名和类名 | ||||
| //            //String packageName = this.getPackageName(); | ||||
| //            //String serviceClassName = SimpleOperateSignalCenterService.class.getName(); | ||||
| // | ||||
| //            // 构建Intent | ||||
| //            Intent intentService = new Intent(); | ||||
| //            intentService.setComponent(new ComponentName(szPackageName, szServiceName)); | ||||
| //            intentService.putExtra(ISOSService.EXTRA_ENABLE, true); | ||||
| //            startService(intentService); | ||||
| //            LogUtils.d(TAG, "startService(intentService)"); | ||||
| //        } | ||||
| //        LogUtils.d(TAG, "helpISOSService"); | ||||
| //    } | ||||
| } | ||||
| @@ -0,0 +1,119 @@ | ||||
| package cc.winboll.studio.libappbase; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@AliYun.Com | ||||
|  * @Date 2025/02/11 00:14:05 | ||||
|  */ | ||||
| import android.app.Activity; | ||||
| import android.content.ClipData; | ||||
| import android.content.ClipboardManager; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.content.pm.PackageManager; | ||||
| import android.content.res.TypedArray; | ||||
| import android.graphics.Color; | ||||
| import android.net.Uri; | ||||
| import android.os.Bundle; | ||||
| import android.text.SpannableString; | ||||
| import android.text.style.ForegroundColorSpan; | ||||
| import android.view.Menu; | ||||
| import android.view.MenuItem; | ||||
| import android.widget.LinearLayout; | ||||
| import android.widget.TextView; | ||||
| import android.widget.Toast; | ||||
| import androidx.appcompat.widget.Toolbar; | ||||
| import cc.winboll.studio.libappbase.R; | ||||
| import androidx.appcompat.app.AppCompatActivity; | ||||
|  | ||||
| public final class GlobalCrashActivity extends AppCompatActivity implements MenuItem.OnMenuItemClickListener { | ||||
|  | ||||
|     private static final int MENUITEM_COPY = 0; | ||||
|     private static final int MENUITEM_RESTART = 1; | ||||
|  | ||||
|     GlobalCrashReportView mGlobalCrashReportView; | ||||
|     String mLog; | ||||
|      | ||||
|  | ||||
|     public static final String TAG = "GlobalCrashActivity"; | ||||
|  | ||||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         CrashHandler.AppCrashSafetyWire.getInstance().postResumeCrashSafetyWireHandler(getApplicationContext()); | ||||
|  | ||||
|         mLog = getIntent().getStringExtra(CrashHandler.EXTRA_CRASH_INFO); | ||||
|         //setTheme(android.R.style.Theme_Holo_Light_NoActionBar); | ||||
|         //setTheme(R.style.APPBaseTheme); | ||||
|         setContentView(R.layout.activity_globalcrash); | ||||
|         mGlobalCrashReportView = findViewById(R.id.activityglobalcrashGlobalCrashReportView1); | ||||
|         mGlobalCrashReportView.setReport(mLog); | ||||
|         setSupportActionBar(mGlobalCrashReportView.getToolbar()); | ||||
|          | ||||
|         getSupportActionBar().setTitle(CrashHandler.TITTLE); | ||||
|         getSupportActionBar().setSubtitle(GlobalApplication.getAppName(getApplicationContext())); | ||||
|     } | ||||
|  | ||||
|     @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); | ||||
|     } | ||||
|  | ||||
|     @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)); | ||||
|                 Toast.makeText(getApplication(), "The text is copied.", Toast.LENGTH_SHORT).show(); | ||||
|                 break; | ||||
|             case MENUITEM_RESTART:  | ||||
|                 CrashHandler.AppCrashSafetyWire.getInstance().resumeToMaximumImmediately(); | ||||
|                 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); | ||||
|          | ||||
|         // 更新菜单文字风格 | ||||
|         mGlobalCrashReportView.updateMenuStyle(); | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     void joinQQGroup(String key) { | ||||
|         // 创建Intent | ||||
|         Intent intent = new Intent(); | ||||
|         // 设置动作 | ||||
|         intent.setAction("android.intent.action.VIEW"); | ||||
|         // 设置数据为网址的URI | ||||
|         Uri content_url = Uri.parse("https://www.winboll.cc"); | ||||
|         intent.setData(content_url); | ||||
|         // 添加标志 | ||||
|         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||||
|         // 设置类名和活动名 | ||||
|         intent.setClassName("com.android.browser", "com.android.browser.BrowserActivity"); | ||||
|         // 启动Activity | ||||
|         startActivity(intent); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,138 @@ | ||||
| package cc.winboll.studio.libappbase; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@AliYun.Com | ||||
|  * @Date 2025/02/11 20:18:30 | ||||
|  * @Describe 应用崩溃报告视图 | ||||
|  */ | ||||
| import android.content.Context; | ||||
| import android.content.res.TypedArray; | ||||
| import android.graphics.Color; | ||||
| import android.text.SpannableString; | ||||
| import android.text.style.ForegroundColorSpan; | ||||
| import android.util.AttributeSet; | ||||
| import android.view.Menu; | ||||
| import android.view.MenuItem; | ||||
| import android.widget.LinearLayout; | ||||
| import android.widget.TextView; | ||||
| import androidx.appcompat.widget.Toolbar; | ||||
| import cc.winboll.studio.libappbase.R; | ||||
|  | ||||
| public class GlobalCrashReportView extends LinearLayout { | ||||
|  | ||||
|     public static final String TAG = "GlobalCrashReportView"; | ||||
|  | ||||
|     Context mContext; | ||||
|     Toolbar mToolbar; | ||||
|     int colorTittle; | ||||
|     int colorTittleBackground; | ||||
|     int colorText; | ||||
|     int colorTextBackground; | ||||
|     TextView mtvReport; | ||||
|  | ||||
|     public GlobalCrashReportView(Context context) { | ||||
|         super(context); | ||||
|         mContext = context; | ||||
|         //initView(); | ||||
|     } | ||||
|  | ||||
|     public GlobalCrashReportView(Context context, AttributeSet attrs) { | ||||
|         super(context, attrs); | ||||
|         mContext = context; | ||||
|         initView(attrs); | ||||
|     } | ||||
|  | ||||
|     public GlobalCrashReportView(Context context, AttributeSet attrs, int defStyleAttr) { | ||||
|         super(context, attrs, defStyleAttr); | ||||
|         mContext = context; | ||||
|         //initView(); | ||||
|     } | ||||
|  | ||||
|     public GlobalCrashReportView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { | ||||
|         super(context, attrs, defStyleAttr, defStyleRes); | ||||
|         mContext = context; | ||||
|         //initView(); | ||||
|     } | ||||
|  | ||||
|     public void setColorTittle(int colorTittle) { | ||||
|         this.colorTittle = colorTittle; | ||||
|     } | ||||
|  | ||||
|     public int getColorTittle() { | ||||
|         return colorTittle; | ||||
|     } | ||||
|  | ||||
|     public void setColorTittleBackground(int colorTittleBackground) { | ||||
|         this.colorTittleBackground = colorTittleBackground; | ||||
|     } | ||||
|  | ||||
|     public int getColorTittleBackground() { | ||||
|         return colorTittleBackground; | ||||
|     } | ||||
|  | ||||
|     public void setColorText(int colorText) { | ||||
|         this.colorText = colorText; | ||||
|     } | ||||
|  | ||||
|     public int getColorText() { | ||||
|         return colorText; | ||||
|     } | ||||
|  | ||||
|     public void setColorTextBackground(int colorTextBackground) { | ||||
|         this.colorTextBackground = colorTextBackground; | ||||
|     } | ||||
|  | ||||
|     public int getColorTextBackground() { | ||||
|         return colorTextBackground; | ||||
|     } | ||||
|  | ||||
|     void initView(AttributeSet attrs) { | ||||
|         TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.GlobalCrashActivity, R.attr.themeGlobalCrashActivity, 0); | ||||
|         this.colorTittle = a.getColor(R.styleable.GlobalCrashActivity_colorTittle, Color.WHITE); | ||||
|         this.colorTittleBackground = a.getColor(R.styleable.GlobalCrashActivity_colorTittleBackgound, Color.BLACK); | ||||
|         this.colorText = a.getColor(R.styleable.GlobalCrashActivity_colorText, Color.BLACK); | ||||
|         this.colorTextBackground = a.getColor(R.styleable.GlobalCrashActivity_colorTextBackgound, Color.WHITE); | ||||
|         // 返回一个绑定资源结束的信号给资源 | ||||
|         a.recycle(); | ||||
|  | ||||
|         /*this.colorTittle = Color.WHITE; | ||||
|         this.colorTittleBackground = Color.BLACK; | ||||
|         this.colorText = Color.BLACK; | ||||
|         this.colorTextBackground = Color.WHITE; | ||||
|         */ | ||||
|          | ||||
|         inflate(mContext, R.layout.view_globalcrashreport, this); | ||||
|  | ||||
|         LinearLayout llMain = findViewById(R.id.viewglobalcrashreportLinearLayout1); | ||||
|         llMain.setBackgroundColor(this.colorTextBackground); | ||||
|         mToolbar = findViewById(R.id.viewglobalcrashreportToolbar1); | ||||
|         mToolbar.setBackgroundColor(this.colorTittleBackground); | ||||
|         mToolbar.setTitleTextColor(this.colorTittle); | ||||
|         mToolbar.setSubtitleTextColor(this.colorTittle); | ||||
|         mtvReport = findViewById(R.id.viewglobalcrashreportTextView1); | ||||
|         mtvReport.setTextColor(this.colorText); | ||||
|         mtvReport.setBackgroundColor(this.colorTextBackground); | ||||
|     } | ||||
|  | ||||
|     public void setReport(String report) { | ||||
|         mtvReport.setText(report); | ||||
|     } | ||||
|  | ||||
|     public Toolbar getToolbar() { | ||||
|         return mToolbar; | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     // 更新菜单文字风格 | ||||
|     // | ||||
|     public void updateMenuStyle() { | ||||
|         // 设置菜单文本颜色 | ||||
|         Menu menu = mToolbar.getMenu(); | ||||
|         for (int i = 0; i < menu.size(); i++) { | ||||
|             MenuItem item = menu.getItem(i); | ||||
|             SpannableString spanString = new SpannableString(item.getTitle().toString()); | ||||
|             spanString.setSpan(new ForegroundColorSpan(this.colorTittle), 0, spanString.length(), 0); | ||||
|             item.setTitle(spanString); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,24 @@ | ||||
| package cc.winboll.studio.libappbase; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2025/02/04 10:20:16 | ||||
|  * @Describe WinBoll Activity 接口 | ||||
|  */ | ||||
| import androidx.appcompat.app.AppCompatActivity; | ||||
| import androidx.appcompat.widget.Toolbar; | ||||
| import cc.winboll.studio.libappbase.bean.APPInfo; | ||||
|  | ||||
| public interface IWinBollActivity { | ||||
|  | ||||
|     public static final String TAG = "IWinBollActivity"; | ||||
|  | ||||
|     // 获取当前具有 IWinBoll 特征的 AppCompatActivity 活动窗口 | ||||
|     AppCompatActivity getActivity(); | ||||
|  | ||||
|     abstract public APPInfo getAppInfo(); | ||||
|     abstract public String getTag(); | ||||
|     abstract Toolbar initToolBar(); | ||||
|     abstract boolean isEnableDisplayHomeAsUp(); | ||||
|     abstract boolean isAddWinBollToolBar(); | ||||
| } | ||||
| @@ -1,15 +0,0 @@ | ||||
| package cc.winboll.studio.libappbase; | ||||
|  | ||||
| import android.app.*; | ||||
| import android.os.*; | ||||
| import cc.winboll.studio.R; | ||||
|  | ||||
| public class LibraryActivity extends Activity  | ||||
| { | ||||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) | ||||
|     { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         setContentView(R.layout.library); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,68 @@ | ||||
| package cc.winboll.studio.libappbase; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/08/12 15:07:58 | ||||
|  * @Describe WinBoll 应用日志窗口 | ||||
|  */ | ||||
| import android.os.Bundle; | ||||
| import androidx.appcompat.app.AppCompatActivity; | ||||
| import androidx.appcompat.widget.Toolbar; | ||||
| import cc.winboll.studio.libappbase.GlobalApplication; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| import cc.winboll.studio.libappbase.bean.APPInfo; | ||||
| import cc.winboll.studio.libappbase.R; | ||||
|  | ||||
| public class LogActivity extends AppCompatActivity implements IWinBollActivity { | ||||
|  | ||||
|     public static final String TAG = "LogActivity"; | ||||
|  | ||||
|     LogView mLogView; | ||||
|  | ||||
|     @Override | ||||
|     public AppCompatActivity getActivity() { | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public APPInfo getAppInfo() { | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getTag() { | ||||
|         return TAG; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Toolbar initToolBar() { | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean isEnableDisplayHomeAsUp() { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean isAddWinBollToolBar() { | ||||
|         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); | ||||
|  | ||||
|         if (GlobalApplication.isDebuging()) { mLogView.start(); } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onResume() { | ||||
|         LogUtils.d(TAG, "onResume"); | ||||
|         super.onResume(); | ||||
|         mLogView.start(); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,370 @@ | ||||
| package cc.winboll.studio.libappbase; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/08/12 13:44:06 | ||||
|  * @Describe LogUtils | ||||
|  * @Describe 应用日志类 | ||||
|  */ | ||||
| import android.content.Context; | ||||
| import cc.winboll.studio.libappbase.GlobalApplication; | ||||
| 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 (GlobalApplication.isDebuging()) { | ||||
|             // 初始化日志缓存文件路径 | ||||
|             _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 (NoClassDefFoundError | ClassNotFoundException | IllegalAccessException e) { | ||||
|                     LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); | ||||
|                     //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 { | ||||
|                 UTF8FileUtils.writeStringToFile(_mfLogCatchFile.getPath(), ""); | ||||
|                 //LogUtils.d(TAG, "cleanLog"); | ||||
|             } catch (IOException e) { | ||||
|                 LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,70 @@ | ||||
| package cc.winboll.studio.libappbase; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/08/23 15:39:07 | ||||
|  * @Describe LogUtils 数据配置类。 | ||||
|  */ | ||||
| import android.util.JsonReader; | ||||
| import android.util.JsonWriter; | ||||
| 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,87 @@ | ||||
| package cc.winboll.studio.libappbase; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2025/01/04 14:17:02 | ||||
|  * @Describe 日志类class TAG 标签数据类 | ||||
|  */ | ||||
| import android.util.JsonReader; | ||||
| import android.util.JsonWriter; | ||||
| 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,387 @@ | ||||
| package cc.winboll.studio.libappbase; | ||||
|  | ||||
| /** | ||||
|  * @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.libappbase.LogUtils; | ||||
| import cc.winboll.studio.libappbase.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.libappbase.R.layout.view_log, null)); | ||||
|         // 初始化日志子控件视图 | ||||
|         // | ||||
|         mScrollView = findViewById(cc.winboll.studio.libappbase.R.id.viewlogScrollViewLog); | ||||
|         mTextView = findViewById(cc.winboll.studio.libappbase.R.id.viewlogTextViewLog); | ||||
|         // 获取Log Level spinner实例 | ||||
|         mLogLevelSpinner = findViewById(cc.winboll.studio.libappbase.R.id.viewlogSpinner1); | ||||
|  | ||||
|         (findViewById(cc.winboll.studio.libappbase.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.libappbase.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.libappbase.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.libappbase.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.libappbase.R.id.viewlogCheckBox1); | ||||
|         cbALLTAG.setChecked(isAllSelect); | ||||
|  | ||||
|         // 加载标签表 | ||||
|         recyclerView = findViewById(cc.winboll.studio.libappbase.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.libappbase.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,80 @@ | ||||
| package cc.winboll.studio.libappbase; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/08/12 14:43:50 | ||||
|  * @Describe 日志视图线程类 | ||||
|  */ | ||||
| import android.os.FileObserver; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| 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,57 @@ | ||||
| package cc.winboll.studio.libappbase; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@AliYun.Com | ||||
|  * @Date 2025/02/13 21:09:36 | ||||
|  * @Describe SOS 组件 | ||||
|  */ | ||||
| import android.content.ComponentName; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.content.ServiceConnection; | ||||
| import android.os.IBinder; | ||||
| import cc.winboll.studio.libappbase.bean.APPSOSBean; | ||||
| import java.util.ArrayList; | ||||
|  | ||||
| public class SOS { | ||||
|  | ||||
|     public static final String TAG = "SOS"; | ||||
|      | ||||
|     public static final String ACTION_SOS = SOS.class.getName() + ".ACTION_SOS"; | ||||
|     public static final String ACTION_BIND = SOS.class.getName() + ".ACTION_BIND"; | ||||
|     public static final String ACTION_SERVICE_ENABLE = SOS.class.getName() + ".ACTION_SERVICE_ENABLE"; | ||||
|     public static final String ACTION_SERVICE_DISABLE = SOS.class.getName() + ".ACTION_SERVICE_DISENABLE"; | ||||
|      | ||||
|     public static void sosWinBollService(Context context, APPSOSBean bean) { | ||||
|         Intent intent = new Intent(ACTION_SOS); | ||||
|         intent.putExtra("SOS", "Service"); | ||||
|         intent.putExtra("APPSOSBean", bean.toString()); | ||||
|         String szToPackage = ""; | ||||
|         if (GlobalApplication.isDebuging()) { | ||||
|             szToPackage = "cc.winboll.studio.appbase.beta"; | ||||
|         } else { | ||||
|             szToPackage = "cc.winboll.studio.appbase"; | ||||
|         } | ||||
|         intent.setPackage(szToPackage); | ||||
|         context.sendBroadcast(intent); | ||||
|  | ||||
|         LogUtils.d(TAG, String.format("Send ACTION_SOS To WinBoll. (szToPackage : %s)", szToPackage)); | ||||
|         //ToastUtils.show("SOS Send To WinBoll"); | ||||
|     } | ||||
|  | ||||
|     public static void bindToAPPService(Context context, APPSOSBean bean) { | ||||
|         Intent intent = new Intent(ACTION_BIND); | ||||
|         intent.putExtra("SOS", "Service"); | ||||
|         intent.putExtra("APPSOSBean", bean.toString()); | ||||
|         String szToPackage = ""; | ||||
|         if (GlobalApplication.isDebuging()) { | ||||
|             szToPackage = "cc.winboll.studio.appbase.beta"; | ||||
|         } else { | ||||
|             szToPackage = "cc.winboll.studio.appbase"; | ||||
|         } | ||||
|         intent.setPackage(szToPackage); | ||||
|         context.sendBroadcast(intent); | ||||
|         LogUtils.d(TAG, String.format("Send ACTION_BIND To WinBoll. (szToPackage : %s)", szToPackage)); | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -0,0 +1,32 @@ | ||||
| package cc.winboll.studio.libappbase; | ||||
|  | ||||
| import android.content.BroadcastReceiver; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@AliYun.Com | ||||
|  * @Date 2025/02/12 23:44:57 | ||||
|  * @Describe 简单信号通信中心接收器 | ||||
|  */ | ||||
| public class SOSCSBroadcastReceiver extends BroadcastReceiver { | ||||
|  | ||||
|     public static final String TAG = "SOSCSBroadcastReceiver"; | ||||
|     public static final String ACTION_SOS = SOSCSBroadcastReceiver.class.getName() + ".ACTION_SOS"; | ||||
|  | ||||
|     //ISOSAPP mISOSAPP; | ||||
|  | ||||
|     public SOSCSBroadcastReceiver() { | ||||
|         //mISOSAPP = iSOSAPP; | ||||
|     } | ||||
|     @Override | ||||
|     public void onReceive(Context context, Intent intent) { | ||||
|         String action = intent.getAction(); | ||||
|         if (action.equals(ACTION_SOS)) { | ||||
|             LogUtils.d(TAG, "ACTION_SOS"); | ||||
|             //mISOSAPP.helpISOSService(intent); | ||||
|         } else { | ||||
|             LogUtils.d(TAG, String.format("%s", action)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,187 @@ | ||||
| package cc.winboll.studio.libappbase; | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@AliYun.Com | ||||
|  * @Date 2025/02/12 11:12:25 | ||||
|  * @Describe 简单信号服务中心 | ||||
|  */ | ||||
| import android.app.Service; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.os.IBinder; | ||||
| import android.os.IInterface; | ||||
| import android.os.Parcel; | ||||
| import android.os.RemoteException; | ||||
| import cc.winboll.studio.libappbase.bean.SimpleOperateSignalCenterServiceBean; | ||||
| import java.io.FileDescriptor; | ||||
|  | ||||
| public class SimpleOperateSignalCenterService extends Service { | ||||
|  | ||||
|     public static final String TAG = "SimpleOperateSignalCenterService"; | ||||
|     public static final String ACTION_ENABLE = SimpleOperateSignalCenterService.class.getName() + ".ACTION_ENABLE"; | ||||
|     public static final String ACTION_DISABLE = SimpleOperateSignalCenterService.class.getName() + ".ACTION_DISABLE"; | ||||
|      | ||||
|     private final IBinder binder =(IBinder)new SOSBinder(); | ||||
|      | ||||
|     SimpleOperateSignalCenterServiceBean mSimpleOperateSignalCenterServiceBean; | ||||
|     static MainThread _MainThread; | ||||
|     public static synchronized MainThread getMainThreadInstance() { | ||||
|         if (_MainThread == null) { | ||||
|             _MainThread = new MainThread(); | ||||
|         } | ||||
|         return _MainThread; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public IBinder onBind(Intent intent) { | ||||
|         return binder; | ||||
|     } | ||||
|      | ||||
|     public class SOSBinder implements IBinder { | ||||
|  | ||||
|         @Override | ||||
|         public void dump(FileDescriptor fileDescriptor, String[] string) throws RemoteException { | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void dumpAsync(FileDescriptor fileDescriptor, String[] string) throws RemoteException { | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public String getInterfaceDescriptor() throws RemoteException { | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public boolean isBinderAlive() { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void linkToDeath(IBinder.DeathRecipient deathRecipient, int p) throws RemoteException { | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public boolean pingBinder() { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public IInterface queryLocalInterface(String string) { | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public boolean transact(int p, Parcel parcel, Parcel parcel1, int p1) throws RemoteException { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public boolean unlinkToDeath(IBinder.DeathRecipient deathRecipient, int p) { | ||||
|             return false; | ||||
|         } | ||||
|          | ||||
|         public static final String TAG = "SOSBinder"; | ||||
|         SimpleOperateSignalCenterService getService() { | ||||
|             return SimpleOperateSignalCenterService.this; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     @Override | ||||
|     public void onCreate() { | ||||
|         super.onCreate(); | ||||
|         LogUtils.d(TAG, "onCreate"); | ||||
|         mSimpleOperateSignalCenterServiceBean = SimpleOperateSignalCenterServiceBean.loadBean(this, SimpleOperateSignalCenterServiceBean.class); | ||||
|         if(mSimpleOperateSignalCenterServiceBean == null) { | ||||
|             mSimpleOperateSignalCenterServiceBean = new SimpleOperateSignalCenterServiceBean(); | ||||
|             SimpleOperateSignalCenterServiceBean.saveBean(this, mSimpleOperateSignalCenterServiceBean); | ||||
|         } | ||||
|         runMainThread(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int onStartCommand(Intent intent, int flags, int startId) { | ||||
|         LogUtils.d(TAG, "onStartCommand"); | ||||
| //        if (intent.getBooleanExtra(ISOSService.EXTRA_ENABLE, false)) { | ||||
| //            LogUtils.d(TAG, "onStartCommand enable service"); | ||||
| //            mSimpleOperateSignalCenterServiceBean.setIsEnable(true); | ||||
| //            SimpleOperateSignalCenterServiceBean.saveBean(this, mSimpleOperateSignalCenterServiceBean); | ||||
| //        } | ||||
|  | ||||
|         runMainThread(); | ||||
|  | ||||
|         //return super.onStartCommand(intent, flags, startId); | ||||
|         return mSimpleOperateSignalCenterServiceBean.isEnable() ? Service.START_STICKY: super.onStartCommand(intent, flags, startId); | ||||
|     } | ||||
|  | ||||
|     void runMainThread() { | ||||
|         mSimpleOperateSignalCenterServiceBean = SimpleOperateSignalCenterServiceBean.loadBean(this, SimpleOperateSignalCenterServiceBean.class); | ||||
|         if (mSimpleOperateSignalCenterServiceBean.isEnable() | ||||
|             && _MainThread == null) { | ||||
|             getMainThreadInstance().start(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onDestroy() { | ||||
|         super.onDestroy(); | ||||
|         LogUtils.d(TAG, "onDestroy"); | ||||
|         mSimpleOperateSignalCenterServiceBean = SimpleOperateSignalCenterServiceBean.loadBean(this, SimpleOperateSignalCenterServiceBean.class); | ||||
|         if (mSimpleOperateSignalCenterServiceBean.isEnable()) { | ||||
|             LogUtils.d(TAG, "mSimpleOperateSignalCenterServiceBean.isEnable()"); | ||||
| //            ISOSAPP iSOSAPP = (ISOSAPP)getApplication(); | ||||
| //            iSOSAPP.helpISOSService(getISOSServiceIntentWhichAskForHelp()); | ||||
|         }  | ||||
|         if (_MainThread != null) { | ||||
|             _MainThread.isExist = true; | ||||
|             _MainThread = null; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static void stopISOSService(Context context) { | ||||
|         LogUtils.d(TAG, "stopISOSService"); | ||||
|         SimpleOperateSignalCenterServiceBean bean = new SimpleOperateSignalCenterServiceBean(); | ||||
|         bean.setIsEnable(false); | ||||
|         SimpleOperateSignalCenterServiceBean.saveBean(context, bean); | ||||
|         context.stopService(new Intent(context, SimpleOperateSignalCenterService.class)); | ||||
|     } | ||||
|  | ||||
|     public static void startISOSService(Context context) { | ||||
|         LogUtils.d(TAG, "startISOSService"); | ||||
|         SimpleOperateSignalCenterServiceBean bean = new SimpleOperateSignalCenterServiceBean(); | ||||
|         bean.setIsEnable(true); | ||||
|         SimpleOperateSignalCenterServiceBean.saveBean(context, bean); | ||||
|         context.startService(new Intent(context, SimpleOperateSignalCenterService.class)); | ||||
|     } | ||||
|      | ||||
|     public String getMessage() { | ||||
|         return "Hello from SimpleOperateSignalCenterService"; | ||||
|     } | ||||
|  | ||||
|     static class MainThread extends Thread { | ||||
|         volatile boolean isExist = false; | ||||
|  | ||||
|         public void setIsExist(boolean isExist) { | ||||
|             this.isExist = isExist; | ||||
|         } | ||||
|  | ||||
|         public boolean isExist() { | ||||
|             return isExist; | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void run() { | ||||
|             super.run(); | ||||
|             while (!isExist) { | ||||
|                 LogUtils.d(TAG, "run"); | ||||
|                 try { | ||||
|                     sleep(1000); | ||||
|                 } catch (InterruptedException e) { | ||||
|                     LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,49 @@ | ||||
| package cc.winboll.studio.libappbase; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2024/07/19 14:30:57 | ||||
|  * @Describe UTF-8编码文件工具类 | ||||
|  */ | ||||
| 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.nio.charset.StandardCharsets; | ||||
|  | ||||
| public class UTF8FileUtils { | ||||
|  | ||||
|     public static final String TAG = "FileUtils"; | ||||
|  | ||||
|     // | ||||
|     // 把字符串写入文件,指定 UTF-8 编码 | ||||
|     // | ||||
|     public static void writeStringToFile(String szFilePath, String szContent) throws IOException { | ||||
|         File file = new File(szFilePath); | ||||
|         if (!file.getParentFile().exists()) { | ||||
|             file.getParentFile().mkdirs(); | ||||
|         } | ||||
|         FileOutputStream outputStream = new FileOutputStream(file); | ||||
|         OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8); | ||||
|         writer.write(szContent); | ||||
|         writer.close(); | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     // 读取文件到字符串,指定 UTF-8 编码 | ||||
|     // | ||||
|     public static String readStringFromFile(String szFilePath) throws IOException { | ||||
|         File file = new File(szFilePath); | ||||
|         FileInputStream inputStream = new FileInputStream(file); | ||||
|         InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8); | ||||
|         StringBuilder content = new StringBuilder(); | ||||
|         int character; | ||||
|         while ((character = reader.read()) != -1) { | ||||
|             content.append((char) character); | ||||
|         } | ||||
|         reader.close(); | ||||
|         return content.toString(); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,142 @@ | ||||
| package cc.winboll.studio.libappbase.bean; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@QQ.COM | ||||
|  * @Date 2025/01/20 14:19:02 | ||||
|  * @Describe 应用信息类 | ||||
|  */ | ||||
| import cc.winboll.studio.libappbase.R; | ||||
| import java.io.Serializable; | ||||
|  | ||||
| public class APPInfo implements Serializable { | ||||
|  | ||||
|     public static final String TAG = "APPInfo"; | ||||
|  | ||||
|     // 应用名称 | ||||
|     String appName; | ||||
|     // 应用图标 | ||||
|     int appIcon; | ||||
|     // 应用描述 | ||||
|     String appDescription; | ||||
|     // 应用Git仓库地址 | ||||
|     String appGitName; | ||||
|     // 应用Git仓库拥有者 | ||||
|     String appGitOwner; | ||||
|     // 应用Git仓库分支 | ||||
|     String appGitAPPBranch; | ||||
|     // 应用Git仓库子项目文件夹 | ||||
|     String appGitAPPSubProjectFolder; | ||||
|     // 应用主页 | ||||
|     String appHomePage; | ||||
|     // 应用包名称 | ||||
|     String appAPKName; | ||||
|     // 应用包存储文件夹名称 | ||||
|     String appAPKFolderName; | ||||
|  | ||||
|     public APPInfo(String appName, int appIcon, String appDescription, String appGitName, String appGitOwner, String appGitAPPBranch, String appGitAPPSubProjectFolder, String appHomePage, String appAPKName, String appAPKFolderName) { | ||||
|         this.appName = appName; | ||||
|         this.appIcon = appIcon; | ||||
|         this.appDescription = appDescription; | ||||
|         this.appGitName = appGitName; | ||||
|         this.appGitOwner = appGitOwner; | ||||
|         this.appGitAPPBranch = appGitAPPBranch; | ||||
|         this.appGitAPPSubProjectFolder = appGitAPPSubProjectFolder; | ||||
|         this.appHomePage = appHomePage; | ||||
|         this.appAPKName = appAPKName; | ||||
|         this.appAPKFolderName = appAPKFolderName; | ||||
|     } | ||||
|  | ||||
|     public APPInfo() { | ||||
|         this.appName = "WinBoll-APP"; | ||||
|         this.appIcon = R.drawable.ic_launcher; | ||||
|         this.appDescription = "WinBoll APP"; | ||||
|         this.appGitName = "APP"; | ||||
|         this.appGitOwner = "Studio"; | ||||
|         this.appGitAPPBranch = "app"; | ||||
|         this.appGitAPPSubProjectFolder = "app"; | ||||
|         this.appHomePage = "https://www.winboll.cc/studio/details.php?app=APP"; | ||||
|         this.appAPKName = "APP"; | ||||
|         this.appAPKFolderName = "APP"; | ||||
|     } | ||||
|  | ||||
|     public void setAppGitOwner(String appGitOwner) { | ||||
|         this.appGitOwner = appGitOwner; | ||||
|     } | ||||
|  | ||||
|     public String getAppGitOwner() { | ||||
|         return appGitOwner; | ||||
|     } | ||||
|  | ||||
|     public void setAppGitAPPBranch(String appGitAPPBranch) { | ||||
|         this.appGitAPPBranch = appGitAPPBranch; | ||||
|     } | ||||
|  | ||||
|     public String getAppGitAPPBranch() { | ||||
|         return appGitAPPBranch; | ||||
|     } | ||||
|  | ||||
|     public void setAppGitAPPSubProjectFolder(String appGitAPPSubProjectFolder) { | ||||
|         this.appGitAPPSubProjectFolder = appGitAPPSubProjectFolder; | ||||
|     } | ||||
|  | ||||
|     public String getAppGitAPPSubProjectFolder() { | ||||
|         return appGitAPPSubProjectFolder; | ||||
|     } | ||||
|  | ||||
|     public void setAppIcon(int appIcon) { | ||||
|         this.appIcon = appIcon; | ||||
|     } | ||||
|  | ||||
|     public int getAppIcon() { | ||||
|         return appIcon; | ||||
|     } | ||||
|  | ||||
|     public void setAppDescription(String appDescription) { | ||||
|         this.appDescription = appDescription; | ||||
|     } | ||||
|  | ||||
|     public String getAppDescription() { | ||||
|         return appDescription; | ||||
|     } | ||||
|  | ||||
|     public void setAppAPKFolderName(String appAPKFolderName) { | ||||
|         this.appAPKFolderName = appAPKFolderName; | ||||
|     } | ||||
|  | ||||
|     public String getAppAPKFolderName() { | ||||
|         return appAPKFolderName; | ||||
|     } | ||||
|  | ||||
|     public void setAppName(String appName) { | ||||
|         this.appName = appName; | ||||
|     } | ||||
|  | ||||
|     public String getAppName() { | ||||
|         return appName; | ||||
|     } | ||||
|  | ||||
|     public void setAppGitName(String appGitName) { | ||||
|         this.appGitName = appGitName; | ||||
|     } | ||||
|  | ||||
|     public String getAppGitName() { | ||||
|         return appGitName; | ||||
|     } | ||||
|  | ||||
|     public void setAppHomePage(String appHomePage) { | ||||
|         this.appHomePage = appHomePage; | ||||
|     } | ||||
|  | ||||
|     public String getAppHomePage() { | ||||
|         return appHomePage; | ||||
|     } | ||||
|  | ||||
|     public void setAppAPKName(String appAPKName) { | ||||
|         this.appAPKName = appAPKName; | ||||
|     } | ||||
|  | ||||
|     public String getAppAPKName() { | ||||
|         return appAPKName; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -0,0 +1,87 @@ | ||||
| package cc.winboll.studio.libappbase.bean; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@AliYun.Com | ||||
|  * @Date 2025/02/17 00:29:29 | ||||
|  * @Describe APPSOSReportBean | ||||
|  */ | ||||
| import android.util.JsonReader; | ||||
| import android.util.JsonWriter; | ||||
| import cc.winboll.studio.libappbase.BaseBean; | ||||
| import java.io.IOException; | ||||
| import java.io.Serializable; | ||||
|  | ||||
| public class APPSOSBean extends BaseBean { | ||||
|  | ||||
|     public static final String TAG = "APPSOSBean"; | ||||
|  | ||||
|     protected String sosPackage; | ||||
|     protected String sosClassName; | ||||
|  | ||||
|     public APPSOSBean() { | ||||
|         this.sosPackage = ""; | ||||
|         this.sosClassName = ""; | ||||
|     } | ||||
|  | ||||
|     public APPSOSBean(String sosPackage, String sosClassName) { | ||||
|         this.sosPackage = sosPackage; | ||||
|         this.sosClassName = sosClassName; | ||||
|     } | ||||
|  | ||||
|     public void setSosPackage(String sosPackage) { | ||||
|         this.sosPackage = sosPackage; | ||||
|     } | ||||
|  | ||||
|     public String getSosPackage() { | ||||
|         return sosPackage; | ||||
|     } | ||||
|  | ||||
|     public void setSosClassName(String sosClassName) { | ||||
|         this.sosClassName = sosClassName; | ||||
|     } | ||||
|  | ||||
|     public String getSosClassName() { | ||||
|         return sosClassName; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getName() { | ||||
|         return APPSOSBean.class.getName(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { | ||||
|         super.writeThisToJsonWriter(jsonWriter); | ||||
|         jsonWriter.name("sosPackage").value(getSosPackage()); | ||||
|         jsonWriter.name("sosClassName").value(getSosClassName()); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException { | ||||
|         if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else { | ||||
|             if (name.equals("sosPackage")) { | ||||
|                 setSosPackage(jsonReader.nextString()); | ||||
|             } else if (name.equals("sosClassName")) { | ||||
|                 setSosClassName(jsonReader.nextString()); | ||||
|             } else { | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException { | ||||
|         jsonReader.beginObject(); | ||||
|         while (jsonReader.hasNext()) { | ||||
|             String name = jsonReader.nextName(); | ||||
|             if (!initObjectsFromJsonReader(jsonReader, name)) { | ||||
|                 jsonReader.skipValue(); | ||||
|             } | ||||
|         } | ||||
|         // 结束 JSON 对象 | ||||
|         jsonReader.endObject(); | ||||
|         return this; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,66 @@ | ||||
| package cc.winboll.studio.libappbase.bean; | ||||
| import android.util.JsonReader; | ||||
| import android.util.JsonWriter; | ||||
| import cc.winboll.studio.libappbase.BaseBean; | ||||
| import java.io.IOException; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@AliYun.Com | ||||
|  * @Date 2025/02/13 04:27:42 | ||||
|  */ | ||||
| public class SimpleOperateSignalCenterServiceBean extends BaseBean { | ||||
|  | ||||
|     public static final String TAG = "SimpleOperateSignalCenterServiceBean"; | ||||
|  | ||||
|     boolean isEnable; | ||||
|      | ||||
|     public SimpleOperateSignalCenterServiceBean() { | ||||
|         this.isEnable = false; | ||||
|     } | ||||
|  | ||||
|     public void setIsEnable(boolean isEnable) { | ||||
|         this.isEnable = isEnable; | ||||
|     } | ||||
|  | ||||
|     public boolean isEnable() { | ||||
|         return isEnable; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getName() { | ||||
|         return SimpleOperateSignalCenterServiceBean.class.getName(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { | ||||
|         super.writeThisToJsonWriter(jsonWriter); | ||||
|         jsonWriter.name("isEnable").value(isEnable()); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException { | ||||
|         if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else { | ||||
|             if (name.equals("isEnable")) { | ||||
|                 setIsEnable(jsonReader.nextBoolean()); | ||||
|             } else { | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException { | ||||
|         jsonReader.beginObject(); | ||||
|         while (jsonReader.hasNext()) { | ||||
|             String name = jsonReader.nextName(); | ||||
|             if (!initObjectsFromJsonReader(jsonReader, name)) { | ||||
|                 jsonReader.skipValue(); | ||||
|             } | ||||
|         } | ||||
|         // 结束 JSON 对象 | ||||
|         jsonReader.endObject(); | ||||
|         return this; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,67 @@ | ||||
| package cc.winboll.studio.libappbase.bean; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@AliYun.Com | ||||
|  * @Date 2025/02/19 13:34:52 | ||||
|  * @Describe TestServiceBean | ||||
|  */ | ||||
| import android.util.JsonReader; | ||||
| import android.util.JsonWriter; | ||||
| import cc.winboll.studio.libappbase.BaseBean; | ||||
| import java.io.IOException; | ||||
|  | ||||
| public class TestServiceBean extends BaseBean { | ||||
|  | ||||
|     public static final String TAG = "TestServiceBean"; | ||||
|  | ||||
|     boolean isEnable; | ||||
|  | ||||
|     public TestServiceBean() { | ||||
|         this.isEnable = false; | ||||
|     } | ||||
|  | ||||
|     public void setIsEnable(boolean isEnable) { | ||||
|         this.isEnable = isEnable; | ||||
|     } | ||||
|  | ||||
|     public boolean isEnable() { | ||||
|         return isEnable; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getName() { | ||||
|         return TestServiceBean.class.getName(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { | ||||
|         super.writeThisToJsonWriter(jsonWriter); | ||||
|         jsonWriter.name("isEnable").value(isEnable()); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException { | ||||
|         if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else { | ||||
|             if (name.equals("isEnable")) { | ||||
|                 setIsEnable(jsonReader.nextBoolean()); | ||||
|             } else { | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException { | ||||
|         jsonReader.beginObject(); | ||||
|         while (jsonReader.hasNext()) { | ||||
|             String name = jsonReader.nextName(); | ||||
|             if (!initObjectsFromJsonReader(jsonReader, name)) { | ||||
|                 jsonReader.skipValue(); | ||||
|             } | ||||
|         } | ||||
|         // 结束 JSON 对象 | ||||
|         jsonReader.endObject(); | ||||
|         return this; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,29 @@ | ||||
| package cc.winboll.studio.libappbase.receiver; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@AliYun.Com | ||||
|  * @Date 2025/02/13 21:19:09 | ||||
|  * @Describe MyBroadcastReceiver | ||||
|  */ | ||||
| import android.content.BroadcastReceiver; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| import cc.winboll.studio.libappbase.R; | ||||
|  | ||||
| public class MyBroadcastReceiver extends BroadcastReceiver { | ||||
|      | ||||
|     public static final String TAG = "MyBroadcastReceiver"; | ||||
|      | ||||
|     @Override | ||||
|     public void onReceive(Context context, Intent intent) { | ||||
|         if (context.getString(R.string.action_sos).equals(intent.getAction())) { | ||||
|             String message = intent.getStringExtra("message"); | ||||
|             String sosPackage = intent.getStringExtra("sosPackage"); | ||||
|              | ||||
|             // 处理接收到的广播消息 | ||||
|             LogUtils.d(TAG, String.format("MyBroadcastReceiver action %s \n%s\n%s", intent.getAction(), sosPackage, message)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -0,0 +1,149 @@ | ||||
| package cc.winboll.studio.libappbase.services; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@AliYun.Com | ||||
|  * @Date 2025/02/15 20:48:36 | ||||
|  * @Describe TestService | ||||
|  */ | ||||
| import android.app.Service; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.os.Binder; | ||||
| import android.os.IBinder; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| import cc.winboll.studio.libappbase.SOS; | ||||
| import cc.winboll.studio.libappbase.bean.APPSOSBean; | ||||
| import cc.winboll.studio.libappbase.bean.TestServiceBean; | ||||
|  | ||||
| public class TestService extends Service { | ||||
|  | ||||
|     public static final String TAG = "TestService"; | ||||
|  | ||||
|     volatile static TestThread _TestThread; | ||||
|  | ||||
|     volatile static boolean _IsRunning; | ||||
|  | ||||
|     public synchronized static void setIsRunning(boolean isRunning) { | ||||
|         _IsRunning = isRunning; | ||||
|     } | ||||
|  | ||||
|     public static boolean isRunning() { | ||||
|         return _IsRunning; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public IBinder onBind(Intent intent) { | ||||
|         return new MyBinder(); | ||||
|     } | ||||
|  | ||||
|     public class MyBinder extends Binder { | ||||
|         public TestService getService() { | ||||
|             return TestService.this; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onCreate() { | ||||
|         super.onCreate(); | ||||
|         LogUtils.d(TAG, "onCreate()"); | ||||
|  | ||||
|  | ||||
|         run(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int onStartCommand(Intent intent, int flags, int startId) { | ||||
|         LogUtils.d(TAG, "onStartCommand(...)"); | ||||
|         TestServiceBean bean = TestServiceBean.loadBean(this, TestServiceBean.class); | ||||
|         if (bean == null) { | ||||
|             bean = new TestServiceBean(); | ||||
|         } | ||||
|         if (intent.getAction() != null && intent.getAction().equals(SOS.ACTION_SERVICE_ENABLE)) { | ||||
|             bean.setIsEnable(true); | ||||
|             TestServiceBean.saveBean(this, bean); | ||||
|             run(); | ||||
|         } else if (intent.getAction() != null && intent.getAction().equals(SOS.ACTION_SERVICE_DISABLE)) { | ||||
|             bean.setIsEnable(false); | ||||
|             TestServiceBean.saveBean(this, bean); | ||||
|         } | ||||
|         LogUtils.d(TAG, String.format("TestServiceBean.saveBean setIsEnable %s", bean.isEnable())); | ||||
|         return (bean.isEnable()) ? START_STICKY : super.onStartCommand(intent, flags, startId); | ||||
|         //return super.onStartCommand(intent, flags, startId); | ||||
|     } | ||||
|  | ||||
|     void run() { | ||||
|         LogUtils.d(TAG, "run()"); | ||||
|         TestServiceBean bean = TestServiceBean.loadBean(this, TestServiceBean.class); | ||||
|         if (bean == null) { | ||||
|             bean = new TestServiceBean(); | ||||
|             TestServiceBean.saveBean(this, bean); | ||||
|         } | ||||
|         if (bean.isEnable()) { | ||||
|             LogUtils.d(TAG, "run() bean.isEnable()"); | ||||
|             TestThread.getInstance(this).start(); | ||||
|             LogUtils.d(TAG, "_TestThread.start()"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     @Override | ||||
|     public void onDestroy() { | ||||
|         super.onDestroy(); | ||||
|         LogUtils.d(TAG, "onDestroy()"); | ||||
|         TestThread.getInstance(this).setIsExit(true); | ||||
|          | ||||
|         _IsRunning = false; | ||||
|     } | ||||
|  | ||||
|     static class TestThread extends Thread { | ||||
|  | ||||
|         volatile static TestThread _TestThread; | ||||
|         Context mContext; | ||||
|         volatile boolean isStarted = false; | ||||
|         volatile boolean isExit = false; | ||||
|  | ||||
|         TestThread(Context context) { | ||||
|             super(); | ||||
|             mContext = context; | ||||
|         } | ||||
|  | ||||
|         public static synchronized TestThread getInstance(Context context) { | ||||
|             if (_TestThread != null) { | ||||
|                 _TestThread.setIsExit(true); | ||||
|             } | ||||
|             _TestThread = new TestThread(context); | ||||
|  | ||||
|             return _TestThread; | ||||
|         } | ||||
|  | ||||
|         public synchronized void setIsExit(boolean isExit) { | ||||
|             this.isExit = isExit; | ||||
|         } | ||||
|  | ||||
|         public boolean isExit() { | ||||
|             return isExit; | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void run() { | ||||
|             if (isStarted == false) { | ||||
|                 isStarted = true; | ||||
|                 super.run(); | ||||
|                 LogUtils.d(TAG, "run() start"); | ||||
|                 SOS.bindToAPPService(mContext, new APPSOSBean(mContext.getPackageName(), TestService.class.getName())); | ||||
|  | ||||
|                 while (!isExit()) { | ||||
|                     LogUtils.d(TAG, "run()"); | ||||
|  | ||||
|                     try { | ||||
|                         Thread.sleep(1000); | ||||
|                     } catch (InterruptedException e) { | ||||
|                         LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 LogUtils.d(TAG, "run() exit"); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,48 @@ | ||||
| package cc.winboll.studio.libappbase.utils; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@AliYun.Com | ||||
|  * @Date 2025/02/17 19:38:20 | ||||
|  * @Describe 服务工具集 | ||||
|  */ | ||||
| import android.app.ActivityManager; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.os.Build; | ||||
| import android.util.Log; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| import java.util.List; | ||||
|  | ||||
| public class ServiceUtils { | ||||
|  | ||||
|     public static final String TAG = "ServiceUtils"; | ||||
|  | ||||
|     /** | ||||
|      * 检查指定服务是否正在运行 | ||||
|      * @param context 上下文 | ||||
|      * @param serviceClass 服务类 | ||||
|      * @return true 如果服务正在运行,否则返回 false | ||||
|      */ | ||||
|     public static boolean isServiceRunning(Context context, String serviceClassName) { | ||||
|         ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); | ||||
|         if (activityManager == null) { | ||||
|             return false; | ||||
|         } | ||||
|         List<ActivityManager.RunningServiceInfo> runningServices; | ||||
| //        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { | ||||
| //            Intent intent = new Intent(context, serviceClass); | ||||
| //            runningServices = activityManager.getRunningServices(100, intent); | ||||
| //        } else { | ||||
|         runningServices = activityManager.getRunningServices(100); | ||||
|         //} | ||||
|         for (ActivityManager.RunningServiceInfo serviceInfo : runningServices) { | ||||
|             if (serviceClassName.equals(serviceInfo.service.getClassName())) { | ||||
|                 LogUtils.d(TAG, "Service is running: " + serviceInfo.service.getClassName()); | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|         LogUtils.d(TAG, "Service is not running: " + serviceClassName); | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -0,0 +1,63 @@ | ||||
| package cc.winboll.studio.libappbase.widgets; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@AliYun.Com | ||||
|  * @Date 2025/02/17 20:32:12 | ||||
|  */ | ||||
| import android.app.PendingIntent; | ||||
| import android.appwidget.AppWidgetManager; | ||||
| import android.appwidget.AppWidgetProvider; | ||||
| import android.content.ComponentName; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.widget.RemoteViews; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| import cc.winboll.studio.libappbase.R; | ||||
| import cc.winboll.studio.libappbase.utils.ServiceUtils; | ||||
| import com.hjq.toast.ToastUtils; | ||||
| import android.content.ServiceConnection; | ||||
| import android.os.IBinder; | ||||
| import cc.winboll.studio.libappbase.services.TestService; | ||||
|  | ||||
| public class StatusWidget extends AppWidgetProvider { | ||||
|  | ||||
|     public static final String TAG = "StatusWidget"; | ||||
|  | ||||
|     public static final String ACTION_STATUS_UPDATE = "cc.winboll.studio.libappbase.widgets.APPWidget.ACTION_STATUS_UPDATE"; | ||||
|  | ||||
|     @Override | ||||
|     public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { | ||||
|         for (int appWidgetId : appWidgetIds) { | ||||
|             updateAppWidget(context, appWidgetManager, appWidgetId); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onReceive(Context context, Intent intent) { | ||||
|         super.onReceive(context, intent); | ||||
|         if (intent.getAction().equals(ACTION_STATUS_UPDATE)) { | ||||
|             AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); | ||||
|             int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, StatusWidget.class)); | ||||
|             for (int appWidgetId : appWidgetIds) { | ||||
|                 updateAppWidget(context, appWidgetManager, appWidgetId); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) { | ||||
|         RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_status); | ||||
|         //设置按钮点击事件 | ||||
|         Intent intentAppButton = new Intent(context, StatusWidgetClickListener.class); | ||||
|         intentAppButton.setAction(StatusWidgetClickListener.ACTION_IVAPP); | ||||
|         PendingIntent pendingIntentAppButton = PendingIntent.getBroadcast(context, 0, intentAppButton, PendingIntent.FLAG_UPDATE_CURRENT); | ||||
|         views.setOnClickPendingIntent(R.id.ivapp, pendingIntentAppButton); | ||||
|  | ||||
|         boolean isActive = ServiceUtils.isServiceRunning(context, TestService.class.getName()); | ||||
|         if (isActive) { | ||||
|             views.setImageViewResource(R.id.ivapp, cc.winboll.studio.libappbase.R.drawable.ic_launcher); | ||||
|         } else { | ||||
|             views.setImageViewResource(R.id.ivapp, cc.winboll.studio.libappbase.R.drawable.ic_launcher_disable); | ||||
|         } | ||||
|         appWidgetManager.updateAppWidget(appWidgetId, views); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,33 @@ | ||||
| package cc.winboll.studio.libappbase.widgets; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen@AliYun.Com | ||||
|  * @Date 2025/02/17 20:33:53 | ||||
|  * @Describe APPWidgetClickListener | ||||
|  */ | ||||
| import android.content.BroadcastReceiver; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| import com.hjq.toast.ToastUtils; | ||||
|  | ||||
| public class StatusWidgetClickListener extends BroadcastReceiver { | ||||
|      | ||||
|     public static final String TAG = "APPWidgetClickListener"; | ||||
|  | ||||
|     public static final String ACTION_IVAPP = "cc.winboll.studio.libappbase.widgets.StatusWidgetClickListener.ACTION_IVAPP"; | ||||
|  | ||||
|     @Override | ||||
|     public void onReceive(Context context, Intent intent) { | ||||
|         String action = intent.getAction(); | ||||
|         if (action == null) { | ||||
|             LogUtils.d(TAG, String.format("action %s", action)); | ||||
|             return; | ||||
|         } | ||||
|         if (action.equals(ACTION_IVAPP)) { | ||||
|             ToastUtils.show("ACTION_LAUNCHER"); | ||||
|         } else { | ||||
|             LogUtils.d(TAG, String.format("action %s", action)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 9.2 KiB | 
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 5.1 KiB | 
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 14 KiB | 
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 19 KiB | 
							
								
								
									
										41
									
								
								libappbase/src/main/res/drawable/bg_shadow.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								libappbase/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> | ||||
							
								
								
									
										13
									
								
								libappbase/src/main/res/drawable/ic_launcher.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								libappbase/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" | ||||
|     android:layout_width="24dp" | ||||
| 	android:layout_height="24dp"> | ||||
|     <item android:drawable="@drawable/ic_launcher_background"/>  | ||||
|     <item  | ||||
|         android:left="0dp"  | ||||
|         android:top="0dp"  | ||||
|         android:right="0dp"  | ||||
|         android:bottom="0dp" | ||||
|         android:drawable="@drawable/ic_launcher_foreground"/> | ||||
| </layer-list> | ||||
							
								
								
									
										170
									
								
								libappbase/src/main/res/drawable/ic_launcher_background.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								libappbase/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="#FF005C12" | ||||
|         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> | ||||
							
								
								
									
										13
									
								
								libappbase/src/main/res/drawable/ic_launcher_disable.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								libappbase/src/main/res/drawable/ic_launcher_disable.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" | ||||
|     android:layout_width="24dp" | ||||
| 	android:layout_height="24dp"> | ||||
|     <item android:drawable="@drawable/ic_launcher_background"/>  | ||||
|     <item  | ||||
|         android:left="0dp"  | ||||
|         android:top="0dp"  | ||||
|         android:right="0dp"  | ||||
|         android:bottom="0dp" | ||||
|         android:drawable="@drawable/ic_launcher_foreground_disable"/> | ||||
| </layer-list> | ||||
							
								
								
									
										10
									
								
								libappbase/src/main/res/drawable/ic_launcher_foreground.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								libappbase/src/main/res/drawable/ic_launcher_foreground.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -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> | ||||
| @@ -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="#FF808080" | ||||
|         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> | ||||
							
								
								
									
										8
									
								
								libappbase/src/main/res/drawable/view_border.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								libappbase/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> | ||||
							
								
								
									
										15
									
								
								libappbase/src/main/res/layout/activity_globalcrash.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								libappbase/src/main/res/layout/activity_globalcrash.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <LinearLayout | ||||
| 	xmlns:android="http://schemas.android.com/apk/res/android" | ||||
| 	xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
|     android:orientation="vertical" | ||||
| 	android:layout_width="match_parent" | ||||
| 	android:layout_height="match_parent"> | ||||
|  | ||||
| 	<cc.winboll.studio.libappbase.GlobalCrashReportView | ||||
| 		android:layout_width="match_parent" | ||||
| 		android:layout_height="match_parent" | ||||
| 		android:id="@+id/activityglobalcrashGlobalCrashReportView1"/> | ||||
|  | ||||
| </LinearLayout> | ||||
|  | ||||
							
								
								
									
										16
									
								
								libappbase/src/main/res/layout/activity_log.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								libappbase/src/main/res/layout/activity_log.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <LinearLayout | ||||
|     xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
|     android:orientation="vertical" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="match_parent"> | ||||
|  | ||||
|     <cc.winboll.studio.libappbase.LogView | ||||
|         android:orientation="horizontal" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="0dp" | ||||
|         android:layout_weight="1.0" | ||||
|         android:id="@+id/logview"/> | ||||
|  | ||||
| </LinearLayout> | ||||
| @@ -1,11 +0,0 @@ | ||||
| <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> | ||||
							
								
								
									
										35
									
								
								libappbase/src/main/res/layout/view_globalcrashreport.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								libappbase/src/main/res/layout/view_globalcrashreport.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| <?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/viewglobalcrashreportLinearLayout1"> | ||||
|  | ||||
| 	<androidx.appcompat.widget.Toolbar | ||||
| 		android:layout_width="match_parent" | ||||
| 		android:layout_height="wrap_content" | ||||
| 		android:id="@+id/viewglobalcrashreportToolbar1"/> | ||||
|  | ||||
| 	<ScrollView | ||||
| 		android:layout_width="match_parent" | ||||
| 		android:layout_height="0dp" | ||||
| 		android:layout_weight="1.0"> | ||||
|  | ||||
| 		<HorizontalScrollView | ||||
| 			android:layout_width="match_parent" | ||||
| 			android:layout_height="match_parent"> | ||||
|  | ||||
| 			<TextView | ||||
| 				android:layout_width="match_parent" | ||||
| 				android:layout_height="wrap_content" | ||||
| 				android:background="#FFFFFFFF" | ||||
| 				android:id="@+id/viewglobalcrashreportTextView1"/> | ||||
|  | ||||
| 		</HorizontalScrollView> | ||||
|  | ||||
| 	</ScrollView> | ||||
|  | ||||
| </LinearLayout> | ||||
|  | ||||
							
								
								
									
										121
									
								
								libappbase/src/main/res/layout/view_log.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								libappbase/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
									
								
								libappbase/src/main/res/layout/view_logtag.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								libappbase/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="0dp" | ||||
|     android:layout_marginRight="5dp"> | ||||
|  | ||||
|     <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:layout_marginLeft="5dp" | ||||
|             android:id="@+id/viewlogtagTextView1"/> | ||||
|  | ||||
|         <CheckBox | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="24dp" | ||||
|             android:id="@+id/viewlogtagCheckBox1"/> | ||||
|  | ||||
|     </LinearLayout> | ||||
|  | ||||
| </androidx.cardview.widget.CardView> | ||||
							
								
								
									
										15
									
								
								libappbase/src/main/res/layout/widget_status.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								libappbase/src/main/res/layout/widget_status.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <LinearLayout | ||||
| 	xmlns:android="http://schemas.android.com/apk/res/android" | ||||
| 	xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
| 	android:orientation="vertical" | ||||
| 	android:layout_width="40dp" | ||||
| 	android:layout_height="40dp"> | ||||
|  | ||||
| 	<ImageView | ||||
| 		android:layout_width="40dp" | ||||
| 		android:layout_height="40dp" | ||||
| 		android:id="@+id/ivapp"/> | ||||
|  | ||||
| </LinearLayout> | ||||
|  | ||||
| @@ -1,5 +0,0 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <style name="AppTheme" parent="@android:style/Theme.Material.Light"> | ||||
| 	</style> | ||||
| </resources> | ||||
							
								
								
									
										11
									
								
								libappbase/src/main/res/values/array.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								libappbase/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> | ||||
							
								
								
									
										15
									
								
								libappbase/src/main/res/values/attrs.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								libappbase/src/main/res/values/attrs.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|      | ||||
|     <attr name="attrColorPrimary" format="color" /> | ||||
|  | ||||
|     <attr name="themeGlobalCrashActivity" format="reference"/> | ||||
|  | ||||
|     <declare-styleable name="GlobalCrashActivity"> | ||||
|         <attr name="colorTittle" format="color" /> | ||||
|         <attr name="colorTittleBackgound" format="color" /> | ||||
|         <attr name="colorText" format="color" /> | ||||
|         <attr name="colorTextBackgound" format="color" /> | ||||
|     </declare-styleable> | ||||
|  | ||||
| </resources> | ||||
							
								
								
									
										7
									
								
								libappbase/src/main/res/values/colors.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								libappbase/src/main/res/values/colors.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <color name="colorPrimary">#FF00B322</color> | ||||
|     <color name="colorPrimaryDark">#FF005C12</color> | ||||
|     <color name="colorAccent">#FF8DFFA2</color> | ||||
|     <color name="colorText">#FFFFFB8D</color> | ||||
| </resources> | ||||
| @@ -3,5 +3,5 @@ | ||||
|  | ||||
|     <string name="lib_name">libappbase</string> | ||||
|     <string name="hello_world">Hello world!</string> | ||||
|  | ||||
|     <string name="action_sos">cc.winboll.studio.libappbase.action.SOS</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -1,5 +1,15 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <style name="AppTheme" parent="@android:style/Theme.Holo.Light"> | ||||
|  | ||||
|     <style name="APPBaseTheme" parent="Theme.AppCompat.Light.NoActionBar"> | ||||
|         <item name="themeGlobalCrashActivity">@style/GlobalCrashActivityTheme</item> | ||||
|     </style> | ||||
|  | ||||
|     <style name="GlobalCrashActivityTheme" parent="@android:style/Theme.DeviceDefault.Light.NoActionBar"> | ||||
|         <item name="colorTittle">#FFFFF600</item> | ||||
|         <item name="colorTittleBackgound">#FF00B322</item> | ||||
|         <item name="colorText">#FF00B322</item> | ||||
|         <item name="colorTextBackgound">#FF000000</item> | ||||
| 	</style> | ||||
| </resources> | ||||
|  | ||||
| </resources> | ||||
|   | ||||
| @@ -0,0 +1,8 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:minWidth="40dp" | ||||
|     android:minHeight="40dp" | ||||
|     android:updatePeriodMillis="1000" | ||||
|     android:initialLayout="@layout/widget_status" | ||||
|     android:resizeMode="none"> | ||||
| </appwidget-provider> | ||||
| @@ -1,10 +1,6 @@ | ||||
| <?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> | ||||
|   | ||||
							
								
								
									
										1
									
								
								mymessagemanager/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								mymessagemanager/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| /build | ||||
							
								
								
									
										45
									
								
								mymessagemanager/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								mymessagemanager/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| # MyMessageManager | ||||
|  | ||||
| #### 介绍 | ||||
| 用正则表达式方法自定义短信过滤和语音播报的短信应用。 | ||||
|  | ||||
| #### 软件架构 | ||||
| 软件架构说明 | ||||
|  | ||||
|  | ||||
| #### 安装教程 | ||||
|  | ||||
| 1.  xxxx | ||||
| 2.  xxxx | ||||
| 3.  xxxx | ||||
|  | ||||
| #### 使用说明 | ||||
|  | ||||
| 1.  xxxx | ||||
| 2.  xxxx | ||||
| 3.  xxxx | ||||
|  | ||||
| #### 参与贡献 | ||||
|  | ||||
| 1.  Fork 本仓库 | ||||
| 2.  新建 Feat_xxx 分支 | ||||
| 3.  提交代码:ZhanGSKen(ZhanGSKen@QQ.COM) | ||||
| 4.  新建 Pull Request | ||||
|  | ||||
|  | ||||
| #### 特技 | ||||
|  | ||||
| 1.  使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md | ||||
| 2.  Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) | ||||
| 3.  你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 | ||||
| 4.  [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 | ||||
| 5.  Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) | ||||
| 6.  Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) | ||||
|  | ||||
| #### 参考文档 | ||||
|  | ||||
| 使用GitHub Actions实现Android自动打包apk | ||||
| https://blog.csdn.net/ZZL23333/article/details/115798615?app_version=6.0.0&code=app_1562916241&csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22115798615%22%2C%22source%22%3A%22weixin_38986226%22%7D&uLinkId=usr1mkqgl919blen&utm_source=app | ||||
|  | ||||
| Android中assets的使用(用于读取内容) | ||||
| https://blog.csdn.net/qq_27664947/article/details/103924058?app_version=6.0.0&code=app_1562916241&csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22103924058%22%2C%22source%22%3A%22weixin_38986226%22%7D&uLinkId=usr1mkqgl919blen&utm_source=app | ||||
							
								
								
									
										1
									
								
								mymessagemanager/app_update_description.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								mymessagemanager/app_update_description.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
|  | ||||
							
								
								
									
										69
									
								
								mymessagemanager/build.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								mymessagemanager/build.gradle
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| apply plugin: 'com.android.application' | ||||
| apply from: '../.winboll/winboll_app_build.gradle' | ||||
| apply from: '../.winboll/winboll_lint_build.gradle' | ||||
|  | ||||
| def genVersionName(def versionName){ | ||||
|     // 检查编译标志位配置 | ||||
|     assert (winbollBuildProps['stageCount'] != null) | ||||
|     assert (winbollBuildProps['baseVersion'] != null) | ||||
|     // 保存基础版本号 | ||||
|     winbollBuildProps.setProperty("baseVersion", "${versionName}"); | ||||
|     //保存编译标志配置 | ||||
|     FileOutputStream fos = new FileOutputStream(winbollBuildPropsFile) | ||||
|     winbollBuildProps.store(fos, "${winbollBuildPropsDesc}"); | ||||
|     fos.close(); | ||||
|      | ||||
|     // 返回编译版本号 | ||||
|     return "${versionName}." + winbollBuildProps['stageCount'] | ||||
| } | ||||
|  | ||||
| android { | ||||
|     compileSdkVersion 30 | ||||
|     buildToolsVersion "30.0.3" | ||||
|  | ||||
|     defaultConfig { | ||||
|         applicationId "cc.winboll.studio.mymessagemanager" | ||||
|         minSdkVersion 26 | ||||
|         targetSdkVersion 29 | ||||
|         versionCode 8 | ||||
|         // versionName 更新后需要手动设置  | ||||
|         // .winboll/winbollBuildProps.properties 文件的 stageCount=0 | ||||
|         // Gradle编译环境下合起来的 versionName 就是 "${versionName}.0" | ||||
|         versionName "4.1"  | ||||
|         if(true) { | ||||
|             versionName = genVersionName("${versionName}") | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     buildTypes { | ||||
|         release { | ||||
|             minifyEnabled false | ||||
|             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| dependencies { | ||||
| 	api 'cc.winboll.studio:winboll-shared:1.6.4' | ||||
|     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' | ||||
|      | ||||
|     // 权限请求框架:https://github.com/getActivity/XXPermissions | ||||
|     api 'com.github.getActivity:XXPermissions:18.63' | ||||
|     api 'com.baoyz.pullrefreshlayout:library:1.2.0' | ||||
|      | ||||
|     api 'androidx.appcompat:appcompat:1.0.0' | ||||
|     api 'androidx.fragment:fragment:1.0.0' | ||||
|     api 'com.google.android.material:material:1.0.0' | ||||
|      | ||||
|     api 'cc.winboll.studio:libaes:7.6.0' | ||||
|      | ||||
|     api fileTree(dir: 'libs', include: ['*.jar']) | ||||
| } | ||||
							
								
								
									
										8
									
								
								mymessagemanager/build.properties
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								mymessagemanager/build.properties
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| #Created by .winboll/winboll_app_build.gradle | ||||
| #Tue Feb 25 10:52:41 GMT 2025 | ||||
| stageCount=14 | ||||
| libraryProject= | ||||
| baseVersion=4.1 | ||||
| publishVersion=4.1.13 | ||||
| buildCount=5 | ||||
| baseBetaVersion=4.1.14 | ||||
							
								
								
									
										17
									
								
								mymessagemanager/proguard-rules.pro
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								mymessagemanager/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 *; | ||||
| #} | ||||
							
								
								
									
										23
									
								
								mymessagemanager/src/beta/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								mymessagemanager/src/beta/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:tools="http://schemas.android.com/tools" > | ||||
|  | ||||
|     <application> | ||||
|  | ||||
|         <!-- Put flavor specific code here --> | ||||
|         <provider | ||||
|             android:name="androidx.core.content.FileProvider" | ||||
|             android:authorities="cc.winboll.studio.mymessagemanager.beta.fileprovider" | ||||
|             android:exported="false" | ||||
|             android:grantUriPermissions="true"> | ||||
|  | ||||
|             <meta-data | ||||
|                 android:name="android.support.FILE_PROVIDER_PATHS" | ||||
|                 android:resource="@xml/file_provider"/> | ||||
|  | ||||
|         </provider> | ||||
|          | ||||
|     </application> | ||||
|  | ||||
| </manifest> | ||||
|  | ||||
							
								
								
									
										6
									
								
								mymessagemanager/src/beta/res/values-zh/strings.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								mymessagemanager/src/beta/res/values-zh/strings.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|  | ||||
|     <string name="app_name">我的短信管家 ☆</string> | ||||
|      | ||||
| </resources> | ||||
							
								
								
									
										6
									
								
								mymessagemanager/src/beta/res/values/strings.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								mymessagemanager/src/beta/res/values/strings.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|  | ||||
|     <string name="app_name">My Message Manager +</string> | ||||
|  | ||||
| </resources> | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	 ZhanGSKen
					ZhanGSKen