appbase 合并 winboll

This commit is contained in:
ZhanGSKen
2025-09-28 14:53:17 +08:00
89 changed files with 4817 additions and 4 deletions

View File

@@ -25,7 +25,14 @@ buildscript {
//mavenLocal()
// WinBoLL.CC 紧急备用 Maven 仓库
maven { url 'https://spare-maven.winboll.cc/repository/' }
//maven { url 'https://spare-maven.winboll.cc/repository/' }
maven {
url 'sftp://studio@winboll.cc:2222//winboll/spare-maven/repository/'
credentials {
username 'studio'
password 'winbollstudio'
}
}
}
dependencies {
classpath 'com.android.tools.build:gradle:7.2.1' // 对应 compileSdkVersion 32
@@ -40,7 +47,6 @@ allprojects {
// 设置本地Maven仓库路径
url 'file:///sdcard/.m2/repository/'
}
// Nexus Maven 库地址
// "WinBoLL Release"
maven { url "https://nexus.winboll.cc/repository/maven-public/" }
@@ -61,7 +67,14 @@ allprojects {
//mavenLocal()
// WinBoLL.CC 紧急备用 Maven 仓库
maven { url 'https://spare-maven.winboll.cc/repository/' }
//maven { url 'https://spare-maven.winboll.cc/repository/' }
maven {
url 'sftp://studio@winboll.cc:2222//winboll/spare-maven/repository/'
credentials {
username 'studio'
password 'winbollstudio'
}
}
}
ext {
// 定义全局变量,常用于版本管理

View File

@@ -50,6 +50,7 @@
//include ':androidxdemo'
//rootProject.name = "androidxdemo"
<<<<<<< HEAD
// Ollama 项目编译设置
//include ':ollama'
//rootProject.name = "ollama"
@@ -64,4 +65,10 @@
// WebPageSources 项目编译设置
//include ':webpagesources'
//rootProject.name = "webpagesources"
//rootProject.name = "webpagesources"
=======
// WinBoLL 项目编译设置
//include ':winboll'
//rootProject.name = "winboll"
>>>>>>> refs/heads/winboll

73
winboll/build.gradle Normal file
View File

@@ -0,0 +1,73 @@
apply plugin: 'com.android.application'
apply from: '../.winboll/winboll_app_build.gradle'
apply from: '../.winboll/winboll_lint_build.gradle'
def genVersionName(def versionName){
// 检查编译标志位配置
assert (winbollBuildProps['stageCount'] != null)
assert (winbollBuildProps['baseVersion'] != null)
// 保存基础版本号
winbollBuildProps.setProperty("baseVersion", "${versionName}");
//保存编译标志配置
FileOutputStream fos = new FileOutputStream(winbollBuildPropsFile)
winbollBuildProps.store(fos, "${winbollBuildPropsDesc}");
fos.close();
// 返回编译版本号
return "${versionName}." + winbollBuildProps['stageCount']
}
android {
compileSdkVersion 32
buildToolsVersion "32.0.0"
defaultConfig {
applicationId "cc.winboll.studio.winboll"
minSdkVersion 24
targetSdkVersion 30
versionCode 1
// versionName 更新后需要手动设置
// .winboll/winbollBuildProps.properties 文件的 stageCount=0
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
versionName "15.0"
if(true) {
versionName = genVersionName("${versionName}")
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
api fileTree(dir: 'libs', include: ['*.jar'])
// SSH
api 'com.jcraft:jsch:0.1.55'
// Html 解析
api 'org.jsoup:jsoup:1.13.1'
// 二维码类库
api 'com.google.zxing:core:3.4.1'
api 'com.journeyapps:zxing-android-embedded:3.6.0'
// 应用介绍页类库
api 'io.github.medyo:android-about-page:2.0.0'
// 吐司类库
api 'com.github.getActivity:ToastUtils:10.5'
// 网络连接类库
api 'com.squareup.okhttp3:okhttp:4.4.1'
// AndroidX 类库
api 'androidx.appcompat:appcompat:1.1.0'
api 'com.google.android.material:material:1.4.0'
//api 'androidx.viewpager:viewpager:1.0.0'
//api 'androidx.vectordrawable:vectordrawable:1.1.0'
//api 'androidx.vectordrawable:vectordrawable-animated:1.1.0'
//api 'androidx.fragment:fragment:1.1.0'
api 'cc.winboll.studio:libaes:15.8.0'
api 'cc.winboll.studio:libapputils:15.8.2'
api 'cc.winboll.studio:libappbase:15.8.2'
}

8
winboll/build.properties Normal file
View File

@@ -0,0 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Thu May 22 13:37:34 HKT 2025
stageCount=3
libraryProject=
baseVersion=15.0
publishVersion=15.0.2
buildCount=0
baseBetaVersion=15.0.3

21
winboll/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" >
<application
tools:replace="android:icon"
android:icon="@drawable/ic_launcher_beta">
<!-- Put flavor specific code here -->
</application>
</manifest>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">WinBoLL+</string>
</resources>

View File

@@ -0,0 +1,222 @@
<?xml version='1.0' encoding='utf-8'?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="cc.winboll.studio.winboll">
<!-- 拥有完全的网络访问权限 -->
<uses-permission android:name="android.permission.INTERNET"/>
<!-- 发送持久广播 -->
<uses-permission android:name="android.permission.BROADCAST_STICKY"/>
<!-- 对正在运行的应用重新排序 -->
<uses-permission android:name="android.permission.REORDER_TASKS"/>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher_stage"
android:roundIcon="@drawable/ic_launcher_stage"
android:label="@string/app_name"
android:theme="@style/MyAppTheme"
android:resizeableActivity="true"
android:name=".App">
<activity
android:name=".MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name=".WinBoLLUnitTestActivity.java"
android:label="@string/app_name"
android:exported="true"
android:resizeableActivity="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity
android:name=".activities.NewActivity"
android:label="NewActivity"
android:exported="true"
android:resizeableActivity="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"/>
<activity
android:name=".activities.New2Activity"
android:label="New2Activity"
android:exported="true"
android:resizeableActivity="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"/>
<service
android:name=".MyTileService"
android:exported="true"
android:label="@string/tileservice_name"
android:icon="@drawable/ic_launcher"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE"/>
</intent-filter>
</service>
<service
android:name=".services.MainService"
android:exported="true"/>
<service
android:name="cc.winboll.studio.appbase.services.TestDemoBindService"
android:exported="true"/>
<service
android:name="cc.winboll.studio.appbase.services.TestDemoService"
android:exported="true"/>
<service android:name=".services.AssistantService"/>
<receiver
android:name="cc.winboll.studio.appbase.receivers.MainReceiver"
android:exported="true">
<intent-filter>
<action android:name="cc.winboll.studio.appbase.receivers.MainReceiver"/>
</intent-filter>
</receiver>
<receiver
android:name=".widgets.APPNewsWidget"
android:exported="true">
<intent-filter>
<action android:name="cc.winboll.studio.appbase.widgets.APPNewsWidget.ACTION_WAKEUP_SERVICE"/>
<action android:name="cc.winboll.studio.appbase.widgets.APPNewsWidget.ACTION_RELOAD_REPORT"/>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_provider_info_sos"/>
</receiver>
<receiver
android:name=".receivers.APPNewsWidgetClickListener"
android:exported="true">
<intent-filter>
<action android:name="cc.winboll.studio.appbase.receivers.APPNewsWidgetClickListener.ACTION_PRE"/>
<action android:name="cc.winboll.studio.appbase.receivers.APPNewsWidgetClickListener.ACTION_NEXT"/>
</intent-filter>
</receiver>
<service
android:name=".SimpleOperateSignalCenterService"
android:exported="true">
</service>
<service
android:name=".services.TestService"
android:exported="true"/>
<receiver
android:name=".receiver.MyBroadcastReceiver"
android:exported="true">
<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>
<service android:name="cc.winboll.studio.libappbase.sos.SOSCenter"/>
<receiver
android:name="cc.winboll.studio.libappbase.sos.SOSCenterServiceReceiver"
android:exported="true">
<intent-filter>
<action android:name="cc.winboll.studio.libappbase.sos.SOSCenterServiceReceiver"/>
</intent-filter>
</receiver>
<activity android:name="cc.winboll.studio.libappbase.activities.YunActivity"/>
<activity android:name="cc.winboll.studio.libappbase.activities.LogonActivity"/>
</application>
</manifest>

View File

@@ -0,0 +1,345 @@
package cc.winboll.studio.winboll;
import android.app.Activity;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.res.Resources;
import android.graphics.Typeface;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.ViewGroup;
import android.widget.HorizontalScrollView;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import cc.winboll.studio.libappbase.GlobalApplication;
import com.hjq.toast.ToastUtils;
import com.hjq.toast.style.WhiteToastStyle;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.Thread.UncaughtExceptionHandler;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
public class App extends GlobalApplication {
private static Handler MAIN_HANDLER = new Handler(Looper.getMainLooper());
@Override
public void onCreate() {
super.onCreate();
// 初始化 Toast 框架
ToastUtils.init(this);
// 设置 Toast 布局样式
//ToastUtils.setView(R.layout.view_toast);
ToastUtils.setStyle(new WhiteToastStyle());
ToastUtils.setGravity(Gravity.BOTTOM, 0, 200);
//CrashHandler.getInstance().registerGlobal(this);
//CrashHandler.getInstance().registerPart(this);
}
public static void write(InputStream input, OutputStream output) throws IOException {
byte[] buf = new byte[1024 * 8];
int len;
while ((len = input.read(buf)) != -1) {
output.write(buf, 0, len);
}
}
public static void write(File file, byte[] data) throws IOException {
File parent = file.getParentFile();
if (parent != null && !parent.exists()) parent.mkdirs();
ByteArrayInputStream input = new ByteArrayInputStream(data);
FileOutputStream output = new FileOutputStream(file);
try {
write(input, output);
} finally {
closeIO(input, output);
}
}
public static String toString(InputStream input) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
write(input, output);
try {
return output.toString("UTF-8");
} finally {
closeIO(input, output);
}
}
public static void closeIO(Closeable... closeables) {
for (Closeable closeable : closeables) {
try {
if (closeable != null) closeable.close();
} catch (IOException ignored) {}
}
}
public static class CrashHandler {
public static final UncaughtExceptionHandler DEFAULT_UNCAUGHT_EXCEPTION_HANDLER = Thread.getDefaultUncaughtExceptionHandler();
private static CrashHandler sInstance;
private PartCrashHandler mPartCrashHandler;
public static CrashHandler getInstance() {
if (sInstance == null) {
sInstance = new CrashHandler();
}
return sInstance;
}
public void registerGlobal(Context context) {
registerGlobal(context, null);
}
public void registerGlobal(Context context, String crashDir) {
Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandlerImpl(context.getApplicationContext(), crashDir));
}
public void unregister() {
Thread.setDefaultUncaughtExceptionHandler(DEFAULT_UNCAUGHT_EXCEPTION_HANDLER);
}
public void registerPart(Context context) {
unregisterPart(context);
mPartCrashHandler = new PartCrashHandler(context.getApplicationContext());
MAIN_HANDLER.postAtFrontOfQueue(mPartCrashHandler);
}
public void unregisterPart(Context context) {
if (mPartCrashHandler != null) {
mPartCrashHandler.isRunning.set(false);
mPartCrashHandler = null;
}
}
private static class PartCrashHandler implements Runnable {
private final Context mContext;
public AtomicBoolean isRunning = new AtomicBoolean(true);
public PartCrashHandler(Context context) {
this.mContext = context;
}
@Override
public void run() {
while (isRunning.get()) {
try {
Looper.loop();
} catch (final Throwable e) {
e.printStackTrace();
if (isRunning.get()) {
MAIN_HANDLER.post(new Runnable(){
@Override
public void run() {
Toast.makeText(mContext, e.toString(), Toast.LENGTH_LONG).show();
}
});
} else {
if (e instanceof RuntimeException) {
throw (RuntimeException)e;
} else {
throw new RuntimeException(e);
}
}
}
}
}
}
private static class UncaughtExceptionHandlerImpl implements UncaughtExceptionHandler {
private static DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy_MM_dd-HH_mm_ss");
private final Context mContext;
private final File mCrashDir;
public UncaughtExceptionHandlerImpl(Context context, String crashDir) {
this.mContext = context;
this.mCrashDir = TextUtils.isEmpty(crashDir) ? new File(mContext.getExternalCacheDir(), "crash") : new File(crashDir);
}
@Override
public void uncaughtException(Thread thread, Throwable throwable) {
try {
String log = buildLog(throwable);
writeLog(log);
try {
Intent intent = new Intent(mContext, CrashActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(Intent.EXTRA_TEXT, log);
mContext.startActivity(intent);
} catch (Throwable e) {
e.printStackTrace();
writeLog(e.toString());
}
throwable.printStackTrace();
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(0);
} catch (Throwable e) {
if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null) DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(thread, throwable);
}
}
private String buildLog(Throwable throwable) {
String time = DATE_FORMAT.format(new Date());
String versionName = "unknown";
long versionCode = 0;
try {
PackageInfo packageInfo = mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0);
versionName = packageInfo.versionName;
versionCode = Build.VERSION.SDK_INT >= 28 ? packageInfo.getLongVersionCode() : packageInfo.versionCode;
} catch (Throwable ignored) {}
LinkedHashMap<String, String> head = new LinkedHashMap<String, String>();
head.put("Time Of Crash", time);
head.put("Device", String.format("%s, %s", Build.MANUFACTURER, Build.MODEL));
head.put("Android Version", String.format("%s (%d)", Build.VERSION.RELEASE, Build.VERSION.SDK_INT));
head.put("App Version", String.format("%s (%d)", versionName, versionCode));
head.put("Kernel", getKernel());
head.put("Support Abis", Build.VERSION.SDK_INT >= 21 && Build.SUPPORTED_ABIS != null ? Arrays.toString(Build.SUPPORTED_ABIS): "unknown");
head.put("Fingerprint", Build.FINGERPRINT);
StringBuilder builder = new StringBuilder();
for (String key : head.keySet()) {
if (builder.length() != 0) builder.append("\n");
builder.append(key);
builder.append(" : ");
builder.append(head.get(key));
}
builder.append("\n\n");
builder.append(Log.getStackTraceString(throwable));
return builder.toString();
}
private void writeLog(String log) {
String time = DATE_FORMAT.format(new Date());
File file = new File(mCrashDir, "crash_" + time + ".txt");
try {
write(file, log.getBytes("UTF-8"));
} catch (Throwable e) {
e.printStackTrace();
}
}
private static String getKernel() {
try {
return App.toString(new FileInputStream("/proc/version")).trim();
} catch (Throwable e) {
return e.getMessage();
}
}
}
}
public static final class CrashActivity extends Activity {
private String mLog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme(android.R.style.Theme_DeviceDefault);
setTitle("App Crash");
mLog = getIntent().getStringExtra(Intent.EXTRA_TEXT);
ScrollView contentView = new ScrollView(this);
contentView.setFillViewport(true);
HorizontalScrollView horizontalScrollView = new HorizontalScrollView(this);
TextView textView = new TextView(this);
int padding = dp2px(16);
textView.setPadding(padding, padding, padding, padding);
textView.setText(mLog);
textView.setTextIsSelectable(true);
textView.setTypeface(Typeface.DEFAULT);
textView.setLinksClickable(true);
horizontalScrollView.addView(textView);
contentView.addView(horizontalScrollView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
setContentView(contentView);
}
private void restart() {
Intent intent = getPackageManager().getLaunchIntentForPackage(getPackageName());
if (intent != null) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
finish();
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(0);
}
private static int dp2px(float dpValue) {
final float scale = Resources.getSystem().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
menu.add(0, android.R.id.copy, 0, android.R.string.copy)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.copy:
ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
cm.setPrimaryClip(ClipData.newPlainText(getPackageName(), mLog));
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onBackPressed() {
restart();
}
}
}

View File

@@ -0,0 +1,53 @@
package cc.winboll.studio.winboll;
/**
* @Author ZhanGSKen<zhangsken@188.com>
* @Date 2025/05/22 13:08
*/
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
public class CustomToolbar extends Toolbar {
private View viewMain;
public CustomToolbar(Context context) {
this(context, null);
}
public CustomToolbar(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomToolbar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context, attrs);
}
private void initView(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomToolbar);
// 获取属性值
String toolbarTitle = typedArray.getString(R.styleable.CustomToolbar_toolbarTitle);
int toolbarTitleColor = typedArray.getColor(R.styleable.CustomToolbar_toolbarTitleColor, android.graphics.Color.WHITE);
int toolbarBackgroundColor = typedArray.getColor(R.styleable.CustomToolbar_toolbarBackgroundColor, android.graphics.Color.BLUE);
// 加载布局
viewMain = LayoutInflater.from(context).inflate(R.layout.view_toolbar, this, true);
// 应用属性值
TextView toolbarTitleTextView = viewMain.findViewById(R.id.toolbar_title);
toolbarTitleTextView.setText(toolbarTitle);
toolbarTitleTextView.setTextColor(toolbarTitleColor);
viewMain.setBackgroundColor(toolbarBackgroundColor);
// 释放 TypedArray
typedArray.recycle();
}
}

View File

@@ -0,0 +1,51 @@
package cc.winboll.studio.winboll;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.libappbase.LogView;
public class MainActivity extends AppCompatActivity {
public static final String TAG = "MainActivity";
LogView mLogView;
private TerminalView mTerminalView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
// 先设置 Toolbar 为支持 ActionBar
setSupportActionBar(toolbar);
// 为导航图标设置点击事件
toolbar.setNavigationOnClickListener(new android.view.View.OnClickListener() {
@Override
public void onClick(android.view.View v) {
// 这里可以添加点击事件的逻辑
}
});
//getSupportActionBar().setDisplayHomeAsUpEnabled(true);
//getSupportActionBar().setHomeButtonEnabled(true);
// 如果要设置其他图标(如菜单图标等),可根据具体需求进行设置
// 例如,有些情况下可以通过 MenuInflater 来 inflate 菜单资源,然后在菜单的 onMenuItemClick 中处理图标点击事件
mLogView = findViewById(R.id.logview);
mTerminalView = findViewById(R.id.terminalView);
// 示例:模拟用户输入 "ls" 命令
mTerminalView.sendInput("ls");
}
@Override
protected void onResume() {
super.onResume();
mLogView.start();
}
}

View File

@@ -0,0 +1,79 @@
package cc.winboll.studio.appbase;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/02/13 19:30:10
*/
import android.content.Context;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
import cc.winboll.studio.appbase.models.MainServiceBean;
import cc.winboll.studio.appbase.services.MainService;
public class MyTileService extends TileService {
public static final String TAG = "MyTileService";
volatile static MyTileService _MyTileService;
@Override
public void onStartListening() {
super.onStartListening();
_MyTileService = this;
Tile tile = getQsTile();
MainServiceBean bean = MainServiceBean.loadBean(this, MainServiceBean.class);
if (bean != null && bean.isEnable()) {
//MainService.startMainService(context);
tile.setState(Tile.STATE_ACTIVE);
tile.setIcon(android.graphics.drawable.Icon.createWithResource(this, R.drawable.ic_cloud));
} else {
//MainService.stopMainService(context);
tile.setState(Tile.STATE_INACTIVE);
tile.setIcon(android.graphics.drawable.Icon.createWithResource(this, R.drawable.ic_cloud_outline));
}
tile.updateTile();
// Tile tile = getQsTile();
// tile.setState(Tile.STATE_INACTIVE);
// tile.setLabel(getString(R.string.tileservice_name));
// tile.setIcon(android.graphics.drawable.Icon.createWithResource(this, R.drawable.ic_cloud_outline));
// tile.updateTile();
}
@Override
public void onClick() {
super.onClick();
Tile tile = getQsTile();
MainServiceBean bean = MainServiceBean.loadBean(this, MainServiceBean.class);
if (bean == null) {
bean = new MainServiceBean();
}
if (tile.getState() == Tile.STATE_ACTIVE) {
bean.setIsEnable(false);
MainServiceBean.saveBean(this, bean);
MainService.stopMainService(this);
} else if (tile.getState() == Tile.STATE_INACTIVE) {
bean.setIsEnable(true);
MainServiceBean.saveBean(this, bean);
MainService.startMainService(this);
}
updateServiceIconStatus(this);
}
public static void updateServiceIconStatus(Context context) {
if (_MyTileService == null) {
return;
}
Tile tile = _MyTileService.getQsTile();
MainServiceBean bean = MainServiceBean.loadBean(context, MainServiceBean.class);
if (bean != null && bean.isEnable()) {
tile.setState(Tile.STATE_ACTIVE);
tile.setIcon(android.graphics.drawable.Icon.createWithResource(context, R.drawable.ic_cloud));
} else {
tile.setState(Tile.STATE_INACTIVE);
tile.setIcon(android.graphics.drawable.Icon.createWithResource(context, R.drawable.ic_cloud_outline));
}
tile.updateTile();
}
}

View File

@@ -0,0 +1,150 @@
package cc.winboll.studio.libappbase.activities;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.RadioButton;
import cc.winboll.studio.libappbase.BuildConfig;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.LogView;
import cc.winboll.studio.libappbase.R;
import cc.winboll.studio.libappbase.models.UserInfoModel;
import cc.winboll.studio.libappbase.utils.RSAUtils;
import cc.winboll.studio.libappbase.utils.YunUtils;
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/06/04 13:29
* @Describe 用户登录框
*/
public class LogonActivity extends Activity implements IWinBoLLActivity {
public static final String TAG = "LogonActivity";
public static final String DEBUG_HOST = "http://10.8.0.250:456";
public static final String YUN_HOST = "https://yun.winboll.cc";
String mHost = "";
RadioButton mrbYunHost;
RadioButton mrbDebugHost;
LogView mLogView;
@Override
public Activity getActivity() {
return this;
}
@Override
public String getTag() {
return TAG;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_logon);
mLogView = findViewById(R.id.logview);
mLogView.start();
mHost = BuildConfig.DEBUG ? DEBUG_HOST: YUN_HOST;
if (BuildConfig.DEBUG) {
mrbYunHost = findViewById(R.id.rb_yunhost);
mrbDebugHost = findViewById(R.id.rb_debughost);
mrbYunHost.setChecked(!BuildConfig.DEBUG);
mrbDebugHost.setChecked(BuildConfig.DEBUG);
} else {
findViewById(R.id.ll_hostbar).setVisibility(View.GONE);
}
}
public void onSwitchHost(View view) {
if (view.getId() == R.id.rb_yunhost) {
mrbDebugHost.setChecked(false);
mHost = YUN_HOST;
} else if (view.getId() == R.id.rb_debughost) {
mrbYunHost.setChecked(false);
mHost = DEBUG_HOST;
}
}
@Override
protected void onResume() {
super.onResume();
mLogView.start();
}
public void onTestLogin(View view) {
LogUtils.d(TAG, "onTestLogin");
final YunUtils yunUtils = YunUtils.getInstance(this);
UserInfoModel userInfoModel = new UserInfoModel();
userInfoModel.setUsername("jian");
userInfoModel.setPassword("kkiio");
userInfoModel.setToken("aaa111");
yunUtils.login(mHost, userInfoModel);
}
public void onTestRSA(View view) {
LogUtils.d(TAG, "onTestRSA");
RSAUtils utils = RSAUtils.getInstance(this);
try {
// 测试 1首次生成密钥对
LogUtils.d(TAG, "==== 首次生成密钥对 ====");
if (utils.keysExist()) {
LogUtils.d(TAG, "密钥对已生成");
} else {
utils.generateAndSaveKeys();
LogUtils.d(TAG, "密钥对生成成功。");
}
// 测试 2获取密钥对自动读取已生成的文件
KeyPair keyPair = utils.getOrGenerateKeys();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
// 打印密钥信息
LogUtils.d(TAG, "\n==== 密钥信息 ====");
LogUtils.d(TAG, "公钥算法:" + publicKey.getAlgorithm());
LogUtils.d(TAG, "公钥编码长度:" + publicKey.getEncoded().length + "字节");
LogUtils.d(TAG, "私钥算法:" + privateKey.getAlgorithm());
LogUtils.d(TAG, "私钥编码长度:" + privateKey.getEncoded().length + "字节");
// 测试 3重复调用时检查是否复用文件
LogUtils.d(TAG, "\n==== 二次调用 ====");
KeyPair reusedPair = utils.getOrGenerateKeys();
LogUtils.d(TAG, "是否为同一公钥:" + (publicKey.equals(reusedPair.getPublic()))); // true单例引用
LogUtils.d(TAG, "操作完成");
String testMessage = "Hello, RSA Encryption!";
// 1. 获取或生成密钥对
PublicKey publicKeyReused = reusedPair.getPublic();
PrivateKey privateKeyReused = reusedPair.getPrivate();
// 2. 公钥加密
byte[] encryptedData = utils.encryptWithPublicKey(testMessage, publicKeyReused);
LogUtils.d(TAG, "加密后数据(字节长度):" + encryptedData.length);
// 3. 私钥解密
String decryptedMessage = utils.decryptWithPrivateKey(encryptedData, privateKeyReused);
LogUtils.d(TAG, "解密结果: " + decryptedMessage);
// 4. 验证解密是否成功
if (testMessage.equals(decryptedMessage)) {
LogUtils.d(TAG, "加密解密测试通过!");
} else {
LogUtils.d(TAG, "测试失败:内容不一致");
}
} catch (Exception e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
}
}

View File

@@ -0,0 +1,77 @@
package cc.winboll.studio.appbase.activities;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/03/25 11:46:40
* @Describe 测试窗口2
*/
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toolbar;
import cc.winboll.studio.appbase.R;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
public class New2Activity extends WinBoLLActivity implements IWinBoLLActivity {
public static final String TAG = "New2Activity";
Toolbar mToolbar;
//LogView mLogView;
@Override
public Activity getActivity() {
return this;
}
@Override
public String getTag() {
return TAG;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_new2);
// mLogView = findViewById(R.id.logview);
// mLogView.start();
mToolbar = findViewById(R.id.toolbar);
setActionBar(mToolbar);
}
@Override
protected void onResume() {
super.onResume();
//mLogView.start();
}
public void onCloseThisActivity(View view) {
GlobalApplication.getWinBoLLActivityManager().finish(this);
}
public void onCloseAllActivity(View view) {
GlobalApplication.getWinBoLLActivityManager().finishAll();
}
public void onNewActivity(View view) {
GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(this, NewActivity.class);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.toolbar_main, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// 在switch语句中处理每个ID并在处理完后返回true未处理的情况返回false。
return super.onOptionsItemSelected(item);
}
}

View File

@@ -0,0 +1,76 @@
package cc.winboll.studio.appbase.activities;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/03/25 05:04:22
* @Describe
*/
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toolbar;
import cc.winboll.studio.appbase.R;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
public class NewActivity extends WinBoLLActivity implements IWinBoLLActivity {
public static final String TAG = "NewActivity";
Toolbar mToolbar;
//LogView mLogView;
@Override
public Activity getActivity() {
return this;
}
@Override
public String getTag() {
return TAG;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_new);
// mLogView = findViewById(R.id.logview);
// mLogView.start();
mToolbar = findViewById(R.id.toolbar);
setActionBar(mToolbar);
}
@Override
protected void onResume() {
super.onResume();
//mLogView.start();
}
public void onCloseThisActivity(View view) {
GlobalApplication.getWinBoLLActivityManager().finish(this);
}
public void onCloseAllActivity(View view) {
GlobalApplication.getWinBoLLActivityManager().finishAll();
}
public void onNew2Activity(View view) {
GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(this, New2Activity.class);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.toolbar_main, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// 在switch语句中处理每个ID并在处理完后返回true未处理的情况返回false。
return super.onOptionsItemSelected(item);
}
}

View File

@@ -0,0 +1,61 @@
package cc.winboll.studio.appbase.activities;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/05/10 09:48
* @Describe WinBoLL 窗口基础类
*/
import android.app.Activity;
import android.os.Bundle;
import android.view.MenuItem;
import cc.winboll.studio.appbase.MainActivity;
import cc.winboll.studio.appbase.R;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
public class WinBoLLActivity extends Activity implements IWinBoLLActivity {
public static final String TAG = "WinBoLLActivity";
@Override
public Activity getActivity() {
return this;
}
@Override
public String getTag() {
return TAG;
}
@Override
protected void onResume() {
super.onResume();
LogUtils.d(TAG, String.format("onResume %s", getTag()));
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.item_log) {
GlobalApplication.getWinBoLLActivityManager().startLogActivity(this);
return true;
} else if (item.getItemId() == R.id.item_home) {
GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(getApplicationContext(), MainActivity.class);
return true;
}
// 在switch语句中处理每个ID并在处理完后返回true未处理的情况返回false。
return super.onOptionsItemSelected(item);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
GlobalApplication.getWinBoLLActivityManager().add(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
GlobalApplication.getWinBoLLActivityManager().registeRemove(this);
}
}

View File

@@ -0,0 +1,164 @@
package cc.winboll.studio.appbase;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.CheckBox;
import android.widget.Toolbar;
import cc.winboll.studio.appbase.R;
import cc.winboll.studio.libappbase.CrashHandler;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.GlobalCrashActivity;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.utils.ToastUtils;
public class WinBoLLUnitTestActivity extends Activity {
public static final String TAG = "MainActivity";
Toolbar mToolbar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ToastUtils.show("onCreate");
setContentView(R.layout.activity_main);
mToolbar = findViewById(R.id.toolbar);
setActionBar(mToolbar);
CheckBox cbIsDebugMode = findViewById(R.id.activitymainCheckBox1);
cbIsDebugMode.setChecked(GlobalApplication.isDebuging());
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.toolbar_main, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if(item.getItemId() == R.id.item_yun) {
GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(this, cc.winboll.studio.libappbase.activities.YunActivity.class);
} else if(item.getItemId() == R.id.item_logon) {
GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(this, cc.winboll.studio.libappbase.activities.LogonActivity.class);
}
// 在switch语句中处理每个ID并在处理完后返回true未处理的情况返回false。
return super.onOptionsItemSelected(item);
}
@Override
protected void onDestroy() {
super.onDestroy();
Intent intentAPPWidget = new Intent(this, StatusWidget.class);
intentAPPWidget.setAction(StatusWidget.ACTION_STATUS_UPDATE);
sendBroadcast(intentAPPWidget);
}
public void onSwitchDebugMode(View view) {
boolean isDebuging = ((CheckBox)view).isChecked();
GlobalApplication.setIsDebuging(isDebuging);
GlobalApplication.saveDebugStatus();
}
public void onPreviewGlobalCrashActivity(View view) {
Intent intent = new Intent(this, GlobalCrashActivity.class);
intent.putExtra(CrashHandler.EXTRA_CRASH_INFO, "Demo log...");
startActivity(intent);
}
public void onStartCenter(View view) {
MainService.startMainService(this);
}
public void onStopCenter(View view) {
MainService.stopMainService(this);
}
public void onTestStopMainServiceWithoutSettingEnable(View view) {
LogUtils.d(TAG, "onTestStopMainServiceWithoutSettingEnable");
stopService(new Intent(this, MainService.class));
}
public void onTestUseComponentStartService(View view) {
LogUtils.d(TAG, "onTestUseComponentStartService");
// 目标服务的包名和类名
String packageName = this.getPackageName();
String serviceClassName = TestDemoService.class.getName();
// 构建Intent
Intent intentService = new Intent();
intentService.setComponent(new ComponentName(packageName, serviceClassName));
startService(intentService);
}
public void onTestDemoServiceSOS(View view) {
Intent intent = new Intent(this, TestDemoService.class);
stopService(intent);
if (App.isDebuging()) {
SOS.sosToAppBaseBeta(this, TestDemoService.class.getName());
} else {
SOS.sosToAppBase(this, TestDemoService.class.getName());
}
}
public void onSartTestDemoService(View view) {
Intent intent = new Intent(this, TestDemoService.class);
intent.setAction(TestDemoService.ACTION_ENABLE);
startService(intent);
}
public void onStopTestDemoService(View view) {
Intent intent = new Intent(this, TestDemoService.class);
intent.setAction(TestDemoService.ACTION_DISABLE);
startService(intent);
Intent intentStop = new Intent(this, TestDemoService.class);
stopService(intentStop);
}
public void onStopTestDemoServiceNoSettings(View view) {
Intent intent = new Intent(this, TestDemoService.class);
stopService(intent);
}
public void onSartTestDemoBindService(View view) {
Intent intent = new Intent(this, TestDemoBindService.class);
intent.setAction(TestDemoBindService.ACTION_ENABLE);
startService(intent);
}
public void onStopTestDemoBindService(View view) {
Intent intent = new Intent(this, TestDemoBindService.class);
intent.setAction(TestDemoBindService.ACTION_DISABLE);
startService(intent);
Intent intentStop = new Intent(this, TestDemoBindService.class);
stopService(intentStop);
}
public void onStopTestDemoBindServiceNoSettings(View view) {
Intent intent = new Intent(this, TestDemoBindService.class);
stopService(intent);
}
public void onTestOpenNewActivity(View view) {
GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(this, NewActivity.class);
}
@Override
protected void onResume() {
super.onResume();
}
}

View File

@@ -0,0 +1,126 @@
package cc.winboll.studio.libappbase.activities;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import cc.winboll.studio.libappbase.BuildConfig;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.R;
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
import java.io.IOException;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import android.widget.RadioButton;
import cc.winboll.studio.libappbase.LogView;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/06/04 11:06
* @Describe 云宝云
*/
public class YunActivity extends Activity implements IWinBoLLActivity {
public static final String TAG = "YunActivity";
public static final String DEBUG_HOST = "http://10.8.0.250:456";
public static final String YUN_HOST = "https://yun.winboll.cc";
String mHost = "";
RadioButton mrbYunHost;
RadioButton mrbDebugHost;
LogView mLogView;
@Override
public Activity getActivity() {
return this;
}
@Override
public String getTag() {
return TAG;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_yun);
mLogView = findViewById(R.id.logview);
mLogView.start();
mHost = BuildConfig.DEBUG ? DEBUG_HOST: YUN_HOST;
if (BuildConfig.DEBUG) {
mrbYunHost = findViewById(R.id.rb_yunhost);
mrbDebugHost = findViewById(R.id.rb_debughost);
mrbYunHost.setChecked(!BuildConfig.DEBUG);
mrbDebugHost.setChecked(BuildConfig.DEBUG);
} else {
findViewById(R.id.ll_hostbar).setVisibility(View.GONE);
}
}
public void onSwitchHost(View view) {
if (view.getId() == R.id.rb_yunhost) {
mrbDebugHost.setChecked(false);
mHost = YUN_HOST;
} else if (view.getId() == R.id.rb_debughost) {
mrbYunHost.setChecked(false);
mHost = DEBUG_HOST;
}
}
@Override
protected void onResume() {
super.onResume();
mLogView.start();
}
public void onTestYun(View view) {
LogUtils.d(TAG, "onTestYun");
(new Thread(new Runnable(){
@Override
public void run() {
testYun();
}
})).start();
}
void testYun() {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(mHost + "/backups/")
.build();
Response response = null;
try {
response = client.newCall(request).execute();
if (response.isSuccessful()) {
String responseBody = "";
if (response.body() != null) {
responseBody = response.body().string();
}
// 正则匹配:任意主机名 -> Test OK主机名部分匹配非空字符
boolean isMatch = responseBody.matches(".+? -> Test OK");
if (isMatch) {
LogUtils.d(TAG, responseBody);
} else {
LogUtils.d(TAG, "响应内容不匹配,内容:" + responseBody);
}
} else {
LogUtils.d(TAG, "请求失败,状态码:" + response.code());
}
} catch (IOException e) {
LogUtils.d(TAG, "读取响应体失败:" + e.getMessage());
} catch (Exception e) {
LogUtils.d(TAG, "异常:" + e.getMessage());
e.printStackTrace(); // Java 7 需显式打印堆栈
} finally {
// 手动关闭 ResponseJava 7 不支持 try-with-resources
if (response != null && response.body() != null) {
response.body().close();
}
}
}
}

View File

@@ -0,0 +1,38 @@
package cc.winboll.studio.appbase.handlers;
/**
* @Author ZhanGSKen<zhangsken@qq.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;
}
}
}
}

View File

@@ -0,0 +1,67 @@
package cc.winboll.studio.appbase.models;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/02/13 07:06:13
*/
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import java.io.IOException;
public class MainServiceBean extends BaseBean {
public static final String TAG = "MainServiceBean";
boolean isEnable;
public MainServiceBean() {
this.isEnable = false;
}
public void setIsEnable(boolean isEnable) {
this.isEnable = isEnable;
}
public boolean isEnable() {
return isEnable;
}
@Override
public String getName() {
return MainServiceBean.class.getName();
}
@Override
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
super.writeThisToJsonWriter(jsonWriter);
jsonWriter.name("isEnable").value(isEnable());
}
@Override
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
if (name.equals("isEnable")) {
setIsEnable(jsonReader.nextBoolean());
} else {
return false;
}
}
return true;
}
@Override
public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
jsonReader.beginObject();
while (jsonReader.hasNext()) {
String name = jsonReader.nextName();
if (!initObjectsFromJsonReader(jsonReader, name)) {
jsonReader.skipValue();
}
}
// 结束 JSON 对象
jsonReader.endObject();
return this;
}
}

View File

@@ -0,0 +1,67 @@
package cc.winboll.studio.appbase.models;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/03/07 12:47:22
* @Describe TestServiceBean
*/
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import java.io.IOException;
public class TestDemoBindServiceBean extends BaseBean {
public static final String TAG = "TestServiceBean";
boolean isEnable;
public TestDemoBindServiceBean() {
this.isEnable = false;
}
public void setIsEnable(boolean isEnable) {
this.isEnable = isEnable;
}
public boolean isEnable() {
return isEnable;
}
@Override
public String getName() {
return TestDemoBindServiceBean.class.getName();
}
@Override
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
super.writeThisToJsonWriter(jsonWriter);
jsonWriter.name("isEnable").value(isEnable());
}
@Override
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
if (name.equals("isEnable")) {
setIsEnable(jsonReader.nextBoolean());
} else {
return false;
}
}
return true;
}
@Override
public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
jsonReader.beginObject();
while (jsonReader.hasNext()) {
String name = jsonReader.nextName();
if (!initObjectsFromJsonReader(jsonReader, name)) {
jsonReader.skipValue();
}
}
// 结束 JSON 对象
jsonReader.endObject();
return this;
}
}

View File

@@ -0,0 +1,68 @@
package cc.winboll.studio.appbase.models;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/03/07 12:49:21
* @Describe TestDemoServiceBean
*/
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import java.io.IOException;
public class TestDemoServiceBean extends BaseBean {
public static final String TAG = "TestDemoServiceBean";
boolean isEnable;
public TestDemoServiceBean() {
this.isEnable = false;
}
public void setIsEnable(boolean isEnable) {
this.isEnable = isEnable;
}
public boolean isEnable() {
return isEnable;
}
@Override
public String getName() {
return TestDemoServiceBean.class.getName();
}
@Override
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
super.writeThisToJsonWriter(jsonWriter);
jsonWriter.name("isEnable").value(isEnable());
}
@Override
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
if (name.equals("isEnable")) {
setIsEnable(jsonReader.nextBoolean());
} else {
return false;
}
}
return true;
}
@Override
public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
jsonReader.beginObject();
while (jsonReader.hasNext()) {
String name = jsonReader.nextName();
if (!initObjectsFromJsonReader(jsonReader, name)) {
jsonReader.skipValue();
}
}
// 结束 JSON 对象
jsonReader.endObject();
return this;
}
}

View File

@@ -0,0 +1,70 @@
package cc.winboll.studio.libappbase.models;
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import java.io.IOException;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/05/10 09:36
* @Describe WinBoLL 应用消息数据模型
*/
public class WinBoLLNewsBean extends BaseBean {
public static final String TAG = "WinBoLLNewsBean";
String message;
public WinBoLLNewsBean() {
this.message = "";
}
public WinBoLLNewsBean(String message) {
this.message = message;
}
public void setMessage(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
@Override
public String getName() {
return WinBoLLNewsBean.class.getName();
}
@Override
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
super.writeThisToJsonWriter(jsonWriter);
jsonWriter.name("message").value(getMessage());
}
@Override
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
if (name.equals("message")) {
setMessage(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;
}
}

View File

@@ -0,0 +1,36 @@
package cc.winboll.studio.appbase.receivers;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/03/24 07:11:44
*/
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import cc.winboll.studio.appbase.widgets.APPNewsWidget;
import cc.winboll.studio.libappbase.LogUtils;
public class APPNewsWidgetClickListener extends BroadcastReceiver {
public static final String TAG = "APPNewsWidgetClickListener";
public static final String ACTION_PRE = APPNewsWidgetClickListener.class.getName() + ".ACTION_PRE";
public static final String ACTION_NEXT = APPNewsWidgetClickListener.class.getName() + ".ACTION_NEXT";
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action == null) {
LogUtils.d(TAG, String.format("action %s", action));
return;
}
if (action.equals(ACTION_PRE)) {
LogUtils.d(TAG, "ACTION_PRE");
APPNewsWidget.prePage(context);
} else if (action.equals(ACTION_NEXT)) {
LogUtils.d(TAG, "ACTION_NEXT");
APPNewsWidget.nextPage(context);
} else {
LogUtils.d(TAG, String.format("action %s", action));
}
}
}

View File

@@ -0,0 +1,115 @@
package cc.winboll.studio.appbase.receivers;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/02/13 06:58:04
* @Describe 主要广播接收器
*/
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import cc.winboll.studio.appbase.services.MainService;
import cc.winboll.studio.appbase.widgets.APPNewsWidget;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.models.WinBoLLModel;
import cc.winboll.studio.libappbase.models.WinBoLLNewsBean;
import cc.winboll.studio.libappbase.sos.SOS;
import cc.winboll.studio.libappbase.sos.SOSObject;
import cc.winboll.studio.libappbase.utils.ToastUtils;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.text.SimpleDateFormat;
import java.util.Date;
public class MainReceiver extends BroadcastReceiver {
public static final String TAG = "MainReceiver";
public static final String ACTION_BOOT_COMPLETED = "android.intent.action.BOOT_COMPLETED";
WeakReference<MainService> mwrService;
public MainReceiver(MainService service) {
mwrService = new WeakReference<MainService>(service);
}
@Override
public void onReceive(Context context, Intent intent) {
String szAction = intent.getAction();
if (szAction.equals(ACTION_BOOT_COMPLETED)) {
ToastUtils.show("ACTION_BOOT_COMPLETED");
} else if (szAction.equals(IWinBoLLActivity.ACTION_BIND)) {
LogUtils.d(TAG, "ACTION_BIND");
LogUtils.d(TAG, String.format("context.getPackageName() %s", context.getPackageName()));
LogUtils.d(TAG, String.format("intent.getAction() %s", intent.getAction()));
String szWinBoLLModel = intent.getStringExtra(WinBoLL.EXTRA_WINBOLLMODEL);
LogUtils.d(TAG, String.format("szAPPModel %s", szWinBoLLModel));
if (szWinBoLLModel != null && !szWinBoLLModel.equals("")) {
try {
WinBoLLModel bean = WinBoLLModel.parseStringToBean(szWinBoLLModel, WinBoLLModel.class);
if (bean != null) {
String szAppPackageName = bean.getAppPackageName();
LogUtils.d(TAG, String.format("szAppPackageName %s", szAppPackageName));
String szAppMainServiveName = bean.getAppMainServiveName();
LogUtils.d(TAG, String.format("szAppMainServiveName %s", szAppMainServiveName));
mwrService.get().bindWinBoLLModelConnection(bean);
}
} catch (IOException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
}
} else if (intent.getAction().equals(SOS.ACTION_SOS)) {
LogUtils.d(TAG, "ACTION_SOS");
String sos = intent.getStringExtra(SOS.EXTRA_OBJECT);
LogUtils.d(TAG, String.format("SOS %s", sos));
if (sos != null && !sos.equals("")) {
SOSObject bean = SOS.parseSOSObject(sos);
if (bean != null) {
String szObjectPackageName = bean.getObjectPackageName();
LogUtils.d(TAG, String.format("szObjectPackageName %s", szObjectPackageName));
String szObjectServiveName = bean.getObjectServiveName();
LogUtils.d(TAG, String.format("szObjectServiveName %s", szObjectServiveName));
Intent intentService = new Intent();
intentService.setComponent(new ComponentName(szObjectPackageName, szObjectServiveName));
context.startService(intentService);
String appName = AppUtils.getAppNameByPackageName(context, szObjectPackageName);
LogUtils.d(TAG, String.format("appName %s", appName));
WinBoLLNewsBean appWinBoLLNewsBean = new WinBoLLNewsBean(appName);
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
String currentTime = sdf.format(new Date());
StringBuilder sbLine = new StringBuilder();
sbLine.append("[");
sbLine.append(currentTime);
sbLine.append("] Power to ");
sbLine.append(appName);
appWinBoLLNewsBean.setMessage(sbLine.toString());
APPNewsWidget.addWinBoLLNewsBean(context, appWinBoLLNewsBean);
Intent intentWidget = new Intent(context, APPNewsWidget.class);
intentWidget.setAction(APPNewsWidget.ACTION_RELOAD_REPORT);
context.sendBroadcast(intentWidget);
}
}
} else {
ToastUtils.show(szAction);
}
}
// 注册 Receiver
//
public void registerAction(MainService service) {
IntentFilter filter=new IntentFilter();
filter.addAction(ACTION_BOOT_COMPLETED);
filter.addAction(SOS.ACTION_SOS);
filter.addAction(WinBoLL.ACTION_BIND);
//filter.addAction(Intent.ACTION_BATTERY_CHANGED);
service.registerReceiver(this, filter);
}
}

View File

@@ -0,0 +1,29 @@
package cc.winboll.studio.libappbase.receiver;
/**
* @Author ZhanGSKen<zhangsken@qq.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));
}
}
}

View File

@@ -0,0 +1,138 @@
package cc.winboll.studio.appbase.services;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/02/14 03:38:31
* @Describe 守护进程服务
*/
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import cc.winboll.studio.appbase.models.MainServiceBean;
import cc.winboll.studio.appbase.services.AssistantService;
import cc.winboll.studio.appbase.services.MainService;
import cc.winboll.studio.libappbase.LogUtils;
import android.os.Binder;
public class AssistantService extends Service {
public static final String TAG = "AssistantService";
MainServiceBean mMainServiceBean;
MyServiceConnection mMyServiceConnection;
MainService mMainService;
boolean isBound = false;
volatile boolean isThreadAlive = false;
public synchronized void setIsThreadAlive(boolean isThreadAlive) {
LogUtils.d(TAG, "setIsThreadAlive(...)");
LogUtils.d(TAG, String.format("isThreadAlive %s", isThreadAlive));
this.isThreadAlive = isThreadAlive;
}
public boolean isThreadAlive() {
return isThreadAlive;
}
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
@Override
public void onCreate() {
LogUtils.d(TAG, "onCreate");
super.onCreate();
//mMyBinder = new MyBinder();
if (mMyServiceConnection == null) {
mMyServiceConnection = new MyServiceConnection();
}
// 设置运行参数
setIsThreadAlive(false);
assistantService();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
LogUtils.d(TAG, "call onStartCommand(...)");
assistantService();
return START_STICKY;
}
@Override
public void onDestroy() {
//LogUtils.d(TAG, "onDestroy");
setIsThreadAlive(false);
// 解除绑定
if (isBound) {
unbindService(mMyServiceConnection);
isBound = false;
}
super.onDestroy();
}
// 运行服务内容
//
void assistantService() {
LogUtils.d(TAG, "assistantService()");
mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
LogUtils.d(TAG, String.format("mMainServiceBean.isEnable() %s", mMainServiceBean.isEnable()));
if (mMainServiceBean.isEnable()) {
LogUtils.d(TAG, String.format("mIsThreadAlive %s", isThreadAlive()));
if (isThreadAlive() == false) {
// 设置运行状态
setIsThreadAlive(true);
// 唤醒和绑定主进程
wakeupAndBindMain();
}
}
}
// 唤醒和绑定主进程
//
void wakeupAndBindMain() {
LogUtils.d(TAG, "wakeupAndBindMain()");
// 绑定服务的Intent
Intent intent = new Intent(this, MainService.class);
startService(new Intent(this, MainService.class));
bindService(intent, mMyServiceConnection, Context.BIND_IMPORTANT);
// startService(new Intent(this, MainService.class));
// bindService(new Intent(AssistantService.this, MainService.class), mMyServiceConnection, Context.BIND_IMPORTANT);
}
// 主进程与守护进程连接时需要用到此类
//
class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
LogUtils.d(TAG, "onServiceConnected(...)");
MainService.MyBinder binder = (MainService.MyBinder) service;
mMainService = binder.getService();
isBound = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
LogUtils.d(TAG, "onServiceDisconnected(...)");
mMainServiceBean = MainServiceBean.loadBean(AssistantService.this, MainServiceBean.class);
if (mMainServiceBean.isEnable()) {
wakeupAndBindMain();
}
isBound = false;
mMainService = null;
}
}
// 用于返回服务实例的Binder
public class MyBinder extends Binder {
AssistantService getService() {
LogUtils.d(TAG, "AssistantService MyBinder getService()");
return AssistantService.this;
}
}
}

View File

@@ -0,0 +1,317 @@
package cc.winboll.studio.appbase.services;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/02/13 06:56:41
* @Describe 拨号主服务
* 参考:
* 进程保活-双进程守护的正确姿势
* https://blog.csdn.net/sinat_35159441/article/details/75267380
* Android Service之onStartCommand方法研究
* https://blog.csdn.net/cyp331203/article/details/38920491
*/
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Binder;
import android.os.IBinder;
import cc.winboll.studio.appbase.MyTileService;
import cc.winboll.studio.appbase.handlers.MainServiceHandler;
import cc.winboll.studio.appbase.models.MainServiceBean;
import cc.winboll.studio.appbase.receivers.MainReceiver;
import cc.winboll.studio.appbase.services.AssistantService;
import cc.winboll.studio.appbase.threads.MainServiceThread;
import cc.winboll.studio.appbase.widgets.APPNewsWidget;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.models.APPModel;
import cc.winboll.studio.libappbase.models.WinBoLLModel;
import java.util.ArrayList;
public class MainService extends Service {
public static final String TAG = "MainService";
public static final int MSG_UPDATE_STATUS = 0;
static MainService _mControlCenterService;
volatile boolean isServiceRunning;
MainServiceBean mMainServiceBean;
MainServiceThread mMainServiceThread;
MainServiceHandler mMainServiceHandler;
MyServiceConnection mMyServiceConnection;
AssistantService mAssistantService;
boolean isBound = false;
MainReceiver mMainReceiver;
ArrayList<APPConnection> mAPPModelConnectionList;
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
public MainServiceThread getRemindThread() {
return mMainServiceThread;
}
@Override
public void onCreate() {
super.onCreate();
LogUtils.d(TAG, "onCreate()");
mAPPModelConnectionList = new ArrayList<APPConnection>();
_mControlCenterService = MainService.this;
isServiceRunning = false;
mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
if (mMyServiceConnection == null) {
mMyServiceConnection = new MyServiceConnection();
}
mMainServiceHandler = new MainServiceHandler(this);
// 运行服务内容
mainService();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
LogUtils.d(TAG, "onStartCommand(...)");
// 运行服务内容
mainService();
return (mMainServiceBean.isEnable()) ? START_STICKY : super.onStartCommand(intent, flags, startId);
}
// 运行服务内容
//
void mainService() {
LogUtils.d(TAG, "mainService()");
mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
if (mMainServiceBean.isEnable() && isServiceRunning == false) {
LogUtils.d(TAG, "mainService() start running");
isServiceRunning = true;
// 唤醒守护进程
wakeupAndBindAssistant();
if (mMainReceiver == null) {
// 注册广播接收器
mMainReceiver = new MainReceiver(this);
mMainReceiver.registerAction(this);
}
// 启动小部件
Intent intentTimeWidget = new Intent(this, APPNewsWidget.class);
intentTimeWidget.setAction(APPNewsWidget.ACTION_RELOAD_REPORT);
this.sendBroadcast(intentTimeWidget);
startMainServiceThread();
MyTileService.updateServiceIconStatus(this);
LogUtils.i(TAG, "Main Service Is Start.");
}
}
// 唤醒和绑定守护进程
//
void wakeupAndBindAssistant() {
LogUtils.d(TAG, "wakeupAndBindAssistant()");
Intent intent = new Intent(this, AssistantService.class);
startService(intent);
// 绑定服务的Intent
bindService(intent, mMyServiceConnection, Context.BIND_IMPORTANT);
}
// 开启提醒铃声线程
//
public void startMainServiceThread() {
LogUtils.d(TAG, "startMainServiceThread");
if (mMainServiceThread == null) {
mMainServiceThread = new MainServiceThread(this, mMainServiceHandler);
LogUtils.d(TAG, "new MainServiceThread");
} else {
if (mMainServiceThread.isExist() == true) {
mMainServiceThread = new MainServiceThread(this, mMainServiceHandler);
LogUtils.d(TAG, "renew MainServiceThread");
} else {
// 提醒进程正在进行中就更新状态后退出
LogUtils.d(TAG, "A mMainServiceThread running.");
return;
}
}
mMainServiceThread.start();
}
public void stopRemindThread() {
if (mMainServiceThread != null) {
mMainServiceThread.setIsExist(true);
mMainServiceThread = null;
}
}
@Override
public void onDestroy() {
//LogUtils.d(TAG, "onDestroy");
mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
if (mMainServiceBean.isEnable() == false) {
// 设置运行状态
isServiceRunning = false;// 解除绑定
if (isBound) {
unbindService(mMyServiceConnection);
isBound = false;
}
// 停止守护进程
Intent intent = new Intent(this, AssistantService.class);
stopService(intent);
// 停止Receiver
if (mMainReceiver != null) {
unregisterReceiver(mMainReceiver);
mMainReceiver = null;
}
// 停止前台通知栏
stopForeground(true);
// 停止消息提醒进程
stopRemindThread();
MyTileService.updateServiceIconStatus(this);
super.onDestroy();
//LogUtils.d(TAG, "onDestroy done");
}
}
public void bindWinBoLLModelConnection(WinBoLLModel bean) {
LogUtils.d(TAG, "bindAPPModelConnection(...)");
// 清理旧的绑定链接
for (int i = mAPPModelConnectionList.size() - 1; i > -1; i--) {
APPConnection item = mAPPModelConnectionList.get(i);
if (item.isBindToAPP(bean)) {
LogUtils.d(TAG, "Bind Servive exist.");
unbindService(item);
mAPPModelConnectionList.remove(i);
}
}
// 绑定服务
APPConnection appConnection = new APPConnection();
Intent intentService = new Intent();
intentService.setComponent(new ComponentName(bean.getAppPackageName(), bean.getAppMainServiveName()));
bindService(intentService, appConnection, Context.BIND_IMPORTANT);
mAPPModelConnectionList.add(appConnection);
Intent intentWidget = new Intent(this, APPNewsWidget.class);
intentWidget.setAction(APPNewsWidget.ACTION_WAKEUP_SERVICE);
WinBoLLModel appSOSBean = new WinBoLLModel(bean.getAppPackageName(), bean.getAppMainServiveName());
intentWidget.putExtra("APPSOSBean", appSOSBean.toString());
sendBroadcast(intentWidget);
}
public class APPConnection implements ServiceConnection {
ComponentName mComponentName;
boolean isBindToAPP(WinBoLLModel bean) {
return mComponentName != null
&& mComponentName.getClassName().equals(bean.getAppMainServiveName())
&& mComponentName.getPackageName().equals(bean.getAppPackageName());
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
LogUtils.d(TAG, "onServiceConnected(...)");
mComponentName = name;
LogUtils.d(TAG, String.format("onServiceConnected : \ngetClassName %s\ngetPackageName %s", name.getClassName(), name.getPackageName()));
}
@Override
public void onServiceDisconnected(ComponentName name) {
LogUtils.d(TAG, "onServiceDisconnected(...)");
LogUtils.d(TAG, String.format("onServiceDisconnected : \ngetClassName %s\ngetPackageName %s", name.getClassName(), name.getPackageName()));
// 尝试无参数启动一下服务
String appPackage = mComponentName.getPackageName();
LogUtils.d(TAG, String.format("appPackage %s", appPackage));
String appMainServiceClassName = mComponentName.getClassName();
LogUtils.d(TAG, String.format("appMainServiceClassName %s", appMainServiceClassName));
Intent intentService = new Intent();
intentService.setComponent(new ComponentName(appPackage, appMainServiceClassName));
startService(intentService);
}
}
// 主进程与守护进程连接时需要用到此类
//
private class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
LogUtils.d(TAG, "onServiceConnected(...)");
AssistantService.MyBinder binder = (AssistantService.MyBinder) service;
mAssistantService = binder.getService();
isBound = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
LogUtils.d(TAG, "onServiceDisconnected(...)");
if (mMainServiceBean.isEnable()) {
// 唤醒守护进程
wakeupAndBindAssistant();
}
isBound = false;
mAssistantService = null;
}
}
// 用于返回服务实例的Binder
public class MyBinder extends Binder {
MainService getService() {
LogUtils.d(TAG, "MainService MyBinder getService()");
return MainService.this;
}
}
// //
// // 启动服务
// //
// public static void startControlCenterService(Context context) {
// Intent intent = new Intent(context, MainService.class);
// context.startForegroundService(intent);
// }
//
// //
// // 停止服务
// //
// public static void stopControlCenterService(Context context) {
// Intent intent = new Intent(context, MainService.class);
// context.stopService(intent);
// }
public void appenMessage(String message) {
LogUtils.d(TAG, String.format("Message : %s", message));
}
public static void stopMainService(Context context) {
LogUtils.d(TAG, "stopMainService");
MainServiceBean bean = new MainServiceBean();
bean.setIsEnable(false);
MainServiceBean.saveBean(context, bean);
context.stopService(new Intent(context, MainService.class));
}
public static void startMainService(Context context) {
LogUtils.d(TAG, "startMainService");
MainServiceBean bean = new MainServiceBean();
bean.setIsEnable(true);
MainServiceBean.saveBean(context, bean);
context.startService(new Intent(context, MainService.class));
}
}

View File

@@ -0,0 +1,179 @@
package cc.winboll.studio.appbase.services;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/03/07 12:45:49
* @Describe 启动时申请绑定到APPBase主服务的服务示例
*/
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import cc.winboll.studio.appbase.App;
import cc.winboll.studio.appbase.models.TestDemoBindServiceBean;
import cc.winboll.studio.appbase.services.TestDemoBindService;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.sos.SOS;
import cc.winboll.studio.libappbase.winboll.WinBoLL;
public class TestDemoBindService extends Service {
public static final String TAG = "TestDemoBindService";
public static final String ACTION_ENABLE = TestDemoBindService.class.getName() + ".ACTION_ENABLE";
public static final String ACTION_DISABLE = TestDemoBindService.class.getName() + ".ACTION_DISABLE";
volatile static TestThread _TestThread;
volatile static boolean _IsRunning;
public synchronized static void setIsRunning(boolean isRunning) {
_IsRunning = isRunning;
}
public static boolean isRunning() {
return _IsRunning;
}
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
public class MyBinder extends Binder {
public TestDemoBindService getService() {
return TestDemoBindService.this;
}
}
@Override
public void onCreate() {
super.onCreate();
LogUtils.d(TAG, "onCreate()");
run();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
LogUtils.d(TAG, "onStartCommand(...)");
TestDemoBindServiceBean bean = TestDemoBindServiceBean.loadBean(this, TestDemoBindServiceBean.class);
if (bean == null) {
bean = new TestDemoBindServiceBean();
}
if (intent.getAction() != null) {
if (intent.getAction().equals(ACTION_ENABLE)) {
bean.setIsEnable(true);
LogUtils.d(TAG, "setIsEnable(true);");
TestDemoBindServiceBean.saveBean(this, bean);
} else if (intent.getAction().equals(ACTION_DISABLE)) {
bean.setIsEnable(false);
LogUtils.d(TAG, "setIsEnable(false);");
TestDemoBindServiceBean.saveBean(this, bean);
}
}
run();
return (bean.isEnable()) ? START_STICKY : super.onStartCommand(intent, flags, startId);
//return super.onStartCommand(intent, flags, startId);
}
void run() {
LogUtils.d(TAG, "run()");
TestDemoBindServiceBean bean = TestDemoBindServiceBean.loadBean(this, TestDemoBindServiceBean.class);
if (bean == null) {
bean = new TestDemoBindServiceBean();
TestDemoBindServiceBean.saveBean(this, bean);
}
if (bean.isEnable()) {
LogUtils.d(TAG, "run() bean.isEnable()");
TestThread.getInstance(this).start();
LogUtils.d(TAG, "_TestThread.start()");
}
}
@Override
public void onDestroy() {
super.onDestroy();
LogUtils.d(TAG, "onDestroy()");
TestDemoBindServiceBean bean = TestDemoBindServiceBean.loadBean(this, TestDemoBindServiceBean.class);
if (bean == null) {
bean = new TestDemoBindServiceBean();
}
TestThread.getInstance(this).setIsExit(true);
// 预防 APPBase 应用重启绑定失效。
// 所以退出时检查本服务是否配置启用,如果启用就发送一个 SOS 信号。
// 这样 APPBase 就会用组件方式启动本服务。
if (bean.isEnable()) {
if (App.isDebuging()) {
SOS.sosToAppBaseBeta(this, TestDemoBindService.class.getName());
} else {
SOS.sosToAppBase(this, TestDemoBindService.class.getName());
}
}
_IsRunning = false;
}
static class TestThread extends Thread {
volatile static TestThread _TestThread;
Context mContext;
volatile boolean isStarted = false;
volatile boolean isExit = false;
TestThread(Context context) {
super();
mContext = context;
}
public static synchronized TestThread getInstance(Context context) {
if (_TestThread != null) {
_TestThread.setIsExit(true);
}
_TestThread = new TestThread(context);
return _TestThread;
}
public synchronized void setIsExit(boolean isExit) {
this.isExit = isExit;
}
public boolean isExit() {
return isExit;
}
@Override
public void run() {
if (isStarted == false) {
isStarted = true;
super.run();
LogUtils.d(TAG, "run() start");
if (App.isDebuging()) {
WinBoLL.bindToAPPBaseBeta(mContext, TestDemoBindService.class.getName());
} else {
WinBoLL.bindToAPPBase(mContext, TestDemoBindService.class.getName());
}
while (!isExit()) {
LogUtils.d(TAG, "run()");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
}
LogUtils.d(TAG, "run() exit");
}
}
}
}

View File

@@ -0,0 +1,155 @@
package cc.winboll.studio.appbase.services;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/03/07 12:39:24
* @Describe 普通服务示例
*/
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import cc.winboll.studio.appbase.models.TestDemoServiceBean;
import cc.winboll.studio.libappbase.LogUtils;
public class TestDemoService extends Service {
public static final String TAG = "TestDemoService";
public static final String ACTION_ENABLE = TestDemoService.class.getName() + ".ACTION_ENABLE";
public static final String ACTION_DISABLE = TestDemoService.class.getName() + ".ACTION_DISABLE";
volatile static TestThread _TestThread;
volatile static boolean _IsRunning;
public synchronized static void setIsRunning(boolean isRunning) {
_IsRunning = isRunning;
}
public static boolean isRunning() {
return _IsRunning;
}
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
public class MyBinder extends Binder {
public TestDemoService getService() {
return TestDemoService.this;
}
}
@Override
public void onCreate() {
super.onCreate();
LogUtils.d(TAG, "onCreate()");
run();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
LogUtils.d(TAG, "onStartCommand(...)");
TestDemoServiceBean bean = TestDemoServiceBean.loadBean(this, TestDemoServiceBean.class);
if (bean == null) {
bean = new TestDemoServiceBean();
}
if (intent.getAction() != null) {
if (intent.getAction().equals(ACTION_ENABLE)) {
bean.setIsEnable(true);
LogUtils.d(TAG, "setIsEnable(true);");
TestDemoServiceBean.saveBean(this, bean);
} else if (intent.getAction().equals(ACTION_DISABLE)) {
bean.setIsEnable(false);
LogUtils.d(TAG, "setIsEnable(false);");
TestDemoServiceBean.saveBean(this, bean);
}
}
run();
return (bean.isEnable()) ? START_STICKY : super.onStartCommand(intent, flags, startId);
//return super.onStartCommand(intent, flags, startId);
}
void run() {
LogUtils.d(TAG, "run()");
TestDemoServiceBean bean = TestDemoServiceBean.loadBean(this, TestDemoServiceBean.class);
if (bean == null) {
bean = new TestDemoServiceBean();
TestDemoServiceBean.saveBean(this, bean);
}
if (bean.isEnable()) {
LogUtils.d(TAG, "run() bean.isEnable()");
TestThread.getInstance(this).start();
LogUtils.d(TAG, "_TestThread.start()");
}
}
@Override
public void onDestroy() {
super.onDestroy();
LogUtils.d(TAG, "onDestroy()");
TestThread.getInstance(this).setIsExit(true);
_IsRunning = false;
}
static class TestThread extends Thread {
volatile static TestThread _TestThread;
Context mContext;
volatile boolean isStarted = false;
volatile boolean isExit = false;
TestThread(Context context) {
super();
mContext = context;
}
public static synchronized TestThread getInstance(Context context) {
if (_TestThread != null) {
_TestThread.setIsExit(true);
}
_TestThread = new TestThread(context);
return _TestThread;
}
public synchronized void setIsExit(boolean isExit) {
this.isExit = isExit;
}
public boolean isExit() {
return isExit;
}
@Override
public void run() {
if (isStarted == false) {
isStarted = true;
super.run();
LogUtils.d(TAG, "run() start");
while (!isExit()) {
LogUtils.d(TAG, "run()");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
}
LogUtils.d(TAG, "run() exit");
}
}
}
}

View File

@@ -0,0 +1,59 @@
package cc.winboll.studio.libappbase.sos;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/03/02 09:36:29
* @Describe WinBoLL 应用 SOS 机理保护类
*/
import android.content.Context;
import android.content.Intent;
import cc.winboll.studio.libappbase.LogUtils;
import java.io.IOException;
public class SOS {
public static final String TAG = "SOS";
public static final String ACTION_SOS = SOS.class.getName() + ".ACTION_SOS";
public static final String EXTRA_OBJECT = "EXTRA_OBJECT";
public static void sosToAppBase(Context context, String sosService) {
LogUtils.d(TAG, "sosToAppBase()");
String szToPackage = "cc.winboll.studio.appbase";
sos(context, szToPackage, sosService);
}
public static void sosToAppBaseBeta(Context context, String sosService) {
LogUtils.d(TAG, "sosToAppBaseBeta()");
String szToPackage = "cc.winboll.studio.appbase.beta";
sos(context, szToPackage, sosService);
}
static void sos(Context context, String szToPackage, String sosService) {
LogUtils.d(TAG, "sos(...)");
Intent intent = new Intent(ACTION_SOS);
intent.putExtra(EXTRA_OBJECT, genSOSObject(context.getPackageName(), sosService));
intent.setPackage(szToPackage);
LogUtils.d(TAG, String.format("ACTION_SOS :\nTo Package : %sSOS Service : %s\n", szToPackage, sosService));
context.sendBroadcast(intent);
}
public static SOSObject parseSOSObject(String szSOSObject) {
try {
return SOSObject.parseStringToBean(szSOSObject, SOSObject.class);
} catch (IOException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
return null;
}
public static String sosObjectToString(SOSObject object) {
return object.toString();
}
public static String genSOSObject(String objectPackageName, String objectServiveName) {
return (new SOSObject(objectPackageName, objectServiveName)).toString();
}
}

View File

@@ -0,0 +1,182 @@
package cc.winboll.studio.libappbase.sos;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/02/27 14:00:21
* @Describe Simple Operate Signal Service Center.
* 简单操作信号服务中心
*/
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 java.io.FileDescriptor;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import cc.winboll.studio.libappbase.LogUtils;
public class SOSCenterService extends Service {
public static final String TAG = "SOSCenterService";
private final IBinder binder =(IBinder)new SOSBinder();
SOSCenterServiceModel mSOSCenterServiceModel;
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";
SOSCenterService getService() {
return SOSCenterService.this;
}
}
@Override
public void onCreate() {
super.onCreate();
LogUtils.d(TAG, "onCreate");
mSOSCenterServiceModel = SOSCenterServiceModel.loadBean(this, SOSCenterServiceModel.class);
if(mSOSCenterServiceModel == null) {
mSOSCenterServiceModel = new SOSCenterServiceModel();
SOSCenterServiceModel.saveBean(this, mSOSCenterServiceModel);
}
runMainThread();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
LogUtils.d(TAG, "onStartCommand");
runMainThread();
return mSOSCenterServiceModel.isEnable() ? Service.START_STICKY: super.onStartCommand(intent, flags, startId);
}
void runMainThread() {
mSOSCenterServiceModel = mSOSCenterServiceModel.loadBean(this, SOSCenterServiceModel.class);
if (mSOSCenterServiceModel.isEnable()
&& _MainThread == null) {
getMainThreadInstance().start();
}
}
@Override
public void onDestroy() {
super.onDestroy();
LogUtils.d(TAG, "onDestroy");
mSOSCenterServiceModel = SOSCenterServiceModel.loadBean(this, SOSCenterServiceModel.class);
if (mSOSCenterServiceModel.isEnable()) {
LogUtils.d(TAG, "mSOSCenterServiceModel.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");
SOSCenterServiceModel bean = new SOSCenterServiceModel();
bean.setIsEnable(false);
SOSCenterServiceModel.saveBean(context, bean);
context.stopService(new Intent(context, SOSCenterServiceModel.class));
}
public static void startISOSService(Context context) {
LogUtils.d(TAG, "startISOSService");
SOSCenterServiceModel bean = new SOSCenterServiceModel();
bean.setIsEnable(true);
SOSCenterServiceModel.saveBean(context, bean);
context.startService(new Intent(context, SOSCenterServiceModel.class));
}
public String getMessage() {
return "Hello from SOSCenterServiceModel";
}
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());
}
}
}
}
}

View File

@@ -0,0 +1,69 @@
package cc.winboll.studio.libappbase.sos;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/03/02 09:49:45
* @Describe SOSCenterServiceModel
* Simple Operate Signal Service Model.
*/
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import java.io.IOException;
public class SOSCenterServiceModel extends BaseBean {
public static final String TAG = "SOSCenterServiceModel";
boolean isEnable;
public SOSCenterServiceModel() {
this.isEnable = false;
}
public void setIsEnable(boolean isEnable) {
this.isEnable = isEnable;
}
public boolean isEnable() {
return isEnable;
}
@Override
public String getName() {
return SOSCenterServiceModel.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;
}
}

View File

@@ -0,0 +1,29 @@
package cc.winboll.studio.libappbase.sos;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/02/27 14:04:35
* @Describe SOSCenterServiceReceiver
*/
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import cc.winboll.studio.libappbase.LogUtils;
public class SOSCenterServiceReceiver extends BroadcastReceiver {
public static final String TAG = "SOSCenterServiceReceiver";
public static final String ACTION_SOS = SOSCenterServiceReceiver.class.getName() + ".ACTION_SOS";
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(ACTION_SOS)) {
// 处理接收到的广播消息
LogUtils.d(TAG, String.format("Action %s \n%s\n%s", action));
} else {
LogUtils.d(TAG, String.format("%s", action));
}
}
}

View File

@@ -0,0 +1,86 @@
package cc.winboll.studio.libappbase.sos;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/02/27 14:12:05
* @Describe SOSBean
*/
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import java.io.IOException;
public class SOSObject extends BaseBean {
public static final String TAG = "SOSObject";
String objectPackageName;
String objectServiveName;
public SOSObject() {
this.objectPackageName = "";
this.objectServiveName = "";
}
public SOSObject(String objectPackageName, String objectServiveName) {
this.objectPackageName = objectPackageName;
this.objectServiveName = objectServiveName;
}
public void setObjectPackageName(String objectPackageName) {
this.objectPackageName = objectPackageName;
}
public String getObjectPackageName() {
return objectPackageName;
}
public void setObjectServiveName(String objectServiveName) {
this.objectServiveName = objectServiveName;
}
public String getObjectServiveName() {
return objectServiveName;
}
@Override
public String getName() {
return SOSObject.class.getName();
}
@Override
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
super.writeThisToJsonWriter(jsonWriter);
jsonWriter.name("objectPackageName").value(getObjectPackageName());
jsonWriter.name("objectServiveName").value(getObjectServiveName());
}
@Override
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
if (name.equals("objectPackageName")) {
setObjectPackageName(jsonReader.nextString());
} else if (name.equals("objectServiveName")) {
setObjectServiveName(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;
}
}

View File

@@ -0,0 +1,54 @@
package cc.winboll.studio.appbase.threads;
/**
* @Author ZhanGSKen<zhangsken@qq.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.");
}
}

View File

@@ -0,0 +1,31 @@
package cc.winboll.studio.winboll.utils;
/**
* @Author ZhanGSKen<zhangsken@188.com>
* @Date 2025/06/08 09:05
* @Describe Termux 应用操作工具集
*/
public abstract class TermuxUtils {
public static final String TAG = "TermuxUtils";
private void runTermuxCommand(String command) {
// 1. 创建 Intent指定 Termux 的 RunCommandService
Intent intent = new Intent("com.termux.RUN_COMMAND");
intent.setPackage("com.termux"); // Termux 应用的包名
// 2. 传递命令参数(必填)
//intent.putExtra("command", command);
intent.putExtra("cd ~/WinBoLL&&echo 'WinBoLL cmd exec at (data", command);
// 3. 可选:设置工作目录(默认为 Termux 的 home 目录)
intent.putExtra("dir", "/data/data/com.termux/files/home/WinBoLL");
// 4. 发送 Intent需处理可能的安全异常或 ActivityNotFoundException
try {
getApplicationContext().startService(intent);
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,187 @@
package cc.winboll.studio.appbase.widgets;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/02/15 14:41:25
* @Describe TimeWidget
*/
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.widget.RemoteViews;
import cc.winboll.studio.appbase.R;
import cc.winboll.studio.appbase.receivers.APPNewsWidgetClickListener;
import cc.winboll.studio.libappbase.AppUtils;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.models.APPModel;
import cc.winboll.studio.libappbase.models.WinBoLLNewsBean;
import cc.winboll.studio.libappbase.winboll.WinBoLL;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import cc.winboll.studio.libappbase.models.WinBoLLModel;
public class APPNewsWidget extends AppWidgetProvider {
public static final String TAG = "APPNewsWidget";
public static final String ACTION_WAKEUP_SERVICE = APPNewsWidget.class.getName() + ".ACTION_WAKEUP_SERVICE";
public static final String ACTION_RELOAD_REPORT = APPNewsWidget.class.getName() + ".ACTION_RELOAD_REPORT";
volatile static ArrayList<WinBoLLNewsBean> _WinBoLLNewsBeanList;
final static int _MAX_PAGES = 10;
final static int _OnePageLinesCount = 5;
volatile static int _CurrentPageIndex = 0;
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
initWinBoLLNewsBeanList(context);
for (int appWidgetId : appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId);
}
}
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
initWinBoLLNewsBeanList(context);
if (intent.getAction().equals(ACTION_RELOAD_REPORT)) {
LogUtils.d(TAG, "ACTION_RELOAD_REPORT");
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, APPNewsWidget.class));
for (int appWidgetId : appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId);
}
}else if (intent.getAction().equals(ACTION_WAKEUP_SERVICE)) {
LogUtils.d(TAG, "ACTION_WAKEUP_SERVICE");
String szWinBoLLModel = intent.getStringExtra(WinBoLL.EXTRA_WINBOLLMODEL);
LogUtils.d(TAG, String.format("szWinBoLLModel %s", szWinBoLLModel));
if (szWinBoLLModel != null && !szWinBoLLModel.equals("")) {
try {
WinBoLLModel bean = WinBoLLModel.parseStringToBean(szWinBoLLModel, WinBoLLModel.class);
if (bean != null) {
String szAppPackageName = bean.getAppPackageName();
LogUtils.d(TAG, String.format("szAppPackageName %s", szAppPackageName));
String szAppMainServiveName = bean.getAppMainServiveName();
LogUtils.d(TAG, String.format("szAppMainServiveName %s", szAppMainServiveName));
String appName = AppUtils.getAppNameByPackageName(context, szAppPackageName);
LogUtils.d(TAG, String.format("appName %s", appName));
WinBoLLNewsBean winBollNewsBean = new WinBoLLNewsBean(appName);
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
String currentTime = sdf.format(new Date());
StringBuilder sbLine = new StringBuilder();
sbLine.append("[");
sbLine.append(currentTime);
sbLine.append("] Wake up ");
sbLine.append(appName);
winBollNewsBean.setMessage(sbLine.toString());
addWinBoLLNewsBean(context, winBollNewsBean);
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, APPNewsWidget.class));
for (int appWidgetId : appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId);
}
}
} catch (IOException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
}
}
}
//
// 加入新报告信息
//
public synchronized static void addWinBoLLNewsBean(Context context, WinBoLLNewsBean bean) {
initWinBoLLNewsBeanList(context);
_WinBoLLNewsBeanList.add(0, bean);
// 控制记录总数
while (_WinBoLLNewsBeanList.size() > _MAX_PAGES * _OnePageLinesCount) {
_WinBoLLNewsBeanList.remove(_WinBoLLNewsBeanList.size() - 1);
}
WinBoLLNewsBean.saveBeanList(context, _WinBoLLNewsBeanList, WinBoLLNewsBean.class);
}
synchronized static void initWinBoLLNewsBeanList(Context context) {
if (_WinBoLLNewsBeanList == null) {
_WinBoLLNewsBeanList = new ArrayList<WinBoLLNewsBean>();
WinBoLLNewsBean.loadBeanList(context, _WinBoLLNewsBeanList, WinBoLLNewsBean.class);
}
if (_WinBoLLNewsBeanList == null) {
_WinBoLLNewsBeanList = new ArrayList<WinBoLLNewsBean>();
WinBoLLNewsBean.saveBeanList(context, _WinBoLLNewsBeanList, WinBoLLNewsBean.class);
}
}
private void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
LogUtils.d(TAG, "updateAppWidget(...)");
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_news);
//设置按钮点击事件
Intent intentPre = new Intent(context, APPNewsWidgetClickListener.class);
intentPre.setAction(APPNewsWidgetClickListener.ACTION_PRE);
PendingIntent pendingIntentPre = PendingIntent.getBroadcast(context, 0, intentPre, PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.widget_button_pre, pendingIntentPre);
Intent intentNext = new Intent(context, APPNewsWidgetClickListener.class);
intentNext.setAction(APPNewsWidgetClickListener.ACTION_NEXT);
PendingIntent pendingIntentNext = PendingIntent.getBroadcast(context, 0, intentNext, PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.widget_button_next, pendingIntentNext);
views.setTextViewText(R.id.tv_msg, getPageInfo());
views.setTextViewText(R.id.tv_news, getMessage());
appWidgetManager.updateAppWidget(appWidgetId, views);
}
public static String getMessage() {
ArrayList<String> msgTemp = new ArrayList<String>();
if (_WinBoLLNewsBeanList != null) {
int start = _OnePageLinesCount * _CurrentPageIndex;
start = _WinBoLLNewsBeanList.size() > start ? start : _WinBoLLNewsBeanList.size() - 1;
for (int i = start, j = 0; i < _WinBoLLNewsBeanList.size() && j < _OnePageLinesCount && start > -1; i++, j++) {
msgTemp.add(_WinBoLLNewsBeanList.get(i).getMessage());
}
String message = String.join("\n", msgTemp);
return message;
}
return "";
}
public static void prePage(Context context) {
if (_WinBoLLNewsBeanList != null) {
if (_CurrentPageIndex > 0) {
_CurrentPageIndex = _CurrentPageIndex - 1;
}
Intent intentWidget = new Intent(context, APPNewsWidget.class);
intentWidget.setAction(APPNewsWidget.ACTION_RELOAD_REPORT);
context.sendBroadcast(intentWidget);
}
}
public static void nextPage(Context context) {
if (_WinBoLLNewsBeanList != null) {
if ((_CurrentPageIndex + 1) * _OnePageLinesCount < _WinBoLLNewsBeanList.size()) {
_CurrentPageIndex = _CurrentPageIndex + 1;
}
Intent intentWidget = new Intent(context, APPNewsWidget.class);
intentWidget.setAction(APPNewsWidget.ACTION_RELOAD_REPORT);
context.sendBroadcast(intentWidget);
}
}
String getPageInfo() {
if (_WinBoLLNewsBeanList == null) {
return "0/0";
}
int leftCount = _WinBoLLNewsBeanList.size() % _OnePageLinesCount;
int currentPageCount = _WinBoLLNewsBeanList.size() / _OnePageLinesCount + (leftCount == 0 ?0: 1);
return String.format("%d/%d", _CurrentPageIndex + 1, currentPageCount);
}
}

View File

@@ -0,0 +1,63 @@
package cc.winboll.studio.appbase.widgets;
/**
* @Author ZhanGSKen<zhangsken@qq.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 android.content.ServiceConnection;
import android.os.IBinder;
import cc.winboll.studio.libappbase.utils.ToastUtils;
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)) {
ToastUtils.show("Test");
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);
}
}

View File

@@ -0,0 +1,33 @@
package cc.winboll.studio.libappbase.widgets;
/**
* @Author ZhanGSKen<zhangsken@qq.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 cc.winboll.studio.libappbase.utils.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));
}
}
}

View File

@@ -0,0 +1,34 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeColor="#00000000"
android:strokeWidth="1">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeColor="#00000000"
android:strokeWidth="1" />
</vector>

View 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代表右边的阴影宽度。其实也就是相对应的offsetsolid中的颜色是阴影的颜色也可以设置角度等等 -->
<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>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#ff000000"
android:pathData="M4,1C2.89,1 2,1.89 2,3V7C2,8.11 2.89,9 4,9H1V11H13V9H10C11.11,9 12,8.11 12,7V3C12,1.89 11.11,1 10,1H4M4,3H10V7H4V3M3,12V14H5V12H3M14,13C12.89,13 12,13.89 12,15V19C12,20.11 12.89,21 14,21H11V23H23V21H20C21.11,21 22,20.11 22,19V15C22,13.89 21.11,13 20,13H14M3,15V17H5V15H3M14,15H20V19H14V15M3,18V20H5V18H3M6,18V20H8V18H6M9,18V20H11V18H9Z"/>
</vector>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#ff000000"
android:pathData="M4,1C2.89,1 2,1.89 2,3V7C2,8.11 2.89,9 4,9H1V11H13V9H10C11.11,9 12,8.11 12,7V3C12,1.89 11.11,1 10,1H4M4,3H10V7H4V3M14,13C12.89,13 12,13.89 12,15V19C12,20.11 12.89,21 14,21H11V23H23V21H20C21.11,21 22,20.11 22,19V15C22,13.89 21.11,13 20,13H14M3.88,13.46L2.46,14.88L4.59,17L2.46,19.12L3.88,20.54L6,18.41L8.12,20.54L9.54,19.12L7.41,17L9.54,14.88L8.12,13.46L6,15.59L3.88,13.46M14,15H20V19H14V15Z"/>
</vector>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#ff000000"
android:pathData="M22,6C22,4.9 21.1,4 20,4H4C2.9,4 2,4.9 2,6V18C2,19.1 2.9,20 4,20H20C21.1,20 22,19.1 22,18V6M20,6L12,11L4,6H20M20,18H4V8L12,13L20,8V18Z"/>
</vector>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#ff000000"
android:pathData="M24,7H22V13H24V7M24,15H22V17H24V15M20,6C20,4.9 19.1,4 18,4H2C0.9,4 0,4.9 0,6V18C0,19.1 0.9,20 2,20H18C19.1,20 20,19.1 20,18V6M18,6L10,11L2,6H18M18,18H2V8L10,13L18,8V18Z"/>
</vector>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="1565dp"
android:height="1565dp"
android:viewportWidth="1565"
android:viewportHeight="1565">
<path
android:fillColor="#FFFFFFFF"
android:strokeColor="#FFFFFFFF"
android:strokeWidth="4.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M793.8 222.7C782.5 239.2 736.3 298.6 685.6 361.9 647 410.2 633.3 428.9 633.6 432.7 633.7 433.4 633.8 434.6 633.9 435.3 634.1 437.8 647.9 440.7 663.8 441.5 706.7 443.8 717 445.3 724.9 450.6 733.7 456.4 735.9 467.4 736 504.2 736 504.2 736 521.9 736 521.9 736 521.9 730.8 522.4 730.8 522.4 727.9 522.8 691.8 525.3 650.5 528 609.3 530.8 571.9 533.3 567.5 533.6 531.8 536 503.4 540.5 487.4 546 475 550.3 442.2 566.5 428.2 575.2 392.1 597.6 360.1 629.2 338 664.3 317 697.7 304.6 729.6 292.5 781 287.8 801 283.9 805.6 267.2 811 255.1 814.9 248.7 818.3 243.4 823.5 239.7 827.2 239.2 828.4 237.7 836.5 236.7 841.5 235.5 850 235 855.5 234.3 863.9 232.6 911.1 232.6 924.5 232.6 934.6 234.2 959.8 235 963 237.4 972 242.3 975.8 258.7 981.7 271.7 986.3 277.4 989.5 280.4 993.9 283.1 997.9 286.5 1008.7 287.4 1016.5 289.3 1031.7 293.3 1050.2 297.7 1064 306.4 1091.9 317.6 1107.7 330 1109.5 332.4 1109.9 334.7 1110.3 335 1110.5 335.3 1110.6 339.1 1111.1 343.5 1111.4 347.9 1111.8 353.8 1112.3 356.5 1112.5 370 1113.7 407.8 1115.8 430 1116.5 439.6 1116.8 453.4 1117.3 460.5 1117.5 467.7 1117.8 482.3 1118.2 493 1118.5 503.7 1118.8 520.8 1119.2 531 1119.5 587.8 1121.1 683 1122 794.5 1122 918.8 1122 996.1 1121 1075 1118.5 1083.5 1118.2 1098.6 1117.8 1108.5 1117.4 1118.4 1117.1 1129.4 1116.7 1133 1116.5 1136.6 1116.3 1144.7 1115.8 1151 1115.5 1183.3 1114 1207.5 1110.9 1221.5 1106.3 1235.3 1101.9 1244.8 1094.1 1249.9 1083.2 1253.5 1075.4 1253.7 1074.4 1259 1044 1266.4 1001.7 1269.1 993.5 1276.9 989.2 1278.9 988.1 1284.7 986.1 1289.9 984.6 1305.8 980.1 1313.3 975.5 1314.4 969.7 1316.2 959.5 1317.1 881.4 1315.7 859.5 1314 834.8 1313.1 831.4 1307 824.7 1301.2 818.3 1295 814.8 1283 811 1278 809.5 1272.4 807 1270.5 805.6 1264 800.6 1259.5 790.1 1252.5 763 1243.5 728.4 1235.9 706.8 1224.6 683.5 1202.3 637.7 1167.5 602.5 1111.8 569.1 1087.6 554.7 1054.4 542.7 1028.1 539 1024.1 538.4 1020.5 537.8 1020.1 537.6 1019.8 537.4 1016.4 536.9 1012.7 536.5 1009 536.2 1005.1 535.7 1004.2 535.5 999.7 534.7 988.4 533.6 973.5 532.5 969.7 532.2 964.5 531.8 962 531.5 959.5 531.2 940.4 530.1 919.5 529 851.7 525.4 836.1 523.7 829.5 519.4 821.2 514 825.2 495.6 837.6 481.8 843.8 474.9 844.2 474.3 856.8 448 862.6 436 868.9 425.2 879.9 408.7 888.2 396.2 895 385.5 895 385.1 895 384.6 889.7 383.3 883.3 382 851.1 375.9 812.7 367.8 806.9 365.9 792.4 361.2 790.7 358.7 790.2 342.5 789.7 326.8 792.4 303.4 800 258.5 802.1 246.1 803.3 220.9 801.9 219.6 801.7 219.4 800.5 218.9 799.3 218.6 797.4 218 796.5 218.7 793.8 222.7Z"/>
<path
android:fillColor="#FF62686C"
android:strokeColor="#FF62686C"
android:strokeWidth="4.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M570.4 704.1C553.5 706.1 539.7 713 532.9 722.7 523.5 736.3 519.9 764.2 519.6 826 519.5 859.4 519.7 860.6 530.1 872.9 543.4 888.6 560.4 900 574.1 902.1 586.7 904 601.5 898.9 615.7 887.5 632.5 874.1 633.5 868.9 632.7 793.6 632.2 742.5 631.6 736 627 725.2 620.5 710 596.2 700.9 570.4 704.1Z"/>
<path
android:fillColor="#FF62686C"
android:strokeColor="#FF62686C"
android:strokeWidth="4.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M949.2 705.5C935.1 708.7 927 716.8 922.6 732.4 918.4 746.7 917.8 757.3 917.3 816.5 917.1 847.8 917.3 874 917.7 874.6 919.2 876.7 945.3 892.3 955.5 897.1 973.4 905.5 983.5 905.4 978.9 896.8 977.1 893.4 979.1 892.6 985.9 893.9 993.6 895.4 997.8 894.3 1005.2 889.1 1010.9 885 1020.2 873.6 1023.8 866.5 1029.6 854.9 1031.1 841.6 1031.1 800.5 1031.1 750.1 1028.6 737.6 1014.9 720.4 1009.4 713.5 1004.6 709.8 996.9 706.8 992.2 704.9 989.1 704.6 973.5 704.4 960.6 704.1 953.7 704.5 949.2 705.5Z"/>
<path
android:fillColor="#FF62686C"
android:strokeColor="#FF62686C"
android:strokeWidth="4.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M717 988.7C671.1 990.4 640.9 994.2 635.6 998.8 631.5 1002.5 636.3 1017.5 643.6 1024 650.9 1030.3 661.7 1032.5 694 1034 721.9 1035.3 829.3 1035.3 857 1034 891.4 1032.4 901.4 1029.9 908.9 1021.3 914.5 1015 917.9 1003.4 915.2 999.4 910.4 992.1 856 987.9 772 988.2 745.9 988.3 721.1 988.5 717 988.7Z"/>
</vector>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
android:clickable="true">
<item
android:width="256dp"
android:height="256dp"
android:left="0dp"
android:top="0dp"
android:right="0dp"
android:bottom="0dp"
android:drawable="@drawable/ic_winboll_logo">
</item>
</layer-list>

View File

@@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillColor="#26A69A"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
</vector>

View 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="48dp"
android:layout_height="48dp">
<item android:drawable="@drawable/ic_launcher_background"/>
<item
android:left="0dp"
android:top="0dp"
android:right="0dp"
android:bottom="0dp"
android:drawable="@drawable/ic_iw"/>
</layer-list>

View 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>

View 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>

View 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="#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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -0,0 +1,27 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="512dp"
android:height="512dp"
android:viewportWidth="512"
android:viewportHeight="512">
<path
android:fillColor="#FF1E9B54"
android:strokeColor="#FFF8E733"
android:strokeWidth="20.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M254.63 35.45C374.95 35.45 473.38 133.89 473.38 254.2 473.38 374.51 374.95 472.95 254.63 472.95 134.32 472.95 35.88 374.51 35.88 254.2 35.88 133.89 134.32 35.45 254.63 35.45"/>
<path
android:fillColor="#FF000000"
android:strokeColor="#FF000000"
android:strokeWidth="1.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M257.28 361.25C266.56 361.25 274.14 368.84 274.14 378.11 274.14 387.39 266.56 394.98 257.28 394.98 248.01 394.98 240.42 387.39 240.42 378.11 240.42 368.84 248.01 361.25 257.28 361.25"/>
<path
android:fillColor="#00000000"
android:strokeColor="#FF000000"
android:strokeWidth="30.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M182.16 214.09C181.42 199.71 182.42 177.87 207.64 155.49 213.64 150.16 220.13 146.12 226.28 143.08 238.64 136.97 249.62 134.91 252.55 134.56 252.7 134.54 252.83 134.53 252.94 134.52 253.05 134.51 253.14 134.5 253.2 134.5 255.01 134.48 294.9 136.66 313.05 160.43 332.29 185.63 344.82 221.3 300.07 263.56 263.08 298.49 258.36 318 258.54 317.72"/>
</vector>

View File

@@ -0,0 +1,41 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="512dp"
android:height="512dp"
android:viewportWidth="512"
android:viewportHeight="512">
<path
android:fillColor="#FF1E9B54"
android:strokeColor="#FFF8E733"
android:strokeWidth="20.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M254.63 35.45C374.95 35.45 473.38 133.89 473.38 254.2 473.38 374.51 374.95 472.95 254.63 472.95 134.32 472.95 35.88 374.51 35.88 254.2 35.88 133.89 134.32 35.45 254.63 35.45"/>
<path
android:fillColor="#FFFFFFFF"
android:strokeColor="#FF000000"
android:strokeWidth="20.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M151.49 130.96C151.49 130.96 348.53 130.96 348.53 130.96 348.53 130.96 348.53 393.46 348.53 393.46 348.53 393.46 151.49 393.46 151.49 393.46 151.49 393.46 151.49 130.96 151.49 130.96"/>
<path
android:fillColor="#FFFFFFFF"
android:strokeColor="#FF000000"
android:strokeWidth="20.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M186.28 207.75C186.28 207.75 304.95 207.75 304.95 207.75 304.95 207.75 304.95 205.97 304.95 205.97 304.95 205.97 186.28 205.97 186.28 205.97 186.28 205.97 186.28 207.75 186.28 207.75"/>
<path
android:fillColor="#FFFFFFFF"
android:strokeColor="#FF000000"
android:strokeWidth="20.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M186.28 263.52C186.28 263.52 304.95 263.52 304.95 263.52 304.95 263.52 304.95 264.41 304.95 264.41 304.95 264.41 186.28 264.41 186.28 264.41 186.28 264.41 186.28 263.52 186.28 263.52"/>
<path
android:fillColor="#FFFFFFFF"
android:strokeColor="#FF000000"
android:strokeWidth="20.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M186.28 323.62C186.28 323.62 304.95 323.62 304.95 323.62 304.95 323.62 304.95 320.62 304.95 320.62 304.95 320.62 186.28 320.62 186.28 320.62 186.28 320.62 186.28 323.62 186.28 323.62"/>
</vector>

View File

@@ -0,0 +1,48 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="512dp"
android:height="512dp"
android:viewportWidth="512"
android:viewportHeight="512">
<path
android:fillColor="#FF1E9B54"
android:strokeColor="#FFF8E733"
android:strokeWidth="20.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M254.63 35.45C374.95 35.45 473.38 133.89 473.38 254.2 473.38 374.51 374.95 472.95 254.63 472.95 134.32 472.95 35.88 374.51 35.88 254.2 35.88 133.89 134.32 35.45 254.63 35.45"/>
<path
android:fillColor="#FFFFFFFF"
android:strokeColor="#FFFFFFFF"
android:strokeWidth="1.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M257.28 361.25C266.56 361.25 274.14 368.84 274.14 378.11 274.14 387.39 266.56 394.98 257.28 394.98 248.01 394.98 240.42 387.39 240.42 378.11 240.42 368.84 248.01 361.25 257.28 361.25"/>
<path
android:fillColor="#00000000"
android:strokeColor="#FF000000"
android:strokeWidth="30.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M182.16 214.09C181.42 199.71 182.42 177.87 207.64 155.49 213.64 150.16 220.13 146.12 226.28 143.08 238.64 136.97 249.62 134.91 252.55 134.56 252.7 134.54 252.83 134.53 252.94 134.52 253.05 134.51 253.14 134.5 253.2 134.5 255.01 134.48 294.9 136.66 313.05 160.43 332.29 185.63 344.82 221.3 300.07 263.56 263.08 298.49 258.36 318 258.54 317.72"/>
<path
android:fillColor="#00000000"
android:strokeColor="#FFFFFFFF"
android:strokeWidth="30.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M103.77 307.45C103.02 293.07 104.03 271.24 129.24 248.85 135.25 243.52 141.74 239.48 147.89 236.44 160.24 230.34 171.23 228.28 174.15 227.92 174.31 227.9 174.44 227.89 174.55 227.88 174.66 227.87 174.75 227.86 174.81 227.86 176.62 227.85 216.5 230.02 234.65 253.79 253.9 278.99 266.43 314.66 221.67 356.93 184.69 391.85 179.97 411.36 180.15 411.08"/>
<path
android:fillColor="#00000000"
android:strokeColor="#FFFFFFFF"
android:strokeWidth="30.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M248.17 309.83C247.43 295.45 248.43 273.62 273.64 251.23 279.65 245.9 286.14 241.86 292.29 238.82 304.65 232.72 315.63 230.65 318.55 230.3 318.71 230.28 318.84 230.27 318.95 230.26 319.06 230.25 319.15 230.24 319.21 230.24 321.02 230.22 360.9 232.4 379.06 256.17 398.3 281.37 410.83 317.04 366.08 359.31 329.09 394.23 324.37 413.74 324.55 413.46"/>
<path
android:fillColor="#00000000"
android:strokeColor="#FFFFFFFF"
android:strokeWidth="30.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M182.16 214.09C181.42 199.71 182.42 177.87 207.64 155.49 213.64 150.16 220.13 146.12 226.28 143.08 238.64 136.97 249.62 134.91 252.55 134.56 252.7 134.54 252.83 134.53 252.94 134.52 253.05 134.51 253.14 134.5 253.2 134.5 255.01 134.48 294.9 136.66 313.05 160.43 332.29 185.63 344.82 221.3 300.07 263.56 263.08 298.49 258.36 318 258.54 317.72"/>
</vector>

View File

@@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="512dp"
android:height="512dp"
android:viewportWidth="512"
android:viewportHeight="512">
<path
android:fillColor="#FF1E9B54"
android:strokeColor="#FFF8E733"
android:strokeWidth="20.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M254.63 35.45C374.95 35.45 473.38 133.89 473.38 254.2 473.38 374.51 374.95 472.95 254.63 472.95 134.32 472.95 35.88 374.51 35.88 254.2 35.88 133.89 134.32 35.45 254.63 35.45"/>
<path
android:fillColor="#FFFFFFFF"
android:strokeColor="#FFFFFFFF"
android:strokeWidth="1.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M257.28 361.25C266.56 361.25 274.14 368.84 274.14 378.11 274.14 387.39 266.56 394.98 257.28 394.98 248.01 394.98 240.42 387.39 240.42 378.11 240.42 368.84 248.01 361.25 257.28 361.25"/>
</vector>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:angle="180"
android:endColor="#FFFFFFFF"
android:startColor="#FFFFFFFF"
android:type="linear" />
<corners android:radius="10dp" />
</shape>

View 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>

View File

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:gravity="right"
android:padding="10dp"
android:id="@+id/ll_hostbar">
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="10.8.0.250:456"
android:id="@+id/rb_debughost"
android:onClick="onSwitchHost"/>
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="yun.winboll.cc"
android:id="@+id/rb_yunhost"
android:onClick="onSwitchHost"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Test RSA"
android:onClick="onTestRSA"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Test Login"
android:onClick="onTestLogin"/>
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0">
<cc.winboll.studio.libappbase.LogView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/logview"/>
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<cc.winboll.studio.winboll.CustomToolbar
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:toolbarTitle="@string/app_name"
app:toolbarTitleColor="@color/colorAccent"
app:toolbarBackgroundColor="@color/colorPrimary"
android:id="@+id/toolbar"/>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0"
android:gravity="center_vertical|center_horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello, WinBoLL!"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<cc.winboll.studio.winboll.TerminalView
android:id="@+id/terminalView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0"/>
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0">
<cc.winboll.studio.libappbase.LogView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/logview"/>
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,215 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/toolbar"/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0">
<LinearLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center">
<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="Android 11内部代号 Red Velvet Cake是谷歌于2020年9月8日正式发布的移动端操作系统核心升级聚焦隐私安全强化、通话与通知体验优化、5G与折叠屏适配三大方向为用户和开发者提供更安全、灵活的使用与开发环境。\n\n一、核心升级隐私安全最关键变化\n\nAndroid 11进一步收紧应用权限管控从“被动授权”转向“主动保护用户数据”核心变化包括\n\n- 一次性权限:首次申请定位、麦克风、相机等敏感权限时,新增“仅本次允许”选项,应用退出后权限自动失效,避免长期授权泄露数据。\n- 后台位置权限限制:应用需先获得“前台定位权限”,才能申请“后台定位”,且用户可单独关闭后台定位(此前需完全关闭定位权限),减少后台偷跑定位的情况。\n- 存储权限细分:将原“存储”权限拆分为“媒体文件”和“所有文件”,普通应用仅能访问媒体文件,需访问其他文件(如文档)时需单独申请“所有文件访问权限”,降低存储数据泄露风险。\n\n二、体验优化通知与通话\n\n- 通知分类管理:通知栏新增“对话”“提醒”“无声”三大分类,微信、短信等实时对话会单独归为“对话”分类,支持置顶、标记未读,重要信息更易查找。\n- 通话与通知优先级优化:来电、闹钟等重要提醒会以“浮动窗口”形式显示(非全屏遮挡),不打断当前操作(如看视频、玩游戏),用户可快速选择接听/挂断。\n- 通知历史记录新增“通知历史”功能可查看过去24小时内被清除的通知避免误删重要信息。\n\n三、开发者与硬件适配\n\n- 5G网络优化新增5G网络状态API支持应用识别“5G高速”“5G低延迟”等场景例如视频App可自动切换高清画质游戏App可优化网络传输策略。\n- 折叠屏适配提供“折叠状态监听”“多窗口模式增强”等API帮助开发者适配折叠屏设备避免屏幕折叠/展开时界面错乱。\n- Scoped Storage分区存储强制启用要求应用适配“分区存储”机制Android 10为可选应用默认只能访问自身专属目录和公共媒体目录无法随意读写手机存储提升系统安全性。\n\n四、其他实用功能\n\n- 电源菜单集成控制长按电源键时可直接显示智能家居控制如灯光、空调无需单独打开智能家居App。\n- 无线调试支持通过Wi-Fi连接电脑进行ADB调试无需USB线方便开发者测试设备。\n- 屏幕录制升级:系统自带屏幕录制功能支持录制声音(包括麦克风声音),并可选择是否显示触控操作。\n\n综上Android 11的核心定位是“安全与体验的精细化升级”通过隐私权限的层层收紧保护用户数据同时针对5G、折叠屏等新硬件做了前瞻性适配是Android系统向“更安全、更智能”迭代的重要版本。"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Edit TimeStamp: 20250915_180359_071"/>
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:orientation="horizontal"
android:layout_height="wrap_content"
android:gravity="right|center_vertical"
android:layout_width="wrap_content">
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Debug Mode"
android:layout_weight="1.0"
android:onClick="onSwitchDebugMode"
android:id="@+id/activitymainCheckBox1"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Test Application CrashReport"
android:textAllCaps="false"
android:onClick="onTestApplicationCrashReport"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="PreviewGlobalCrashActivity"
android:textAllCaps="false"
android:onClick="onPreviewGlobalCrashActivity"/>
</LinearLayout>
</HorizontalScrollView>
<ScrollView
android:layout_width="match_parent"
android:layout_height="400dp">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right">
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="StartCenter"
android:textAllCaps="false"
android:onClick="onStartCenter"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="StopCenter"
android:textAllCaps="false"
android:onClick="onStopCenter"/>
</LinearLayout>
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="SartTestDemoService"
android:textAllCaps="false"
android:onClick="onSartTestDemoService"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="StopTestDemoService"
android:textAllCaps="false"
android:onClick="onStopTestDemoService"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="StopTestDemoServiceNoSettings"
android:textAllCaps="false"
android:onClick="onStopTestDemoServiceNoSettings"/>
</LinearLayout>
</HorizontalScrollView>
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="SartTestDemoBindService"
android:textAllCaps="false"
android:onClick="onSartTestDemoBindService"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="StopTestDemoBindService"
android:textAllCaps="false"
android:onClick="onStopTestDemoBindService"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="StopTestDemoBindServiceNoSettings"
android:textAllCaps="false"
android:onClick="onStopTestDemoBindServiceNoSettings"/>
</LinearLayout>
</HorizontalScrollView>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TestStopMainServiceWithoutSettingEnable"
android:textAllCaps="false"
android:onClick="onTestStopMainServiceWithoutSettingEnable"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TestUseComponentStartService"
android:textAllCaps="false"
android:onClick="onTestUseComponentStartService"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TestDemoServiceSOS"
android:textAllCaps="false"
android:onClick="onTestDemoServiceSOS"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TestOpenNewActivity"
android:textAllCaps="false"
android:onClick="onTestOpenNewActivity"/>
</LinearLayout>
</ScrollView>
</LinearLayout>
</LinearLayout>
</ScrollView>
</LinearLayout>

View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/toolbar"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="NewActivity"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="CloseThisActivity"
android:textAllCaps="false"
android:onClick="onCloseThisActivity"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="CloseAllActivity"
android:textAllCaps="false"
android:onClick="onCloseAllActivity"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="New2Activity"
android:textAllCaps="false"
android:onClick="onNew2Activity"/>
</LinearLayout>

View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/toolbar"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="New2Activity"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="CloseThisActivity"
android:textAllCaps="false"
android:onClick="onCloseThisActivity"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="CloseAllActivity"
android:textAllCaps="false"
android:onClick="onCloseAllActivity"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="NewActivity"
android:textAllCaps="false"
android:onClick="onNewActivity"/>
</LinearLayout>

View File

@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:gravity="right"
android:padding="10dp"
android:id="@+id/ll_hostbar">
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="10.8.0.250:456"
android:id="@+id/rb_debughost"
android:onClick="onSwitchHost"/>
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="yun.winboll.cc"
android:id="@+id/rb_yunhost"
android:onClick="onSwitchHost"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:gravity="right">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TestYun"
android:onClick="onTestYun"/>
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0">
<cc.winboll.studio.libappbase.LogView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/logview"/>
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
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">
<ImageView
android:id="@+id/toolbar_icon"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_iw"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_marginStart="6dp"
android:contentDescription="@string/toolbar_icon_description" />
<TextView
android:id="@+id/toolbar_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#ffffff"
android:textSize="18sp"
android:layout_centerVertical="true"
android:layout_toEndOf="@id/toolbar_icon"
android:layout_marginStart="16dp" />
</RelativeLayout>

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#FFFFFFFF">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right|center_vertical">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="@+id/tv_title"
android:layout_weight="1.0"
android:text="WinBoLLNews"
android:textStyle="bold"
android:textSize="16sp"/>
<Button
android:layout_width="48dp"
android:layout_height="48dp"
android:text="⇦"
android:id="@+id/widget_button_pre"/>
<Button
android:layout_width="48dp"
android:layout_height="48dp"
android:text="⇨"
android:id="@+id/widget_button_next"/>
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/tv_msg"/>
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:id="@+id/tv_news"
android:layout_weight="1.0"/>
</LinearLayout>

View 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>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/item_home"
android:title="HOME"
android:icon="@drawable/ic_winboll"/>
<item
android:id="@+id/item_yun"
android:title="YUN"
android:icon="@drawable/ic_winboll"/>
<item
android:id="@+id/item_logon"
android:title="Logon"
android:icon="@drawable/ic_winboll"/>
<item
android:id="@+id/item_log"
android:title="LOG"
android:icon="@drawable/ic_winboll_log"/>
</menu>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CustomToolbar">
<attr name="toolbarTitle" format="string" />
<attr name="toolbarTitleColor" format="color" />
<attr name="toolbarBackgroundColor" format="color" />
</declare-styleable>
</resources>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#009688</color>
<color name="colorPrimaryDark">#00796B</color>
<color name="colorAccent">#FF9800</color>
</resources>

View File

@@ -0,0 +1,4 @@
<resources>
<string name="app_name">WinBoLL</string>
<string name="toolbar_icon_description">WinBoLL APP</string>
</resources>

View File

@@ -0,0 +1,10 @@
<resources>
<!-- Base application theme. -->
<style name="MyAppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<!-- 允许访问 winboll.cc 及其子域名(原配置) -->
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">winboll.cc</domain>
</domain-config>
<!-- **新增:允许访问 IP 地址 10.8.0.250** -->
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="false">10.8.0.250</domain> <!-- 不包含子域名 -->
</domain-config>
</network-security-config>

View File

@@ -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>