Compare commits

...

37 Commits

Author SHA1 Message Date
ZhanGSKen
13e0ad3f03 <timestamp>APK 15.0.6 release Publish. 2025-05-06 20:37:48 +08:00
ZhanGSKen
be85ef923e 更新应用图标,优化状态栏UI。 2025-05-06 20:36:51 +08:00
ZhanGSKen
9a3383a43b <timestamp>APK 15.0.5 release Publish. 2025-05-06 18:33:59 +08:00
ZhanGSKen
d875b6965d <timestamp>APK 15.0.4 release Publish. 2025-05-06 18:33:32 +08:00
ZhanGSKen
bc873852c2 修复时间戳复制格式设置未保存问题 2025-05-06 18:32:51 +08:00
ZhanGSKen
b032de55dc 添加说明书 2025-05-06 18:14:34 +08:00
ZhanGSKen
8fa20b56ec <timestamp>APK 15.0.3 release Publish. 2025-05-06 18:01:46 +08:00
ZhanGSKen
1ca93a610e 优化常驻通知电量消耗 2025-05-06 17:59:57 +08:00
ZhanGSKen
6555346618 重构通知栏模块 2025-05-06 17:04:14 +08:00
ZhanGSKen
df6633046c <timestamp>APK 15.0.2 release Publish. 2025-05-06 11:34:02 +08:00
ZhanGSKen
7d35d5a9b8 添加通知栏打开主窗口功能 2025-05-06 11:33:28 +08:00
ZhanGSKen
ddce5646bd <timestamp>APK 15.0.1 release Publish. 2025-05-06 11:27:46 +08:00
ZhanGSKen
3607a99053 添加通知栏清理功能 2025-05-06 11:27:09 +08:00
ZhanGSKen
6a101d8843 <timestamp>Start New Stage Version. 2025-05-06 11:17:47 +08:00
ZhanGSKen
c5e5e76bbf <timestamp>APK 15.0.0 release Publish. 2025-05-06 11:17:44 +08:00
ZhanGSKen
6d05808a2a 设置目标版本 2025-05-06 11:13:35 +08:00
ZhanGSKen
2625ffe6a6 通知栏优先级设置调试 2025-05-06 11:11:45 +08:00
ZhanGSKen
9c4c1320aa 添加拷贝时间戳到剪贴板功能 2025-05-06 11:07:28 +08:00
ZhanGSKen
551b424960 添加时间戳拷贝与显示的格式设置功能 2025-05-06 10:48:53 +08:00
ZhanGSKen
1062c7d2ee 重构常驻通知栏 2025-05-06 10:09:34 +08:00
ZhanGSKen
e958556073 0937 2025-05-05 21:37:22 +08:00
ZhanGSKen
466e061aa8 0343 2025-05-05 15:43:41 +08:00
ZhanGSKen
2f2512cd76 修复时间戳复制按钮响应 2025-05-05 12:39:45 +08:00
ZhanGSKen
d26de8f2f7 1153 2025-05-05 11:53:48 +08:00
ZhanGSKen
2e9b6ae263 添加通知栏时间戳显示 2025-05-05 11:30:41 +08:00
ZhanGSKen
f0a29dc7a9 添加常驻通知栏 2025-05-05 10:45:25 +08:00
ZhanGSKen
125c2b4431 初始化 TimeStamp 时间戳应用。 2025-05-04 22:03:06 +08:00
ZhanGSKen
03ceaf3e2a Merge remote-tracking branch 'origin/powerbell' into appbase 2025-05-04 13:23:10 +08:00
ZhanGSKen
7ff2cab615 源码整理 2025-05-03 19:36:03 +08:00
ZhanGSKen
954139f0c9 Merge remote-tracking branch 'origin/appbase' into aes 2025-05-03 18:34:10 +08:00
ZhanGSKen
027dc23060 更新Maven库地址设置 2025-05-03 18:33:41 +08:00
ZhanGSKen
9816167e87 优化通知栏显示文字 2025-05-03 14:22:27 +08:00
ZhanGSKen
512c321b76 优化常驻通知栏显示UI 2025-05-03 14:19:32 +08:00
ZhanGSKen
482c007481 应用服务启动后延迟2秒执行提醒任务 2025-05-03 14:01:56 +08:00
ZhanGSKen
609182f909 编译测试 2025-05-03 13:53:48 +08:00
ZhanGSKen
eb69a473a3 调整类库引用顺序 2025-05-03 13:53:32 +08:00
ZhanGSKen
e772563fc0 模块清理 2025-05-02 06:16:08 +08:00
66 changed files with 2275 additions and 937 deletions

3
.gitmodules vendored
View File

@@ -1,6 +1,3 @@
[submodule "winboll-x"]
path = winboll-x
url = https://gitea.winboll.cc/Studio/WinBollX.git
[submodule "libjc/jcc/libs"]
path = libjc/jcc/libs
url = https://gitea.winboll.cc/Studio/APP_libjc_jcc_libs.git

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Tue Apr 29 15:14:41 HKT 2025
#Sun May 04 06:42:28 GMT 2025
stageCount=1
libraryProject=libaes
baseVersion=15.6
publishVersion=15.6.0
buildCount=0
buildCount=10
baseBetaVersion=15.6.1

View File

@@ -10,13 +10,14 @@ import android.content.Context;
import android.os.Bundle;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.libaes.winboll.APPInfo;
import cc.winboll.studio.libaes.winboll.AboutView;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
public class AboutActivity extends WinBoLLActivity implements IWinBoLLActivity {
public class AboutActivity extends AppCompatActivity implements IWinBoLLActivity {
public static final String TAG = "AboutActivity";

View File

@@ -1,60 +0,0 @@
package cc.winboll.studio.aes;
import android.app.Activity;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import cc.winboll.studio.libaes.beans.AESThemeBean;
import cc.winboll.studio.libaes.utils.AESThemeUtil;
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
import android.view.MenuItem;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/03/30 00:34:02
* @Describe WinBoLL 活动窗口通用基类
*/
public class WinBoLLActivity extends AppCompatActivity implements IWinBoLLActivity {
public static final String TAG = "WinBoLLActivity";
protected volatile AESThemeBean.ThemeType mThemeType;
@Override
public Activity getActivity() {
return this;
}
@Override
public String getTag() {
return TAG;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
mThemeType = getThemeType();
setThemeStyle();
super.onCreate(savedInstanceState);
}
AESThemeBean.ThemeType getThemeType() {
/*SharedPreferences sharedPreferences = getSharedPreferences(
SHAREDPREFERENCES_NAME, MODE_PRIVATE);
return AESThemeBean.ThemeType.values()[((sharedPreferences.getInt(DRAWER_THEME_TYPE, AESThemeBean.ThemeType.DEFAULT.ordinal())))];
*/
return AESThemeBean.getThemeStyleType(AESThemeUtil.getThemeTypeID(getApplicationContext()));
}
void setThemeStyle() {
//setTheme(AESThemeBean.getThemeStyle(getThemeType()));
setTheme(AESThemeUtil.getThemeTypeID(getApplicationContext()));
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if(item.getItemId() == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Tue Apr 29 14:47:34 HKT 2025
#Sat May 03 10:32:21 GMT 2025
stageCount=7
libraryProject=libappbase
baseVersion=15.7
publishVersion=15.7.6
buildCount=0
buildCount=4
baseBetaVersion=15.7.7

View File

@@ -1,51 +0,0 @@
package cc.winboll.studio.apputils;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/03/28 17:11:37
* @Describe 应用活动窗口基类
*/
import android.app.Activity;
import android.os.Bundle;
import android.os.PersistableBundle;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
import cc.winboll.studio.libappbase.winboll.WinBoLLActivityManager;
public class WinBoLLActivityBase extends Activity implements IWinBoLLActivity {
public static final String TAG = "WinBoLLActivityBase";
@Override
public Activity getActivity() {
return this;
}
@Override
public String getTag() {
return TAG;
}
WinBoLLActivityManager getWinBoLLActivityManager() {
return WinBoLLActivityManager.getInstance(GlobalApplication.getInstance());
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWinBoLLActivityManager().add(this);
}
@Override
public void onPostCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
super.onPostCreate(savedInstanceState, persistentState);
}
@Override
protected void onDestroy() {
super.onDestroy();
getWinBoLLActivityManager().registeRemove(this);
}
}

View File

@@ -2,7 +2,7 @@
buildscript {
repositories {
// 本地 Maven 仓库(默认路径为 ~/.m2/repository
mavenLocal()
//mavenLocal()
// 或自定义本地仓库路径
maven { url "file:///sdcard/.m2/repository" }
@@ -31,6 +31,11 @@ buildscript {
allprojects {
repositories {
// 本地 Maven 仓库(默认路径为 ~/.m2/repository
//mavenLocal()
// 或自定义本地仓库路径
maven { url "file:///sdcard/.m2/repository" }
// Nexus Maven 库地址
// "WinBoLL Release"
maven { url "https://nexus.winboll.cc/repository/maven-public/" }

View File

@@ -21,6 +21,9 @@ android {
dependencies {
api fileTree(dir: 'libs', include: ['*.jar'])
//api 'cc.winboll.studio:libaes:15.6.0'
api 'cc.winboll.studio:libapputils:15.3.4'
api 'cc.winboll.studio:libappbase:15.7.6'
// 吐司类库
api 'com.github.getActivity:ToastUtils:10.5'
@@ -50,7 +53,4 @@ dependencies {
//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:libapputils:15.3.4'
api 'cc.winboll.studio:libappbase:15.7.6'
}

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Tue Apr 29 15:14:21 HKT 2025
#Sun May 04 06:42:28 GMT 2025
stageCount=1
libraryProject=libaes
baseVersion=15.6
publishVersion=15.6.0
buildCount=0
buildCount=10
baseBetaVersion=15.6.1

View File

@@ -5,27 +5,34 @@
<application>
<activity android:name="cc.winboll.studio.libaes.unittests.SecondaryLibraryActivity"
<activity
android:name="cc.winboll.studio.libaes.unittests.SecondaryLibraryActivity"
android:exported="true"/>
<activity android:name="cc.winboll.studio.libaes.unittests.TestDrawerFragmentActivity"
<activity
android:name="cc.winboll.studio.libaes.unittests.TestDrawerFragmentActivity"
android:exported="true"/>
<activity android:name="cc.winboll.studio.libaes.unittests.TestAToolbarActivity"
<activity
android:name="cc.winboll.studio.libaes.unittests.TestAToolbarActivity"
android:exported="true"/>
<activity android:name="cc.winboll.studio.libaes.unittests.TestASupportToolbarActivity"
<activity
android:name="cc.winboll.studio.libaes.unittests.TestASupportToolbarActivity"
android:exported="true"/>
<service android:name="cc.winboll.studio.libaes.winboll.WinBoLLClientService"
<service
android:name="cc.winboll.studio.libaes.winboll.WinBoLLClientService"
android:exported="true"/>
<service android:name="cc.winboll.studio.libaes.winboll.AssistantService"
<service
android:name="cc.winboll.studio.libaes.winboll.AssistantService"
android:exported="true"/>
<service android:name="cc.winboll.studio.libaes.winboll.WinBoLLMail"
<service
android:name="cc.winboll.studio.libaes.winboll.WinBoLLMail"
android:exported="true"/>
</application>
</manifest>
</manifest>

View File

@@ -1,9 +1,8 @@
package cc.winboll.studio.libaes.winboll;
package cc.winboll.studio.libaes.beans;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/03/28 19:05:15
* @Describe WinBoLLService 运行参数配置
* @Author ZhanGSKen
* @Date 2025/05/03 19:16
*/
import android.content.Context;
import android.util.JsonReader;
@@ -14,11 +13,16 @@ import java.io.IOException;
public class WinBoLLClientServiceBean extends BaseBean {
public static final String TAG = "WinBoLLClientServiceBean";
volatile boolean isEnable;
// 服务是否正在使用中
boolean isEnable;
public WinBoLLClientServiceBean() {
isEnable = false;
this.isEnable = false;
}
public WinBoLLClientServiceBean(boolean isEnable) {
this.isEnable = isEnable;
}
public void setIsEnable(boolean isEnable) {
@@ -29,6 +33,7 @@ public class WinBoLLClientServiceBean extends BaseBean {
return isEnable;
}
@Override
public String getName() {
return WinBoLLClientServiceBean.class.getName();
@@ -38,17 +43,17 @@ public class WinBoLLClientServiceBean extends BaseBean {
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
super.writeThisToJsonWriter(jsonWriter);
WinBoLLClientServiceBean bean = this;
jsonWriter.name("isEnable").value(bean.isEnable());
//jsonWriter.name("logLevel").value(bean.getLogLevel().ordinal());
}
@Override
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
if (name.equals("isEnable")) {
setIsEnable(jsonReader.nextBoolean());
} else {
return false;
}
// if (name.equals("logLevel")) {
// setLogLevel(LogUtils.LOG_LEVEL.values()[jsonReader.nextInt()]);
// } else {
// return false;
// }
}
return true;
}
@@ -68,11 +73,6 @@ public class WinBoLLClientServiceBean extends BaseBean {
}
public static WinBoLLClientServiceBean loadWinBoLLClientServiceBean(Context context) {
WinBoLLClientServiceBean bean = WinBoLLClientServiceBean.loadBean(context, WinBoLLClientServiceBean.class);
return bean == null ? new WinBoLLClientServiceBean() : bean;
}
public static boolean saveWinBoLLServiceBean(WinBoLLClientService service, WinBoLLClientServiceBean bean) {
return WinBoLLClientServiceBean.saveBean(service, bean);
return new WinBoLLClientServiceBean();
}
}

View File

@@ -11,7 +11,8 @@ import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import cc.winboll.studio.libaes.winboll.WinBoLLClientService;
import cc.winboll.studio.libaes.beans.WinBoLLClientServiceBean;
import cc.winboll.studio.libaes.winboll.AssistantService;
import cc.winboll.studio.libappbase.utils.ServiceUtils;
public class AssistantService extends Service {

View File

@@ -1,17 +0,0 @@
package cc.winboll.studio.libaes.winboll;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/03/28 19:08:45
* @Describe WinBoLLService 服务 Binder。
*/
import android.graphics.drawable.Drawable;
public interface IWinBoLLClientServiceBinder {
public static final String TAG = "IWinBoLLClientServiceBinder";
public WinBoLLClientService getService();
public Drawable getCurrentStatusIconDrawable();
}

View File

@@ -1,192 +1,21 @@
package cc.winboll.studio.libaes.winboll;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/03/28 19:06:54
* @Describe WinBoLL 客户端服务
*/
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.drawable.Drawable;
import android.os.IBinder;
import cc.winboll.studio.libaes.winboll.AssistantService;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.utils.ServiceUtils;
import cc.winboll.studio.libapputils.utils.PrefUtils;
import com.hjq.toast.ToastUtils;
public class WinBoLLClientService extends Service implements IWinBoLLClientServiceBinder {
/**
* @Author ZhanGSKen
* @Date 2025/05/03 19:28
*/
public class WinBoLLClientService extends Service {
public static final String TAG = "WinBoLLClientService";
WinBoLLClientServiceBean mWinBoLLClientServiceBean;
MyServiceConnection mMyServiceConnection;
volatile boolean mIsWinBoLLClientThreadRunning;
volatile boolean mIsEnableService;
volatile WinBoLLClientThread mWinBoLLClientThread;
public boolean isWinBoLLClientThreadRunning() {
return mIsWinBoLLClientThreadRunning;
}
@Override
public WinBoLLClientService getService() {
return WinBoLLClientService.this;
}
@Override
public Drawable getCurrentStatusIconDrawable() {
return mIsWinBoLLClientThreadRunning ?
getDrawable(EWUIStatusIconDrawable.getIconDrawableId(EWUIStatusIconDrawable.NORMAL))
: getDrawable(EWUIStatusIconDrawable.getIconDrawableId(EWUIStatusIconDrawable.NEWS));
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
//ToastUtils.show("onCreate");
super.onCreate();
mWinBoLLClientThread = null;
mWinBoLLClientServiceBean = WinBoLLClientServiceBean.loadWinBoLLClientServiceBean(this);
mIsEnableService = mWinBoLLClientServiceBean.isEnable();
if (mMyServiceConnection == null) {
mMyServiceConnection = new MyServiceConnection();
}
// 由系统启动时,应用可以通过下面函数实例化实际服务进程。
runMainThread();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//ToastUtils.show("onStartCommand");
// 由应用 Intent 启动时,应用可以通过下面函数实例化实际服务进程。
runMainThread();
// 返回运行参数持久化存储后,服务状态控制参数
// 无论 Intent 传入如何,服务状态一直以持久化存储后的参数控制,
// PS: 另外当然可以通过 Intent 传入的指标来修改 mWinBoLLServiceBean
// 不过本服务的应用方向会变得繁琐,
// 现阶段只要满足手机端启动与停止本服务WinBoLL 客户端实例运行在手机端就可以了。
return mIsEnableService ? Service.START_STICKY: super.onStartCommand(intent, flags, startId);
}
synchronized void runMainThread() {
if (mWinBoLLClientThread == null) {
//ToastUtils.show("runMainThread()");
mWinBoLLClientThread = new WinBoLLClientThread();
mWinBoLLClientThread.start();
}
}
void syncWinBoLLClientThreadStatus() {
mWinBoLLClientServiceBean = WinBoLLClientServiceBean.loadWinBoLLClientServiceBean(this);
mIsEnableService = mWinBoLLClientServiceBean.isEnable();
LogUtils.d(TAG, String.format("mIsEnableService %s", mIsEnableService));
}
// 唤醒和绑定守护进程
//
void wakeupAndBindAssistant() {
if (ServiceUtils.isServiceRunning(getApplicationContext(), AssistantService.class.getName()) == false) {
startService(new Intent(WinBoLLClientService.this, AssistantService.class));
//LogUtils.d(TAG, "call wakeupAndBindAssistant() : Binding... AssistantService");
bindService(new Intent(WinBoLLClientService.this, AssistantService.class), mMyServiceConnection, Context.BIND_IMPORTANT);
}
}
// 主进程与守护进程连接时需要用到此类
//
private class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
}
@Override
public void onServiceDisconnected(ComponentName name) {
mWinBoLLClientServiceBean = WinBoLLClientServiceBean.loadWinBoLLClientServiceBean(WinBoLLClientService.this);
if (mWinBoLLClientServiceBean.isEnable()) {
// 唤醒守护进程
wakeupAndBindAssistant();
}
}
}
@Override
public void onDestroy() {
super.onDestroy();
//ToastUtils.show("onDestroy");
}
@Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
}
void setWinBoLLServiceEnableStatus(boolean isEnable) {
WinBoLLClientServiceBean bean = WinBoLLClientServiceBean.loadWinBoLLClientServiceBean(this);
bean.setIsEnable(isEnable);
WinBoLLClientServiceBean.saveWinBoLLServiceBean(this, bean);
}
boolean getWinBoLLServiceEnableStatus(Context context) {
mWinBoLLClientServiceBean = WinBoLLClientServiceBean.loadWinBoLLClientServiceBean(context);
return mWinBoLLClientServiceBean.isEnable();
}
/*public interface OnServiceStatusChangeListener {
void onServerStatusChange(boolean isServiceAlive);
}
public void setOnServerStatusChangeListener(OnServiceStatusChangeListener l) {
mOnServerStatusChangeListener = l;
}*/
class WinBoLLClientThread extends Thread {
@Override
public void run() {
super.run();
LogUtils.d(TAG, "run syncWinBoLLClientThreadStatus");
syncWinBoLLClientThreadStatus();
if (mIsEnableService) {
if (mIsWinBoLLClientThreadRunning == false) {
// 设置运行状态
mIsWinBoLLClientThreadRunning = true;
LogUtils.d(TAG, "WinBoLLClientThread run()");
// 唤醒守护进程
//wakeupAndBindAssistant();
while (mIsEnableService) {
// 显示运行状态
WinBoLLServiceStatusView.startConnection();
LogUtils.d(TAG, String.format("while mIsEnableService is %s", mIsEnableService));
try {
Thread.sleep(10 * 1000);
} catch (InterruptedException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
syncWinBoLLClientThreadStatus();
}
// 服务进程退出, 重置进程运行状态
WinBoLLServiceStatusView.stopConnection();
mIsWinBoLLClientThreadRunning = false;
mWinBoLLClientThread = null;
}
}
}
}
}
}

View File

@@ -1,22 +0,0 @@
package cc.winboll.studio.libaes.winboll;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/03/28 19:13:20
* @Describe WinBoLL 邮件服务
*/
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class WinBoLLMail extends Service {
public static final String TAG = "WinBoLLMail";
@Override
public IBinder onBind(Intent intent) {
return null;
}
}

View File

@@ -1,106 +0,0 @@
package cc.winboll.studio.libaes.winboll;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/03/29 15:57:28
* @Describe WinBoLL 服务器服务情况测试访问进程。
*/
import cc.winboll.studio.libappbase.LogUtils;
import java.io.IOException;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Credentials;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
// 新增自定义回调接口
interface TextCallback {
void onSuccess(String text);
void onFailure(Exception e);
}
public class WinBoLLServerConnectionThread extends Thread {
public static final String TAG = "WinBoLLClientService";
private final String url;
private final String username;
private final String password;
private final int connectTimeout;
private final int readTimeout;
private final int maxRetries;
private final TextCallback callback; // 新增回调成员变量
// 新增带回调的构造函数
public WinBoLLServerConnectionThread(String url, String username, String password, TextCallback callback) {
this(url, username, password, 10000, 10000, 5, callback);
}
// 修改原有构造函数,添加回调参数
public WinBoLLServerConnectionThread(String url, String username, String password,
int connectTimeout, int readTimeout, int maxRetries, TextCallback callback) {
this.url = url;
this.username = username;
this.password = password;
this.connectTimeout = connectTimeout;
this.readTimeout = readTimeout;
this.maxRetries = maxRetries;
this.callback = callback;
}
@Override
public void run() {
LogUtils.d(TAG, String.format("run() url %s\nusername %s\npassword %s", url, username, password));
String credential = Credentials.basic(username, password);
LogUtils.d(TAG, String.format("credential %s", credential));
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.header("Accept", "text/plain")
.header("Authorization", credential)
.build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
// 优先调用自定义回调
if (callback != null) {
callback.onFailure(e);
} else {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (!response.isSuccessful()) {
if (callback != null) {
callback.onFailure(new Exception("Unexpected code " + response));
} else {
LogUtils.d(TAG, "Unexpected code " + response, Thread.currentThread().getStackTrace());
}
return;
}
try {
String text = response.body().string();
// 优先调用自定义回调
if (callback != null) {
callback.onSuccess(text);
} else {
LogUtils.d(TAG, text);
}
} catch (Exception e) {
if (callback != null) {
callback.onFailure(e);
} else {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
}
}
});
}
}

View File

@@ -1,473 +1,38 @@
package cc.winboll.studio.libaes.winboll;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.LinearLayout;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/03/28 17:41:55
* @Describe WinBoLL 服务主机连接状态视图
* @Author ZhanGSKen
* @Date 2025/05/03 19:14
*/
import cc.winboll.studio.libaes.R;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import cc.winboll.studio.libaes.winboll.WinBoLLClientService;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libapputils.utils.PrefUtils;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
//import okhttp3.Authenticator;
//import okhttp3.Credentials;
//import okhttp3.OkHttpClient;
//import okhttp3.Request;
//import okhttp3.Response;
//import okhttp3.Route;
public class WinBoLLServiceStatusView extends LinearLayout {
public static final String TAG = "WinBoLLServiceStatusView";
public static final int MSG_CONNECTION_INFO = 0;
public static final int MSG_UPDATE_CONNECTION_STATUS = 1;
static WinBoLLServiceStatusView _WinBoLLServiceStatusView;
Context mContext;
//boolean mIsConnected;
volatile ConnectionThread mConnectionThread;
String mszServerHost;
WinBoLLClientService mWinBoLLService;
ImageView mImageView;
TextView mTextView;
WinBoLLServiceViewHandler mWinBoLLServiceViewHandler;
//WebView mWebView;
static volatile ConnectionStatus mConnectionStatus;
View.OnClickListener mViewOnClickListener;
static String _mUserName;
static String _mPassword;
static enum ConnectionStatus {
DISCONNECTED,
START_CONNECT,
CONNECTING,
CONNECTED;
};
boolean isBound = false;
ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IWinBoLLClientServiceBinder binder = (IWinBoLLClientServiceBinder) service;
mWinBoLLService = binder.getService();
isBound = true;
// 可以在这里调用Service的方法进行通信比如获取数据
mImageView.setBackgroundDrawable(mWinBoLLService.getCurrentStatusIconDrawable());
}
@Override
public void onServiceDisconnected(ComponentName name) {
isBound = false;
}
};
public WinBoLLServiceStatusView(Context context) {
super(context);
mContext = context;
initView();
}
public WinBoLLServiceStatusView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
initView();
}
public WinBoLLServiceStatusView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
initView();
}
public WinBoLLServiceStatusView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mContext = context;
initView();
}
ConnectionStatus getConnectionStatus() {
return false ?
ConnectionStatus.CONNECTED
: ConnectionStatus.DISCONNECTED;
}
void initView() {
_WinBoLLServiceStatusView = this;
mImageView = new ImageView(mContext);
setImageViewByConnection(mImageView, false);
mConnectionStatus = getConnectionStatus();
//mIsConnected = false;
//mWinBoLLServerHostConnectionStatus = WinBoLLServerHostConnectionStatus.DISCONNECTED;
//ToastUtils.show("initView()");
mViewOnClickListener = new View.OnClickListener(){
@Override
public void onClick(View v) {
LogUtils.d(TAG, "onClick()");
if (mConnectionStatus == ConnectionStatus.CONNECTED) {
LogUtils.d(TAG, "Click to stop service.");
WinBoLLClientServiceBean bean = WinBoLLClientServiceBean.loadWinBoLLClientServiceBean(mContext);
bean.setIsEnable(false);
WinBoLLClientServiceBean.saveBean(mContext, bean);
Intent intent = new Intent(mContext, WinBoLLClientService.class);
mContext.stopService(intent);
//stopConnectionThread();
mTextView.setText("");
setImageViewByConnection(mImageView, false);
mConnectionStatus = ConnectionStatus.DISCONNECTED;
} else if (mConnectionStatus == ConnectionStatus.DISCONNECTED) {
LogUtils.d(TAG, "Click to start service.");
WinBoLLClientServiceBean bean = WinBoLLClientServiceBean.loadWinBoLLClientServiceBean(mContext);
bean.setIsEnable(true);
WinBoLLClientServiceBean.saveBean(mContext, bean);
Intent intent = new Intent(mContext, WinBoLLClientService.class);
mContext.startService(intent);
//startConnectionThread();
}
}
};
setOnClickListener(mViewOnClickListener);
addView(mImageView);
mTextView = new TextView(mContext);
mWinBoLLServiceViewHandler = new WinBoLLServiceViewHandler(this);
addView(mTextView);
/*mWebView = new WebView(mContext);
mWebView.setWebViewClient(new WebViewClient() {
@Override
public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
// 弹出系统基本HTTP验证窗口
handler.proceed("username", "password");
}
});
addView(mWebView);*/
}
void checkWinBoLLServerStatusAndUpdateCurrentView() {
LogUtils.d(TAG, "checkWinBoLLServerStatusAndUpdateCurrentView()");
/*if (getConnectionStatus() == ConnectionStatus.CONNECTED) {
mConnectionStatus = ConnectionStatus.CONNECTED;
} else {
mConnectionStatus = ConnectionStatus.DISCONNECTED;
}*/
}
public void setServerHost(String szWinBoLLServerHost) {
mszServerHost = szWinBoLLServerHost;
}
public void setAuthInfo(String username, String password) {
_mUserName = username;
_mPassword = password;
}
void setImageViewByConnection(ImageView imageView, boolean isConnected) {
//mIsConnected = isConnected;
// 获取vector drawable
Drawable drawable = mContext.getDrawable(isConnected ? R.drawable.ic_dev_connected : R.drawable.ic_dev_disconnected);
if (drawable != null) {
imageView.setImageDrawable(drawable);
}
}
TextCallback apiTextCallback = new TextCallback() {
@Override
public void onSuccess(String text) {
// 处理成功响应
LogUtils.d(TAG, text);
}
@Override
public void onFailure(Exception e) {
// 处理失败情况
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
};
TextCallback cipTextCallback = new TextCallback() {
@Override
public void onSuccess(String text) {
// 处理成功响应
LogUtils.d(TAG, text);
LogUtils.d(TAG, "Develop Host Connection IP is : " + text);
mConnectionStatus = ConnectionStatus.CONNECTED;
// 获取当前时间
LocalDateTime now = LocalDateTime.now();
// 定义时间格式
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
// 按照指定格式格式化时间并输出
String formattedDateTime = now.format(formatter);
String msg = "ClientIP<" + formattedDateTime + ">: " + text;
mWinBoLLServiceViewHandler.postMessageText(msg);
mWinBoLLServiceViewHandler.postMessageConnectionStatus(true);
}
@Override
public void onFailure(Exception e) {
// 处理失败情况
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
// 处理网络请求失败
setImageViewByConnection(mImageView, false);
mWinBoLLServiceViewHandler.postMessageText(e.getMessage());
mWinBoLLServiceViewHandler.postMessageConnectionStatus(false);
}
};
public void requestAPIWithBasicAuth() {
String targetUrl = "https://" + (GlobalApplication.isDebuging() ?"dev.winboll": "winboll") + ".cc/api/"; // 替换为实际测试的URL
requestWithBasicAuth(targetUrl, apiTextCallback);
}
public void requestCIPWithBasicAuth() {
String targetUrl = mszServerHost + "/cip/?simple=true";
requestWithBasicAuth(targetUrl, cipTextCallback);
}
public void requestWithBasicAuth(String targetUrl, TextCallback callback) {
String username = "";
String password = "";
if (GlobalApplication.isDebuging()) {
username = PrefUtils.getString(mContext, "metDevUserName", "");
password = PrefUtils.getString(mContext, "metDevUserPassword", "");
} else {
username = "WinBoLL";
password = "WinBoLLPowerByZhanGSKen";
}
void setServerHost(String szWinBoLLServerHost) {
LogUtils.d(TAG, String.format("Connection Start. targetUrl %s", targetUrl));
WinBoLLServerConnectionThread thread = new WinBoLLServerConnectionThread(
targetUrl,
username,
password,
cipTextCallback
);
thread.start();
}
/*void requestWithBasicAuth(final WinBoLLServiceViewHandler textViewHandler, String targetUrl, final String username, final String password) {
// 用户名和密码,替换为实际的认证信息
//String username = "your_username";
//String password = "your_password";
LogUtils.d(TAG, "requestWithBasicAuth(...)");
LogUtils.d(TAG, String.format("targetUrl %s", targetUrl));
// 构建包含认证信息的请求
String credential = Credentials.basic(username, password);
LogUtils.d(TAG, String.format("credential %s", credential));
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(targetUrl)
.header("Accept", "text/plain") // 设置正确的Content-Type头
.header("Authorization", credential)
.build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
// 处理网络请求失败
setImageViewByConnection(mImageView, false);
textViewHandler.postMessageText(e.getMessage());
textViewHandler.postMessageConnectionStatus(false);
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
//String sz = "请求失败,状态码: " + response.code();
//setImageViewByConnection(mImageView, false);
//LogUtils.d(TAG, sz);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (!response.isSuccessful()) {
setImageViewByConnection(mImageView, false);
textViewHandler.postMessageText("Unexpected code " + response);
textViewHandler.postMessageConnectionStatus(false);
LogUtils.d(TAG, "Unexpected code " + response, Thread.currentThread().getStackTrace());
return;
}
try {
// 读取响应体作为字符串,注意这里可能需要解码
String text = response.body().string();
LogUtils.d(TAG, "Develop Host Connection IP is : " + text);
mConnectionStatus = ConnectionStatus.CONNECTED;
// 获取当前时间
LocalDateTime now = LocalDateTime.now();
// 定义时间格式
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
// 按照指定格式格式化时间并输出
String formattedDateTime = now.format(formatter);
textViewHandler.postMessageText("ClientIP<" + formattedDateTime + ">: " + text);
textViewHandler.postMessageConnectionStatus(true);
//org.jsoup.nodes.Document doc = org.jsoup.Jsoup.parse(text);
//LogUtils.d(TAG, doc.text());
// 使用id选择器找到具有特定id的元素
//org.jsoup.nodes.Element elementWithId = doc.select("#LastRelease").first(); // 获取第一个匹配的元素
// 提取并打印元素的文本内容
//mszNewestAppPackageName = elementWithId.text();
//ToastUtils.delayedShow(text + "\n" + mszNewestAppPackageName, 5000);
//mHandler.sendMessage(mHandler.obtainMessage(MSG_APPUPDATE_CHECKED));
//System.out.println(response.body().string());
// mConnectionStatus = ConnectionStatus.CONNECTED;
// // 获取当前时间
// LocalDateTime now = LocalDateTime.now();
//
// // 定义时间格式
// DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
// // 按照指定格式格式化时间并输出
// String formattedDateTime = now.format(formatter);
// //System.out.println(formattedDateTime);
// textViewHandler.postMessageText("ClientIP<" + formattedDateTime + ">: " + response.body().string());
// textViewHandler.postMessageConnectionStatus(true);
} catch (Exception e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
}
});
}*/
class WinBoLLServiceViewHandler extends Handler {
WinBoLLServiceStatusView mDevelopHostConnectionStatusView;
public WinBoLLServiceViewHandler(WinBoLLServiceStatusView view) {
mDevelopHostConnectionStatusView = view;
}
@Override
public void handleMessage(Message msg) {
if (msg.what == MSG_CONNECTION_INFO) {
mDevelopHostConnectionStatusView.mTextView.setText((String)msg.obj);
} else if (msg.what == MSG_UPDATE_CONNECTION_STATUS) {
mDevelopHostConnectionStatusView.setImageViewByConnection(mImageView, (boolean)msg.obj);
mDevelopHostConnectionStatusView.mConnectionStatus = ((boolean)msg.obj) ? ConnectionStatus.CONNECTED : ConnectionStatus.DISCONNECTED;
}
super.handleMessage(msg);
}
void postMessageText(String szMSG) {
Message msg = new Message();
msg.what = MSG_CONNECTION_INFO;
msg.obj = szMSG;
sendMessage(msg);
}
void postMessageConnectionStatus(boolean isConnected) {
Message msg = new Message();
msg.what = MSG_UPDATE_CONNECTION_STATUS;
msg.obj = isConnected;
sendMessage(msg);
}
void setAuthInfo(String szDevUserName, String szDevUserPassword) {
}
public static void startConnection() {
if (_WinBoLLServiceStatusView != null) {
_WinBoLLServiceStatusView.startConnectionThread();
}
}
public static void stopConnection() {
if (_WinBoLLServiceStatusView != null) {
_WinBoLLServiceStatusView.stopConnectionThread();
}
}
void startConnectionThread() {
if (mConnectionStatus == ConnectionStatus.DISCONNECTED) {
mConnectionStatus = ConnectionStatus.START_CONNECT;
LogUtils.d(TAG, "startConnectionThread");
if (mConnectionThread != null) {
LogUtils.d(TAG, "mConnectionThread != null");
mConnectionThread.mIsExist = true;
}
mConnectionThread = new ConnectionThread();
mConnectionThread.start();
} else if (mConnectionStatus == ConnectionStatus.CONNECTING) {
//LogUtils.d(TAG, "mConnectionStatus == ConnectionStatus.CONNECTING");
} else if (mConnectionStatus == ConnectionStatus.CONNECTED) {
//LogUtils.d(TAG, "mConnectionStatus == ConnectionStatus.CONNECTED");
} else {
LogUtils.d(TAG, String.format("Unknow mConnectionStatus %s, can not start ConnectionThread.", mConnectionStatus));
}
}
void stopConnectionThread() {
LogUtils.d(TAG, "stopConnectionThread");
if (mConnectionThread != null) {
LogUtils.d(TAG, "mConnectionThread != null");
mConnectionThread.mIsExist = true;
mConnectionThread = null;
}
}
class ConnectionThread extends Thread {
public volatile boolean mIsExist;
//DevelopHostConnectionStatusViewHandler mDevelopHostConnectionStatusViewHandler;
//public ConnectionThread(DevelopHostConnectionStatusViewHandler developHostConnectionStatusViewHandler) {
//mDevelopHostConnectionStatusViewHandler = developHostConnectionStatusViewHandler;
//}
public ConnectionThread() {
mIsExist = false;
}
@Override
public void run() {
super.run();
while (mIsExist == false) {
if (mConnectionStatus == ConnectionStatus.START_CONNECT) {
mConnectionStatus = ConnectionStatus.CONNECTING;
//requestAPIWithBasicAuth();
requestCIPWithBasicAuth();
} else if (mConnectionStatus == ConnectionStatus.CONNECTED
|| mConnectionStatus == ConnectionStatus.DISCONNECTED) {
//ToastUtils.show("mWinBoLLServerHostConnectionStatus " + mConnectionStatus);
LogUtils.d(TAG, String.format("mConnectionStatus done %s", mConnectionStatus));
} else {
LogUtils.d(TAG, String.format("mConnectionStatus unknow %s", mConnectionStatus));
}
try {
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
}
//ToastUtils.show("ConnectionThread exit.");
LogUtils.d(TAG, "ConnectionThread exit.");
}
}
/*WinBoLLService.OnServiceStatusChangeListener mOnServerStatusChangeListener = new WinBoLLService.OnServiceStatusChangeListener(){
@Override
public void onServerStatusChange(boolean isServiceAlive) {
}
};*/
}

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Tue Apr 29 14:47:16 HKT 2025
#Sat May 03 10:32:21 GMT 2025
stageCount=7
libraryProject=libappbase
baseVersion=15.7
publishVersion=15.7.6
buildCount=0
buildCount=4
baseBetaVersion=15.7.7

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Tue Apr 29 21:00:39 HKT 2025
#Sat May 03 06:21:11 GMT 2025
stageCount=1
libraryProject=
baseVersion=15.3
publishVersion=15.3.0
buildCount=0
buildCount=5
baseBetaVersion=15.3.1

View File

@@ -32,6 +32,8 @@ import cc.winboll.studio.powerbell.utils.AppConfigUtils;
import cc.winboll.studio.powerbell.utils.NotificationHelper;
import cc.winboll.studio.powerbell.utils.ServiceUtils;
import cc.winboll.studio.powerbell.utils.StringUtils;
import android.os.Handler;
import android.os.Looper;
public class ControlCenterService extends Service {
@@ -114,9 +116,17 @@ public class ControlCenterService extends Service {
mControlCenterServiceReceiver = new ControlCenterServiceReceiver(this);
mControlCenterServiceReceiver.registerAction(this);
}
startRemindThread(mAppConfigUtils.mAppConfigBean);
ToastUtils.show("Service Is Start.");
LogUtils.i(TAG, "Service Is Start.");
new Handler(Looper.getMainLooper()).postDelayed(new Runnable(){
@Override
public void run() {
startRemindThread(mAppConfigUtils.mAppConfigBean);
ToastUtils.show("Service Is Start.");
LogUtils.i(TAG, "Service Is Start.");
}
}, 2000);
}
}

View File

@@ -83,8 +83,9 @@ public class NotificationHelper {
Notification notification = new NotificationCompat.Builder(mContext, CHANNEL_ID_FOREGROUND)
.setSmallIcon(R.drawable.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(mContext.getResources(), R.drawable.ic_launcher))
.setContentTitle(title)
.setContentText(content)
//.setContentTitle(title + "\n" + content)
.setContentTitle(content)
//.setContentText(content)
.setContentIntent(pendingIntent)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setOngoing(true)

View File

@@ -37,6 +37,10 @@
//include ':mymessagemanager'
//rootProject.name = "mymessagemanager"
// TimeStamp 项目编译设置
//include ':timestamp'
//rootProject.name = "timestamp"
// AndroidDemo 项目编译设置
//include ':androiddemo'
//rootProject.name = "androiddemo"

1
timestamp/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

6
timestamp/README.md Normal file
View File

@@ -0,0 +1,6 @@
## TimpStamp
## 时间戳工具集
## 使用要点:
1。常驻通知栏按钮的正常使用
需要设置允许应用[写入剪贴板]的[始终允许]权限。

View File

@@ -0,0 +1 @@

72
timestamp/build.gradle Normal file
View File

@@ -0,0 +1,72 @@
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.timestamp"
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'])
api 'cc.winboll.studio:libaes:15.6.0'
api 'cc.winboll.studio:libapputils:15.3.4'
api 'cc.winboll.studio:libappbase:15.7.6'
// 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'
}

View File

@@ -0,0 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Tue May 06 20:37:48 HKT 2025
stageCount=7
libraryProject=
baseVersion=15.0
publishVersion=15.0.6
buildCount=0
baseBetaVersion=15.0.7

21
timestamp/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,12 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" >
<application>
<!-- Put flavor specific code here -->
</application>
</manifest>

View File

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

View File

@@ -0,0 +1,50 @@
<?xml version='1.0' encoding='utf-8'?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="cc.winboll.studio.timestamp">
<!-- 运行前台服务 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<!-- READ_CLIPBOARD -->
<uses-permission android:name="android.permission.READ_CLIPBOARD"/>
<!-- WRITE_CLIPBOARD -->
<uses-permission android:name="android.permission.WRITE_CLIPBOARD"/>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:roundIcon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/MyAppTheme"
android:resizeableActivity="true"
android:name=".App">
<activity
android:name=".MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<meta-data
android:name="android.max_aspect"
android:value="4.0"/>
<activity android:name=".GlobalApplication$CrashActivity"/>
<service android:name=".MainService"/>
<service android:name=".AssistantService"/>
</application>
</manifest>

View File

@@ -0,0 +1,345 @@
package cc.winboll.studio.timestamp;
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.timestamp.R;
import cc.winboll.studio.libappbase.GlobalApplication;
import com.hjq.toast.ToastUtils;
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,113 @@
package cc.winboll.studio.timestamp;
/**
* @Author ZhanGSKen
* @Date 2025/05/05 09:49
* @Describe MainService 守护进程服务
*/
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.timestamp.AssistantService;
import cc.winboll.studio.timestamp.MainService;
import cc.winboll.studio.timestamp.models.AppConfigsModel;
import cc.winboll.studio.timestamp.utils.AppConfigsUtil;
import cc.winboll.studio.timestamp.utils.ServiceUtil;
public class AssistantService extends Service {
public static final String TAG = "AssistantService";
//MyBinder mMyBinder;
MyServiceConnection mMyServiceConnection;
volatile boolean mIsThreadAlive;
@Override
public IBinder onBind(Intent intent) {
//return mMyBinder;
return null;
}
@Override
public void onCreate() {
//LogUtils.d(TAG, "call onCreate()");
super.onCreate();
//mMyBinder = new MyBinder();
if (mMyServiceConnection == null) {
mMyServiceConnection = new MyServiceConnection();
}
// 设置运行参数
mIsThreadAlive = false;
run();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//LogUtils.d(TAG, "call onStartCommand(...)");
run();
AppConfigsModel appConfigs = AppConfigsUtil.getInstance(AssistantService.this).loadAppConfigs();
return appConfigs.isEnableService() ? Service.START_STICKY: super.onStartCommand(intent, flags, startId);
}
/*class MyBinder extends IMyAidlInterface.Stub {
@Override
public String getServiceName() {
return AssistantService.class.getSimpleName();
}
}*/
@Override
public void onDestroy() {
//LogUtils.d(TAG, "call onDestroy()");
mIsThreadAlive = false;
super.onDestroy();
}
// 运行服务内容
//
void run() {
//LogUtils.d(TAG, "call run()");
AppConfigsModel appConfigs = AppConfigsUtil.getInstance(AssistantService.this).loadAppConfigs();
if (appConfigs.isEnableService()) {
if (mIsThreadAlive == false) {
// 设置运行状态
mIsThreadAlive = true;
// 唤醒和绑定主进程
wakeupAndBindMain();
}
}
}
// 唤醒和绑定主进程
//
void wakeupAndBindMain() {
if (ServiceUtil.isServiceAlive(getApplicationContext(), MainService.class.getName()) == false) {
//LogUtils.d(TAG, "wakeupAndBindMain() Wakeup... ControlCenterService");
startForegroundService(new Intent(AssistantService.this, MainService.class));
}
//LogUtils.d(TAG, "wakeupAndBindMain() Bind... ControlCenterService");
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, "call onServiceConnected(...)");
}
@Override
public void onServiceDisconnected(ComponentName name) {
//LogUtils.d(TAG, "call onServiceDisconnected(...)");
AppConfigsModel appConfigs = AppConfigsUtil.getInstance(AssistantService.this).loadAppConfigs();
if (appConfigs.isEnableService()) {
wakeupAndBindMain();
}
}
}
}

View File

@@ -0,0 +1,100 @@
package cc.winboll.studio.timestamp;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.Switch;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.LogView;
import cc.winboll.studio.timestamp.MainService;
import cc.winboll.studio.timestamp.R;
import cc.winboll.studio.timestamp.utils.AppConfigsUtil;
import com.hjq.toast.ToastUtils;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
public class MainActivity extends AppCompatActivity {
public static final String TAG = "MainActivity";
EditText metTimeStampFormatString;
TextView mtvTimeStampFormatString;
EditText metTimeStampCopyFormatString;
TextView mtvTimeStampCopyFormatString;
LogView mLogView;
Switch mswEnableMainService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar=(Toolbar)findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
metTimeStampFormatString = findViewById(R.id.et_timestampformatstring);
mtvTimeStampFormatString = findViewById(R.id.tv_timestampformatstring);
metTimeStampCopyFormatString = findViewById(R.id.et_timestampcopyformatstring);
mtvTimeStampCopyFormatString = findViewById(R.id.tv_timestampcopyformatstring);
metTimeStampFormatString.setText(AppConfigsUtil.getInstance(this).getAppConfigsModel().getTimeStampFormatString());
showPreViewResult(metTimeStampFormatString, mtvTimeStampFormatString);
metTimeStampCopyFormatString.setText(AppConfigsUtil.getInstance(this).getAppConfigsModel().getTimeStampCopyFormatString());
showPreViewResult(metTimeStampCopyFormatString, mtvTimeStampCopyFormatString);
mswEnableMainService = findViewById(R.id.activitymainSwitch1);
mswEnableMainService.setChecked(AppConfigsUtil.getInstance(this).loadAppConfigs().isEnableService());
mLogView = findViewById(R.id.logview);
ToastUtils.show("onCreate");
}
@Override
protected void onResume() {
super.onResume();
mLogView.start();
}
public void onSetMainServiceStatus(View view) {
MainService.setMainServiceStatus(this, mswEnableMainService.isChecked());
}
public void onSaveFormatString(View view) {
if(showPreViewResult(metTimeStampFormatString, mtvTimeStampFormatString)) {
AppConfigsUtil.getInstance(this).getAppConfigsModel().setTimeStampFormatString(metTimeStampFormatString.getText().toString());
AppConfigsUtil.getInstance(this).saveAppConfigs();
}
}
public void onSaveCopyFormatString(View view) {
if(showPreViewResult(metTimeStampCopyFormatString, mtvTimeStampCopyFormatString)) {
AppConfigsUtil.getInstance(this).getAppConfigsModel().setTimeStampCopyFormatString(metTimeStampCopyFormatString.getText().toString());
AppConfigsUtil.getInstance(this).saveAppConfigs();
}
}
boolean showPreViewResult(EditText etFormat, TextView tvShow) {
try {
long currentMillis = System.currentTimeMillis();
Instant instant = Instant.ofEpochMilli(currentMillis);
LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
String szTimeStampFormatString = etFormat.getText().toString();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(szTimeStampFormatString);
String formattedDateTime = ldt.format(formatter);
tvShow.setText(formattedDateTime);
return true;
} catch (Exception e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
return false;
}
}

View File

@@ -0,0 +1,239 @@
package cc.winboll.studio.timestamp;
/**
* @Author ZhanGSKen
* @Date 2025/05/05 09:47
* @Describe 主要服务
*/
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.widget.RemoteViews;
import android.widget.TextView;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.timestamp.AssistantService;
import cc.winboll.studio.timestamp.MainService;
import cc.winboll.studio.timestamp.models.AppConfigsModel;
import cc.winboll.studio.timestamp.receivers.ButtonClickReceiver;
import cc.winboll.studio.timestamp.utils.AppConfigsUtil;
import cc.winboll.studio.timestamp.utils.NotificationHelper;
import cc.winboll.studio.timestamp.utils.ServiceUtil;
import cc.winboll.studio.timestamp.utils.TimeStampRemoteViewsUtil;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
public class MainService extends Service {
public static String TAG = "MainService";
public static final int MSG_UPDATE_TIMESTAMP = 0;
ButtonClickReceiver mButtonClickReceiver;
NotificationHelper mNotificationHelper;
Notification mNotification;
RemoteViews mRemoteViews;
TextView mtvTimeStamp;
//Timer mTimer;
private static boolean _mIsServiceAlive;
public static final String EXTRA_APKFILEPATH = "EXTRA_APKFILEPATH";
final static int MSG_INSTALL_APK = 0;
static MyHandler _MyHandler;
MyServiceConnection mMyServiceConnection;
MainActivity mInstallCompletedFollowUpActivity;
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
// 创建 RemoteViews 对象,并使用包含自定义 View 的布局
//mRemoteViews = new RemoteViews(getPackageName(), R.layout.remoteviews_timestamp);
// 创建广播接收器实例
mButtonClickReceiver = new ButtonClickReceiver();
// 创建 IntentFilter 并设置要接收的广播动作
IntentFilter filter = new IntentFilter(ButtonClickReceiver.BUTTON_COPYTIMESTAMP_ACTION);
// 注册广播接收器
registerReceiver(mButtonClickReceiver, filter);
LogUtils.d(TAG, "onCreate()");
_mIsServiceAlive = false;
_MyHandler = new MyHandler();
if (mMyServiceConnection == null) {
mMyServiceConnection = new MyServiceConnection();
}
run();
}
private void run() {
AppConfigsModel appConfigs = AppConfigsUtil.getInstance(MainService.this).loadAppConfigs();
if (appConfigs.isEnableService()) {
if (_mIsServiceAlive == false) {
// 设置运行状态
_mIsServiceAlive = true;
// 显示前台通知栏
// mNotificationHelper = new NotificationHelper(this);
// //notification = helper.showForegroundNotification(intent, getString(R.string.app_name), getString(R.string.text_aboutservernotification));
// mNotification = mNotificationHelper.showCustomForegroundNotification(new Intent(this, MainActivity.class), mRemoteViews, mRemoteViews);
// startForeground(NotificationHelper.FOREGROUND_NOTIFICATION_ID, mNotification);
// 唤醒守护进程
wakeupAndBindAssistant();
LogUtils.d(TAG, "running...");
_MyHandler.sendEmptyMessage(MSG_UPDATE_TIMESTAMP);
// mTimer = new Timer();
// TimerTask task = new TimerTask() {
// @Override
// public void run() {
// //System.out.println("定时任务执行了");
// mMyHandler.sendEmptyMessage(MSG_UPDATE_TIMESTAMP);
// }
// };
// // 延迟1秒后开始执行之后每隔100毫秒执行一次
// mTimer.schedule(task, 1000, 100);
} else {
LogUtils.d(TAG, "_mIsServiceAlive is " + Boolean.toString(_mIsServiceAlive));
}
}
}
@Override
public void onDestroy() {
super.onDestroy();
// if (mTimer != null) {
// mTimer.cancel();
// }
NotificationManager notificationManager = getSystemService(NotificationManager.class);
notificationManager.cancelAll();
_mIsServiceAlive = false;
_MyHandler = null;
LogUtils.d(TAG, "onDestroy()");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
LogUtils.d(TAG, "onStartCommand");
run();
AppConfigsModel appConfigs = AppConfigsUtil.getInstance(MainService.this).loadAppConfigs();
return appConfigs.isEnableService() ? Service.START_STICKY: super.onStartCommand(intent, flags, startId);
}
public static void setMainServiceStatus(Context context, boolean isEnable) {
AppConfigsModel appConfigs = AppConfigsUtil.getInstance(context).loadAppConfigs();
appConfigs.setIsEnableService(isEnable);
AppConfigsUtil.getInstance(context).saveAppConfigs();
Intent intent = new Intent(context, MainService.class);
if (isEnable) {
context.startService(intent);
} else {
context.stopService(intent);
}
}
// 主进程与守护进程连接时需要用到此类
//
private class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//LogUtils.d(TAG, "call onServiceConnected(...)");
}
@Override
public void onServiceDisconnected(ComponentName name) {
//LogUtils.d(TAG, "call onServiceConnected(...)");
AppConfigsModel appConfigs = AppConfigsUtil.getInstance(MainService.this).loadAppConfigs();
if (appConfigs.isEnableService()) {
// 唤醒守护进程
wakeupAndBindAssistant();
}
}
}
// 唤醒和绑定守护进程
//
void wakeupAndBindAssistant() {
if (ServiceUtil.isServiceAlive(getApplicationContext(), AssistantService.class.getName()) == false) {
startService(new Intent(MainService.this, AssistantService.class));
//LogUtils.d(TAG, "call wakeupAndBindAssistant() : Binding... AssistantService");
bindService(new Intent(MainService.this, AssistantService.class), mMyServiceConnection, Context.BIND_IMPORTANT);
}
}
// void updateTimeStamp() {
// long currentMillis = System.currentTimeMillis();
// Instant instant = Instant.ofEpochMilli(currentMillis);
// LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
// String szTimeStampFormatString = AppConfigs.getInstance(this).getTimeStampFormatString();
// DateTimeFormatter formatter = DateTimeFormatter.ofPattern(szTimeStampFormatString);
// String formattedDateTime = ldt.format(formatter);
// //System.out.println(formattedDateTime);
// mRemoteViews.setTextViewText(R.id.tv_timestamp, formattedDateTime);
// notification = mNotificationHelper.showCustomForegroundNotification(intentMainService, mRemoteViews, mRemoteViews);
// //startForeground(NotificationHelper.FOREGROUND_NOTIFICATION_ID, notification);
// }
//
//
// 服务事务处理类
//
class MyHandler extends Handler {
public void handleMessage(Message message) {
switch (message.what) {
case MSG_UPDATE_TIMESTAMP:
{
long currentMillis = System.currentTimeMillis();
Instant instant = Instant.ofEpochMilli(currentMillis);
LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
String szTimeStampFormatString = AppConfigsUtil.getInstance(MainService.this).getAppConfigsModel().getTimeStampFormatString();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(szTimeStampFormatString);
String formattedDateTime = ldt.format(formatter);
TimeStampRemoteViewsUtil.getInstance(MainService.this).showNotification("时间戳:\n" + formattedDateTime + "\n已拷贝到剪贴板。");
LogUtils.d(TAG, "Hello, World! " + formattedDateTime);
break;
}
default:
break;
}
super.handleMessage(message);
}
}
public static void updateCopiedTimeStamp() {
if (_MyHandler != null) {
_MyHandler.sendEmptyMessage(MSG_UPDATE_TIMESTAMP);
}
}
}

View File

@@ -0,0 +1,98 @@
package cc.winboll.studio.timestamp.models;
/**
* @Author ZhanGSKen
* @Date 2025/05/05 09:51
* @Describe 应用配置数据模型
*/
import android.content.Context;
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import cc.winboll.studio.timestamp.models.AppConfigsModel;
import java.io.IOException;
public class AppConfigsModel extends BaseBean {
public static final String TAG = "AppConfigs";
// 是否启动服务
boolean isEnableService;
// 时间戳显示格式
String timeStampFormatString;
// 时间戳拷贝格式
String timeStampCopyFormatString;
public AppConfigsModel() {
this.isEnableService = false;
this.timeStampFormatString = "yyyy-MM-dd HH:mm:ss";
this.timeStampCopyFormatString = "yyyy_MM_dd-HH_mm_ss";
}
public void setTimeStampCopyFormatString(String timeStampCopyFormatString) {
this.timeStampCopyFormatString = timeStampCopyFormatString;
}
public String getTimeStampCopyFormatString() {
return timeStampCopyFormatString;
}
public void setTimeStampFormatString(String timeStampFormatString) {
this.timeStampFormatString = timeStampFormatString;
}
public String getTimeStampFormatString() {
return timeStampFormatString;
}
public void setIsEnableService(boolean isEnableService) {
this.isEnableService = isEnableService;
}
public boolean isEnableService() {
return isEnableService;
}
@Override
public String getName() {
return AppConfigsModel.class.getName();
}
@Override
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
super.writeThisToJsonWriter(jsonWriter);
jsonWriter.name("isEnableService").value(isEnableService());
jsonWriter.name("timeStampFormatString").value(getTimeStampFormatString());
jsonWriter.name("timeStampCopyFormatString").value(getTimeStampCopyFormatString());
}
@Override
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
if (name.equals("isEnableService")) {
setIsEnableService(jsonReader.nextBoolean());
} else if (name.equals("timeStampFormatString")) {
setTimeStampFormatString(jsonReader.nextString());
} else if (name.equals("timeStampCopyFormatString")) {
setTimeStampCopyFormatString(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,47 @@
package cc.winboll.studio.timestamp.receivers;
/**
* @Author ZhanGSKen
* @Date 2025/05/05 11:35
* @Describe ButtonClickReceiver
*/
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.timestamp.MainService;
import cc.winboll.studio.timestamp.utils.AppConfigsUtil;
import cc.winboll.studio.timestamp.utils.ClipboardUtil;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
public class ButtonClickReceiver extends BroadcastReceiver {
public static final String TAG = "ButtonClickReceiver";
public static final String BUTTON_COPYTIMESTAMP_ACTION = "cc.winboll.studio.timestamp.receivers.ButtonClickReceiver.BUTTON_COPYTIMESTAMP_ACTION";
@Override
public void onReceive(Context context, Intent intent) {
LogUtils.d(TAG, "onReceive");
if (intent.getAction().equals(BUTTON_COPYTIMESTAMP_ACTION)) {
// 在这里编写按钮点击后要执行的代码
long currentMillis = System.currentTimeMillis();
Instant instant = Instant.ofEpochMilli(currentMillis);
LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
String szTimeStampFormatString = AppConfigsUtil.getInstance(context).getAppConfigsModel().getTimeStampCopyFormatString();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(szTimeStampFormatString);
String formattedDateTime = ldt.format(formatter);
ClipboardUtil.copyTextToClipboard(context, formattedDateTime);
// 比如显示一个Toast
Toast.makeText(context, "时间戳:\n" + formattedDateTime + "\n已拷贝到剪贴板。", Toast.LENGTH_SHORT).show();
MainService.updateCopiedTimeStamp();
}
}
}

View File

@@ -0,0 +1,58 @@
package cc.winboll.studio.timestamp.utils;
/**
* @Author ZhanGSKen
* @Date 2025/05/05 14:00
* @Describe AppConfigsUtil
*/
import android.content.Context;
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import cc.winboll.studio.timestamp.models.AppConfigsModel;
import java.io.IOException;
public class AppConfigsUtil {
public static final String TAG = "AppConfigsUtil";
volatile static AppConfigsUtil _AppConfigsUtil;
Context mContext;
AppConfigsModel mAppConfigsModel;
AppConfigsUtil(Context context) {
this.mContext = context;
}
public synchronized static AppConfigsUtil getInstance(Context context){
if(_AppConfigsUtil == null) {
_AppConfigsUtil = new AppConfigsUtil(context);
_AppConfigsUtil.loadAppConfigs();
}
return _AppConfigsUtil;
}
public AppConfigsModel getAppConfigsModel() {
return mAppConfigsModel;
}
public AppConfigsModel loadAppConfigs() {
AppConfigsModel appConfigsModel = null;
appConfigsModel = AppConfigsModel.loadBean(mContext, AppConfigsModel.class);
if (appConfigsModel != null) {
mAppConfigsModel = appConfigsModel;
} else {
saveAppConfigs(new AppConfigsModel());
_AppConfigsUtil = this;
}
return mAppConfigsModel;
}
public void saveAppConfigs(AppConfigsModel appConfigsModel) {
AppConfigsModel.saveBean(mContext, appConfigsModel);
}
public void saveAppConfigs() {
AppConfigsModel.saveBean(mContext, mAppConfigsModel);
}
}

View File

@@ -0,0 +1,37 @@
package cc.winboll.studio.timestamp.utils;
/**
* @Author ZhanGSKen
* @Date 2025/05/06 10:53
* @Describe 剪贴板工具集
*/
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.os.Handler;
public class ClipboardUtil {
public static final String TAG = "ClipboardUtil";
private static final long COPY_DELAY = 500; // 延迟 500 毫秒
private static Handler handler = new Handler();
/**
* 拷贝文本到剪贴板
* @param context 上下文
* @param text 要拷贝的文本
*/
public static void copyTextToClipboard(final Context context, final String text) {
handler.postDelayed(new Runnable() {
@Override
public void run() {
ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
if (clipboardManager != null) {
ClipData clipData = ClipData.newPlainText("label", text);
clipboardManager.setPrimaryClip(clipData);
}
}
}, COPY_DELAY);
}
}

View File

@@ -0,0 +1,47 @@
package cc.winboll.studio.timestamp.utils;
/**
* @Author ZhanGSKen
* @Date 2025/05/05 09:52
* @Describe 文件管理工具类
*/
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
public class FileUtil {
public static final String TAG = "FileUtil";
//
// 把字符串写入文件,指定 UTF-8 编码
//
public static void writeFile(String filePath, String content) throws IOException {
File file = new File(filePath);
FileOutputStream outputStream = new FileOutputStream(file);
OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8);
writer.write(content);
writer.close();
}
//
// 读取文件到字符串,指定 UTF-8 编码
//
public static String readFile(String filePath) throws IOException {
File file = new File(filePath);
FileInputStream inputStream = new FileInputStream(file);
InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
StringBuilder content = new StringBuilder();
int character;
while ((character = reader.read()) != -1) {
content.append((char) character);
}
reader.close();
return content.toString();
}
}

View File

@@ -0,0 +1,223 @@
package cc.winboll.studio.timestamp.utils;
/**
* @Author ZhanGSKen
* @Date 2025/05/05 10:36
* @Describe 应用通知工具类
*/
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.widget.RemoteViews;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.timestamp.R;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class NotificationHelper {
public static final String TAG = "NotificationHelper";
// 渠道ID和名称
private static final String CHANNEL_ID_FOREGROUND = "foreground_channel";
private static final String CHANNEL_NAME_FOREGROUND = "Foreground Service";
private static final String CHANNEL_ID_TEMPORARY = "temporary_channel";
private static final String CHANNEL_NAME_TEMPORARY = "Temporary Notifications";
// 通知ID
public static final int FOREGROUND_NOTIFICATION_ID = 1001;
public static final int TEMPORARY_NOTIFICATION_ID = 2001;
private final Context mContext;
private final NotificationManager mNotificationManager;
// 示例维护当前使用的渠道ID列表
// 键渠道ID渠道重要性级别
Map<String, Integer> activeChannelConfigs = new HashMap<>();
public NotificationHelper(Context context) {
mContext = context;
mNotificationManager = context.getSystemService(NotificationManager.class);
// 初始化配置
activeChannelConfigs.put(
CHANNEL_ID_FOREGROUND,
NotificationManager.IMPORTANCE_HIGH
);
activeChannelConfigs.put(
CHANNEL_ID_TEMPORARY,
NotificationManager.IMPORTANCE_DEFAULT
);
createNotificationChannels();
}
@RequiresApi(api = Build.VERSION_CODES.O)
private void createNotificationChannels() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createForegroundChannel();
createTemporaryChannel();
}
}
@RequiresApi(api = Build.VERSION_CODES.O)
private void createForegroundChannel() {
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID_FOREGROUND,
CHANNEL_NAME_FOREGROUND,
NotificationManager.IMPORTANCE_LOW
);
channel.setDescription("Persistent service notifications");
channel.setSound(null, null);
channel.enableVibration(false);
mNotificationManager.createNotificationChannel(channel);
}
@RequiresApi(api = Build.VERSION_CODES.O)
private void createTemporaryChannel() {
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID_TEMPORARY,
CHANNEL_NAME_TEMPORARY,
NotificationManager.IMPORTANCE_HIGH
);
channel.setDescription("Temporary alert notifications");
channel.setSound(null, null);
channel.enableVibration(true);
channel.setVibrationPattern(new long[]{100, 200, 300, 400});
channel.setBypassDnd(true);
mNotificationManager.createNotificationChannel(channel);
}
// 显示常驻通知(通常用于前台服务)
public Notification showForegroundNotification(Intent intent, String title, String content) {
PendingIntent pendingIntent = createPendingIntent(intent);
Notification notification = new NotificationCompat.Builder(mContext, CHANNEL_ID_FOREGROUND)
.setSmallIcon(R.drawable.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(mContext.getResources(), R.drawable.ic_launcher))
//.setContentTitle(title)
.setContentTitle(content)
//.setContentText(content)
.setContentIntent(pendingIntent)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setOngoing(true)
.build();
mNotificationManager.notify(FOREGROUND_NOTIFICATION_ID, notification);
return notification;
}
// 显示常驻通知(通常用于前台服务)
public Notification showCustomForegroundNotification(Intent intent, RemoteViews contentView, RemoteViews bigContentView) {
PendingIntent pendingIntent = createPendingIntent(intent);
Notification notification = new NotificationCompat.Builder(mContext, CHANNEL_ID_FOREGROUND)
.setSmallIcon(R.drawable.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(mContext.getResources(), R.drawable.ic_launcher))
//.setContentTitle(title)
.setContentIntent(pendingIntent)
.setContent(contentView)
.setCustomBigContentView(bigContentView)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true)
.setOngoing(true)
.build();
mNotificationManager.notify(FOREGROUND_NOTIFICATION_ID, notification);
return notification;
}
// 显示临时通知(自动消失)
public void showTemporaryNotification(Intent intent, String title, String content) {
showTemporaryNotification(intent, TEMPORARY_NOTIFICATION_ID, title, content);
}
// 显示临时通知(自动消失)
public void showTemporaryNotification(Intent intent, int notificationID, String title, String content) {
PendingIntent pendingIntent = createPendingIntent(intent);
Notification notification = new NotificationCompat.Builder(mContext, CHANNEL_ID_TEMPORARY)
.setSmallIcon(R.drawable.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(mContext.getResources(), R.drawable.ic_launcher))
.setContentTitle(title)
.setContentText(content)
.setContentIntent(pendingIntent)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true)
.setVibrate(new long[]{100, 200, 300, 400})
.build();
mNotificationManager.notify(notificationID, notification);
}
// 创建自定义布局通知(可扩展)
public void showCustomNotification(Intent intent, RemoteViews contentView, RemoteViews bigContentView) {
PendingIntent pendingIntent = createPendingIntent(intent);
Notification notification = new NotificationCompat.Builder(mContext, CHANNEL_ID_TEMPORARY)
.setSmallIcon(R.drawable.ic_launcher)
.setContentIntent(pendingIntent)
.setContent(contentView)
.setCustomBigContentView(bigContentView)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true)
.build();
mNotificationManager.notify(TEMPORARY_NOTIFICATION_ID + 1, notification);
}
// 取消所有通知
public void cancelAllNotifications() {
mNotificationManager.cancelAll();
}
// 取消指定通知
public void cancelNotification(int notificationID) {
mNotificationManager.cancel(notificationID);
}
// 创建PendingIntent兼容不同API版本
private PendingIntent createPendingIntent(Intent intent) {
int flags = PendingIntent.FLAG_UPDATE_CURRENT;
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// flags |= PendingIntent.FLAG_IMMUTABLE;
// }
return PendingIntent.getActivity(
mContext,
0,
intent,
flags
);
}
// public void sendSMSReceivedMessage(int notificationID, String szPhone, String szBody) {
// Intent intent = new Intent(mContext, SMSActivity.class);
// intent.putExtra(SMSActivity.EXTRA_PHONE, szPhone);
// String szTitle = mContext.getString(R.string.text_smsfrom) + "<" + szPhone + ">";
// String szContent = "[ " + szBody + " ]";
// showTemporaryNotification(intent, notificationID, szTitle, szContent);
// }
public void cleanOldChannels() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
List<NotificationChannel> allChannels = mNotificationManager.getNotificationChannels();
for (NotificationChannel channel : allChannels) {
LogUtils.d(TAG, "Clean channel : " + channel.getId());
if (!activeChannelConfigs.containsKey(channel.getId())) {
// 安全删除渠道
mNotificationManager.deleteNotificationChannel(channel.getId());
LogUtils.d(TAG, String.format("Deleted Channel %s", channel.getId()));
}
}
}
}
}

View File

@@ -0,0 +1,35 @@
package cc.winboll.studio.timestamp.utils;
/**
* @Author ZhanGSKen
* @Date 2025/05/05 09:58
* @Describe 应用服务管理类
*/
import android.app.ActivityManager;
import android.content.Context;
import java.util.List;
public class ServiceUtil {
public final static String TAG = "ServiceUtil";
public static boolean isServiceAlive(Context context, String szServiceName) {
// 获取Activity管理者对象
ActivityManager manager = (ActivityManager) context
.getSystemService(Context.ACTIVITY_SERVICE);
// 获取正在运行的服务此处设置最多取1000个
List<ActivityManager.RunningServiceInfo> runningServices = manager
.getRunningServices(1000);
if (runningServices.size() <= 0) {
return false;
}
// 遍历若存在名字和传入的serviceName的一致则说明存在
for (ActivityManager.RunningServiceInfo runningServiceInfo : runningServices) {
if (runningServiceInfo.service.getClassName().equals(szServiceName)) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,132 @@
package cc.winboll.studio.timestamp.utils;
/**
* @Author ZhanGSKen
* @Date 2025/05/05 21:10
* @Describe TimeStampRemoteViewsUtil
*/
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.widget.RemoteViews;
import cc.winboll.studio.timestamp.MainActivity;
import cc.winboll.studio.timestamp.R;
import cc.winboll.studio.timestamp.receivers.ButtonClickReceiver;
public class TimeStampRemoteViewsUtil {
public static final String TAG = "TimeStampRemoteViewsUtil";
public static final String CHANNEL_ID = "TimeStampChannel";
static volatile TimeStampRemoteViewsUtil _TimeStampRemoteViewsUtil;
Context mContext;
NotificationHelper mNotificationHelper;
RemoteViews mRemoteViews;
Intent mIntentMain;
TimeStampRemoteViewsUtil(Context context) {
mContext = context;
mNotificationHelper = new NotificationHelper(context);
//createNotificationChannel();
}
public static synchronized TimeStampRemoteViewsUtil getInstance(Context context) {
if (_TimeStampRemoteViewsUtil == null) {
_TimeStampRemoteViewsUtil = new TimeStampRemoteViewsUtil(context);
}
return _TimeStampRemoteViewsUtil;
}
// private void createNotificationChannel() {
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// CharSequence name = "自定义视图通知通道";
// String description = "用于展示自定义视图的通知通道";
// int importance = NotificationManager.IMPORTANCE_HIGH;
// NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance);
// channel.setDescription(description);
// NotificationManager notificationManager = mContext.getSystemService(NotificationManager.class);
// notificationManager.createNotificationChannel(channel);
// }
// }
// public void showNotification(String msg) {
// if (mRemoteViews == null) {
// // 创建 RemoteViews 对象,加载布局
// mRemoteViews = new RemoteViews(mContext.getPackageName(), R.layout.custom_notification_layout);
//
// }
// // 自定义 TextView 的文本
// mRemoteViews.setTextViewText(R.id.tv_timestamp, msg);
// // 自定义 TextView 的文本颜色
// mRemoteViews.setTextColor(R.id.tv_timestamp, mContext.getResources().getColor(R.color.colorAccent, null));
// // 这里虽然不能直接设置字体大小,但可以通过反射等方式尝试(不推荐,且有兼容性问题)
//
// // 创建点击通知后的意图
// Intent intent = new Intent(mContext, MainActivity.class);
// PendingIntent pendingMainIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
// // 设置通知的点击事件
// mRemoteViews.setOnClickPendingIntent(R.id.tv_timestamp, pendingMainIntent);
//
// // 创建点击按钮后要发送的广播 Intent
// Intent broadcastIntent = new Intent(ButtonClickReceiver.BUTTON_COPYTIMESTAMP_ACTION);
// android.app.PendingIntent pendingIntent = android.app.PendingIntent.getBroadcast(
// mContext,
// 0,
// broadcastIntent,
// android.app.PendingIntent.FLAG_UPDATE_CURRENT
// );
//
// // 为按钮设置点击事件
// mRemoteViews.setOnClickPendingIntent(R.id.btn_copytimestamp, pendingIntent);
//
// // 构建通知
// NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext, CHANNEL_ID)
// .setSmallIcon(android.R.drawable.ic_dialog_info)
// .setContent(mRemoteViews)
// .setPriority(NotificationCompat.PRIORITY_HIGH)
// .setOngoing(true)
// .setAutoCancel(true);
//
// // 显示通知
// NotificationManager notificationManager = mContext.getSystemService(NotificationManager.class);
// mNotification = builder.build();
// notificationManager.notify(1, mNotification);
// }
public void showNotification(String msg) {
if (mRemoteViews == null) {
// 创建 RemoteViews 对象,加载布局
mRemoteViews = new RemoteViews(mContext.getPackageName(), R.layout.custom_notification_layout);
}
// 自定义 TextView 的文本
mRemoteViews.setTextViewText(R.id.tv_timestamp, msg);
// 自定义 TextView 的文本颜色
mRemoteViews.setTextColor(R.id.tv_timestamp, mContext.getResources().getColor(R.color.colorAccent, null));
// 这里虽然不能直接设置字体大小,但可以通过反射等方式尝试(不推荐,且有兼容性问题)
// 创建点击通知后的意图
mIntentMain = new Intent(mContext, MainActivity.class);
PendingIntent pendingMainIntent = PendingIntent.getActivity(mContext, 0, mIntentMain, PendingIntent.FLAG_UPDATE_CURRENT);
// 设置通知的点击事件
mRemoteViews.setOnClickPendingIntent(R.id.tv_timestamp, pendingMainIntent);
// 创建点击按钮后要发送的广播 Intent
Intent broadcastIntent = new Intent(ButtonClickReceiver.BUTTON_COPYTIMESTAMP_ACTION);
android.app.PendingIntent pendingIntent = android.app.PendingIntent.getBroadcast(
mContext,
0,
broadcastIntent,
android.app.PendingIntent.FLAG_UPDATE_CURRENT
);
// 为按钮设置点击事件
mRemoteViews.setOnClickPendingIntent(R.id.btn_copytimestamp, pendingIntent);
mNotificationHelper.showCustomForegroundNotification(mIntentMain, mRemoteViews, mRemoteViews);
}
}

View File

@@ -0,0 +1,99 @@
package cc.winboll.studio.timestamp.views;
/**
* @Author ZhanGSKen
* @Date 2025/05/05 12:53
* @Describe TimeStampView
*/
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
public class TimeStampView extends View {
public static final String TAG = "TimeStampView";
public static final int MSG_UPDATE_TIMESTAMP = 0;
//private Paint circlePaint;
//private Paint textPaint;
// Context mContext;
// Timer mTimer;
// TextView mtvTimeStamp;
// MyHandler mMyHandler;
public TimeStampView(Context context) {
super(context);
//initView(context);
init();
}
public TimeStampView(Context context, AttributeSet attrs) {
super(context, attrs);
//initView(context);
init();
}
public TimeStampView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//initView(context);
init();
}
public TimeStampView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
//initView(context);
init();
}
private void init() {
// circlePaint = new Paint();
// circlePaint.setColor(Color.BLUE);
// circlePaint.setStyle(Paint.Style.FILL);
//
// textPaint = new Paint();
// textPaint.setColor(Color.WHITE);
// textPaint.setTextSize(40);
// textPaint.setTextAlign(Paint.Align.CENTER);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//int width = getWidth();
//int height = getHeight();
// int width = 50;
// int height = 50;
// float radius = Math.min(width, height) / 2;
// canvas.drawCircle(width / 2, height / 2, radius, circlePaint);
// String text = "自定义";
// RectF rect = new RectF(0, 0, width, height);
// Paint.FontMetricsInt fontMetrics = textPaint.getFontMetricsInt();
// int baseline =(int)((rect.bottom + rect.top - fontMetrics.bottom - fontMetrics.top) / 2);
// canvas.drawText(text, width / 2, baseline, textPaint);
}
// void initView(Context context) {
// View viewMain = inflate(context, R.layout.view_timestamp, null);
// this.mContext = context;
// mtvTimeStamp = viewMain.findViewById(R.id.tv_timestamp);
// addView(viewMain);
//
// mMyHandler = new MyHandler();
//
// }
// public void updateTimeStamp() {
// try {
//
// } catch (Exception e) {
// LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
// ToastUtils.show(e);
// }
// }
}

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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

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,144 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
</com.google.android.material.appbar.AppBarLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp">
<Switch
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="启用通知栏计时器"
android:id="@+id/activitymainSwitch1"
android:onClick="onSetMainServiceStatus"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Show Format :"
android:paddingRight="10dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Text"
android:id="@+id/tv_timestampformatstring"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp">
<EditText
android:layout_width="0dp"
android:ems="10"
android:layout_height="wrap_content"
android:id="@+id/et_timestampformatstring"
android:layout_weight="1.0"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ApplySave"
android:id="@+id/btn_saveformatstring"
android:textAllCaps="false"
android:onClick="onSaveFormatString"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Copy Format :"
android:paddingRight="10dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Text"
android:id="@+id/tv_timestampcopyformatstring"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp">
<EditText
android:layout_width="0dp"
android:ems="10"
android:layout_height="wrap_content"
android:id="@+id/et_timestampcopyformatstring"
android:layout_weight="1.0"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ApplyCopy"
android:id="@+id/btn_savecopyformatstring"
android:textAllCaps="false"
android:onClick="onSaveCopyFormatString"/>
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0"
android:gravity="center_vertical|center_horizontal">
</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,26 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:padding="16dp"
android:gravity="center_vertical">
<TextView
android:id="@+id/tv_timestamp"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Text"
android:textColor="#000000"
android:textSize="18sp"
android:layout_weight="1.0"/>
<Button
android:layout_width="60dp"
android:layout_height="60dp"
android:id="@+id/btn_copytimestamp"
android:background="@drawable/ic_launcher"/>
</LinearLayout>

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,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,5 @@
<resources>
<string name="app_name">TimeStamp</string>
<string name="text_aboutservernotification">This is the prompt window when the SMS service runs, which you can set to hide this class notification in the notification message settings.</string>
<string name="accessibility_service_description">Accessibility service description.</string>
</resources>

View File

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

View File

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

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Put flavor specific strings here -->
</resources>