diff --git a/autonfc/build.properties b/autonfc/build.properties
index e5b98d6..c377e4f 100644
--- a/autonfc/build.properties
+++ b/autonfc/build.properties
@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
-#Mon Mar 16 06:46:34 GMT 2026
+#Mon Mar 16 08:42:47 GMT 2026
stageCount=0
libraryProject=
baseVersion=15.11
publishVersion=15.0.0
-buildCount=45
+buildCount=53
baseBetaVersion=15.0.1
diff --git a/autonfc/src/main/AndroidManifest.xml b/autonfc/src/main/AndroidManifest.xml
index 6f36135..635feb5 100644
--- a/autonfc/src/main/AndroidManifest.xml
+++ b/autonfc/src/main/AndroidManifest.xml
@@ -6,7 +6,8 @@
-
+
+
diff --git a/autonfc/src/main/java/cc/winboll/studio/autonfc/MainActivity.java b/autonfc/src/main/java/cc/winboll/studio/autonfc/MainActivity.java
index 178b0f6..03743b7 100644
--- a/autonfc/src/main/java/cc/winboll/studio/autonfc/MainActivity.java
+++ b/autonfc/src/main/java/cc/winboll/studio/autonfc/MainActivity.java
@@ -6,7 +6,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.nfc.NfcAdapter;
-import android.nfc.Tag;
import android.os.Bundle;
import android.os.IBinder;
import android.view.Menu;
@@ -14,11 +13,11 @@ import android.view.MenuItem;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
+import cc.winboll.studio.autonfc.nfc.ActionDialog;
import cc.winboll.studio.autonfc.nfc.AutoNFCService;
import cc.winboll.studio.autonfc.nfc.NFCInterfaceActivity;
import cc.winboll.studio.libappbase.LogActivity;
import cc.winboll.studio.libappbase.LogUtils;
-import cc.winboll.studio.libappbase.ToastUtils;
public class MainActivity extends AppCompatActivity {
@@ -37,11 +36,15 @@ public class MainActivity extends AppCompatActivity {
mService = binder.getService();
mBound = true;
LogUtils.d(TAG, "onServiceConnected: 服务已绑定");
+
+ // 关键:把 Activity 传给 Service,用于回调
+ mService.attachActivity(MainActivity.this);
}
@Override
public void onServiceDisconnected(ComponentName name) {
mBound = false;
+ mService = null;
LogUtils.d(TAG, "onServiceDisconnected: 服务已断开");
}
};
@@ -74,7 +77,7 @@ public class MainActivity extends AppCompatActivity {
@Override
protected void onStop() {
super.onStop();
- // 解绑服务,系统会自动停止服务
+ // 解绑服务
if (mBound) {
unbindService(mConnection);
mBound = false;
@@ -104,9 +107,9 @@ public class MainActivity extends AppCompatActivity {
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
- //ToastUtils.show("onNewIntent");
LogUtils.d(TAG, "onNewIntent() -> 检测到 NFC 卡片");
+ // 把 NFC 事件交给 Service 处理
if (mBound && mService != null) {
mService.handleNfcIntent(intent);
} else {
@@ -135,5 +138,43 @@ public class MainActivity extends AppCompatActivity {
public void onNFCInterfaceActivity(View view) {
startActivity(new Intent(this, NFCInterfaceActivity.class));
}
+
+ // ========================= 【新增】关键方法:由 Service 回调来弹出对话框 =========================
+ /**
+ * Service 解析完 NFC 数据后,回调此方法在 Activity 中弹出对话框
+ */
+ public void showNfcActionDialog(final String nfcData) {
+ LogUtils.d(TAG, "showNfcActionDialog() -> Activity 存活,安全弹出对话框");
+
+ // Activity 正在运行,直接弹框,绝对不会报 BadTokenException
+ final ActionDialog dialog = new ActionDialog(this);
+ dialog.setNfcData(nfcData);
+ dialog.setButtonClickListener(new ActionDialog.OnButtonClickListener() {
+ @Override
+ public void onBuildClick() {
+ LogUtils.d(TAG, "点击 Build");
+ if (mService != null) {
+ mService.executeTermuxCommand(AutoNFCService.ACTION_BUILD, nfcData);
+ }
+ dialog.dismiss();
+ }
+
+ @Override
+ public void onViewClick() {
+ LogUtils.d(TAG, "点击 View");
+ if (mService != null) {
+ mService.executeTermuxCommand(AutoNFCService.ACTION_BUILD_VIEW, nfcData);
+ }
+ dialog.dismiss();
+ }
+
+ @Override
+ public void onCancelClick() {
+ dialog.dismiss();
+ }
+ });
+
+ dialog.show();
+ }
}
diff --git a/autonfc/src/main/java/cc/winboll/studio/autonfc/nfc/ActionDialog.java b/autonfc/src/main/java/cc/winboll/studio/autonfc/nfc/ActionDialog.java
new file mode 100644
index 0000000..c3f65d7
--- /dev/null
+++ b/autonfc/src/main/java/cc/winboll/studio/autonfc/nfc/ActionDialog.java
@@ -0,0 +1,123 @@
+package cc.winboll.studio.autonfc.nfc;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+
+import cc.winboll.studio.autonfc.R;
+import cc.winboll.studio.libappbase.LogUtils;
+
+/**
+ * 自定义对话框类,用于与用户交互,展示 NFC 相关操作选项
+ * 兼容 Java 7 语法
+ *
+ * @author 豆包&ZhanGSKen
+ * @create 2025-08-15
+ * @lastModify 2026-03-17
+ */
+public class ActionDialog extends Dialog {
+
+ private static final String TAG = "ActionDialog";
+ private String mNfcData;
+ private OnButtonClickListener mClickListener;
+
+ /**
+ * 构造函数
+ */
+ public ActionDialog(Context context) {
+ super(context);
+ initDialog();
+ }
+
+ /**
+ * 设置 NFC 数据
+ */
+ public void setNfcData(String nfcData) {
+ this.mNfcData = nfcData;
+ LogUtils.d(TAG, "setNfcData() -> " + nfcData);
+ }
+
+ /**
+ * 设置点击监听
+ */
+ public void setButtonClickListener(OnButtonClickListener listener) {
+ this.mClickListener = listener;
+ }
+
+ /**
+ * 初始化布局
+ */
+ private void initDialog() {
+ setTitle("请选择操作");
+
+ LinearLayout layout = new LinearLayout(getContext());
+ layout.setOrientation(LinearLayout.VERTICAL);
+ layout.setPadding(20, 20, 20, 20);
+
+ addButtons(layout);
+
+ setContentView(layout);
+ }
+
+ /**
+ * 添加按钮
+ */
+ private void addButtons(LinearLayout layout) {
+ // Build 按钮
+ Button btnBuild = createButton("Build", new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ LogUtils.d(TAG, "点击 Build");
+ if (mClickListener != null) {
+ mClickListener.onBuildClick();
+ }
+ }
+ });
+ layout.addView(btnBuild);
+
+ // View 按钮
+ Button btnView = createButton("View", new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ LogUtils.d(TAG, "点击 View");
+ if (mClickListener != null) {
+ mClickListener.onViewClick();
+ }
+ }
+ });
+ layout.addView(btnView);
+
+ // 取消按钮
+ Button btnCancel = createButton("Cancel", new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ LogUtils.d(TAG, "点击 Cancel");
+ dismiss();
+ }
+ });
+ layout.addView(btnCancel);
+ }
+
+ /**
+ * 创建按钮
+ */
+ private Button createButton(String text, View.OnClickListener listener) {
+ Button button = new Button(getContext());
+ button.setText(text);
+ button.setPadding(10, 10, 10, 10);
+ button.setOnClickListener(listener);
+ return button;
+ }
+
+ /**
+ * 回调接口
+ */
+ public interface OnButtonClickListener {
+ void onBuildClick();
+ void onViewClick();
+ void onCancelClick();
+ }
+}
+
diff --git a/autonfc/src/main/java/cc/winboll/studio/autonfc/nfc/AutoNFCService.java b/autonfc/src/main/java/cc/winboll/studio/autonfc/nfc/AutoNFCService.java
index d6bb1b3..03829ad 100644
--- a/autonfc/src/main/java/cc/winboll/studio/autonfc/nfc/AutoNFCService.java
+++ b/autonfc/src/main/java/cc/winboll/studio/autonfc/nfc/AutoNFCService.java
@@ -15,36 +15,31 @@ import android.nfc.tech.Ndef;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
+import android.widget.Toast;
+
import cc.winboll.studio.autonfc.MainActivity;
import cc.winboll.studio.autonfc.R;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
+
import java.nio.charset.Charset;
import java.util.Arrays;
-/*
- * 源码说明与描述:
- * NFC 后台服务,仅处理 NFC 数据解析与业务逻辑,不负责监听
- *
- * 作者:豆包&ZhanGSKen
- * 创建时间:2026-03-17 00:00:00
- * 最后编辑时间:2026-03-17 19:40:00
- */
public class AutoNFCService extends Service {
public static final String TAG = "AutoNFCService";
- private static final int NOTIFICATION_ID = 0x1001;
+ private static final int NOTIFICATION_ID = 1001;
private static final String CHANNEL_ID = "NFC_SERVICE_CHANNEL";
- // Binder
+ // ================= 已修改:更新为 Beta 包名 =================
+ public static final String ACTION_BUILD = "cc.winboll.studio.winboll.termux.NfcTermuxBridgeActivity.ACTION_BUILD";
+ public static final String ACTION_BUILD_VIEW = "cc.winboll.studio.winboll.termux.NfcTermuxBridgeActivity.ACTION_BUILD_VIEW";
+
private final IBinder mBinder = new LocalBinder();
+ private String mNfcData;
+ private MainActivity mActivity; // 持有 Activity 引用,用于回调
- public class LocalBinder extends Binder {
- public AutoNFCService getService() {
- return AutoNFCService.this;
- }
- }
-
+ // ========================= 生命周期 =========================
@Override
public void onCreate() {
super.onCreate();
@@ -52,58 +47,158 @@ public class AutoNFCService extends Service {
startForeground(NOTIFICATION_ID, buildNotification());
}
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ LogUtils.d(TAG, "onDestroy() -> 服务已停止");
+ mActivity = null; // 释放引用
+ }
+
+ // ========================= 服务绑定 =========================
@Override
public IBinder onBind(Intent intent) {
- LogUtils.d(TAG, "onBind: 服务被绑定");
+ LogUtils.d(TAG, "onBind() -> 服务被绑定");
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
- LogUtils.d(TAG, "onUnbind: 服务解绑,停止前台服务");
+ LogUtils.d(TAG, "onUnbind() -> 服务解绑");
stopForeground(true);
stopSelf();
return super.onUnbind(intent);
}
- // 对外暴露给 Activity 调用
+ // ========================= 对外暴露方法 =========================
+ /**
+ * 绑定 Activity,用于回调显示对话框
+ */
+ public void attachActivity(MainActivity activity) {
+ this.mActivity = activity;
+ }
+
+ /**
+ * 处理 NFC 意图
+ */
public void handleNfcIntent(Intent intent) {
- ToastUtils.show(TAG + " : handleNfcIntent");
- LogUtils.d(TAG, "handleNfcIntent() start");
+ LogUtils.d(TAG, "handleNfcIntent() -> 开始处理");
if (intent == null) {
- LogUtils.e(TAG, "handleNfcIntent() intent is null");
+ LogUtils.e(TAG, "handleNfcIntent() -> 参数 intent 为空");
return;
}
String action = intent.getAction();
- LogUtils.d(TAG, "handleNfcIntent() action = " + action);
+ LogUtils.d(TAG, "handleNfcIntent() -> Action = " + action);
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)
- || NfcAdapter.ACTION_TECH_DISCOVERED.equals(action)
- || NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) {
+ || NfcAdapter.ACTION_TECH_DISCOVERED.equals(action)
+ || NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) {
- LogUtils.d(TAG, "handleNfcIntent() -> 匹配 NFC 动作,开始处理");
+ LogUtils.d(TAG, "handleNfcIntent() -> 匹配 NFC 动作");
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
if (tag == null) {
- LogUtils.e(TAG, "handleNfcIntent() tag is null");
+ LogUtils.e(TAG, "handleNfcIntent() -> Tag 为空");
return;
}
- LogUtils.d(TAG, "handleNfcIntent() tag id = " + bytesToHexString(tag.getId()));
- LogUtils.d(TAG, "handleNfcIntent() tag tech list = " + Arrays.toString(tag.getTechList()));
+ LogUtils.d(TAG, "handleNfcIntent() -> Tag ID = " + bytesToHexString(tag.getId()));
+ LogUtils.d(TAG, "handleNfcIntent() -> Tech List = " + Arrays.toString(tag.getTechList()));
parseNdefData(tag);
- LogUtils.d(TAG, "handleNfcIntent() parseNdefData() called");
- } else {
- LogUtils.w(TAG, "handleNfcIntent() action not match: " + action);
}
-
- LogUtils.d(TAG, "handleNfcIntent() end");
}
- // 辅助工具:byte[] 转十六进制字符串,用于打印 Tag ID
+ // ========================= 内部业务 =========================
+ private void parseNdefData(Tag tag) {
+ LogUtils.d(TAG, "parseNdefData() -> 开始解析");
+
+ if (tag == null) return;
+
+ Ndef ndef = Ndef.get(tag);
+ if (ndef == null) {
+ LogUtils.e(TAG, "parseNdefData() -> 不支持 NDEF 格式");
+ return;
+ }
+
+ try {
+ ndef.connect();
+ NdefMessage msg = ndef.getNdefMessage();
+
+ if (msg == null || msg.getRecords() == null || msg.getRecords().length == 0) {
+ LogUtils.w(TAG, "parseNdefData() -> 卡片无数据");
+ return;
+ }
+
+ NdefRecord record = msg.getRecords()[0];
+ byte[] payload = record.getPayload();
+
+ int langLen = payload[0] & 0x3F;
+ int start = 1 + langLen;
+
+ if (start < payload.length) {
+ mNfcData = new String(payload, start, payload.length - start, Charset.forName("UTF-8"));
+ LogUtils.d(TAG, "parseNdefData() -> 读卡成功: " + mNfcData);
+
+ // 关键:回调给 Activity 弹框,此时 Activity 一定是存活状态
+ if (mActivity != null) {
+ mActivity.showNfcActionDialog(mNfcData);
+ }
+ }
+
+ } catch (Exception e) {
+ LogUtils.e(TAG, "parseNdefData() -> 读取失败", e);
+ } finally {
+ try {
+ ndef.close();
+ } catch (Exception e) {
+ // 忽略关闭异常
+ }
+ }
+ }
+
+ /**
+ * 执行 Termux 命令
+ */
+ public void executeTermuxCommand(String action, String nfcData) {
+ LogUtils.d(TAG, "executeTermuxCommand() -> 开始执行");
+
+ if (nfcData == null || nfcData.isEmpty()) {
+ ToastUtils.show("数据错误");
+ return;
+ }
+
+ try {
+// String json = String.format(
+// "{\"script\":\"%s\",\"args\":[\"%s\"],\"workDir\":null,\"background\":%b,\"resultDir\":null}",
+// "BuildWinBoLLProject.sh",
+// nfcData,
+// action.equals(ACTION_BUILD)
+// );
+
+ LogUtils.d(TAG, "executeTermuxCommand() -> 发送指令: " + nfcData);
+
+ Intent bridgeIntent = new Intent(action);
+
+ // ================= 已修改:使用 Beta 包名 =================
+ bridgeIntent.setClassName(
+ "cc.winboll.studio.winboll.beta",
+ "cc.winboll.studio.winboll.termux.NfcTermuxBridgeActivity"
+ );
+
+ bridgeIntent.putExtra(Intent.EXTRA_TEXT, nfcData);
+ bridgeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ startActivity(bridgeIntent);
+ ToastUtils.show("指令已发送");
+ } catch (Exception e) {
+ LogUtils.e(TAG, "executeTermuxCommand() -> 发送失败", e);
+ ToastUtils.show("发送失败");
+ }
+ }
+
+ // ========================= 工具方法 =========================
private String bytesToHexString(byte[] bytes) {
if (bytes == null || bytes.length == 0) return "";
StringBuilder sb = new StringBuilder();
@@ -113,53 +208,10 @@ public class AutoNFCService extends Service {
return sb.toString();
}
- private void parseNdefData(Tag tag) {
- if (tag == null) {
- LogUtils.e(TAG, "parseNdefData() -> tag is null");
- return;
- }
-
- Ndef ndef = Ndef.get(tag);
- if (ndef == null) {
- LogUtils.e(TAG, "parseNdefData() -> 不支持 NDEF");
- return;
- }
-
- try {
- ndef.connect();
- NdefMessage msg = ndef.getNdefMessage();
- if (msg == null) {
- LogUtils.w(TAG, "parseNdefData() -> 卡片无数据");
- ndef.close();
- return;
- }
-
- NdefRecord[] records = msg.getRecords();
- if (records != null && records.length > 0) {
- byte[] payload = records[0].getPayload();
- int langLen = payload[0] & 0x3F;
- int start = 1 + langLen;
-
- if (start < payload.length) {
- String data = new String(payload, start, payload.length - start, Charset.forName("UTF-8"));
- LogUtils.d(TAG, "parseNdefData() -> 读卡成功:" + data);
- }
- }
- ndef.close();
- } catch (Exception e) {
- LogUtils.e(TAG, "parseNdefData() -> 读取失败", e);
- }
- }
-
private Notification buildNotification() {
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- NotificationChannel channel = new NotificationChannel(
- CHANNEL_ID,
- "NFC 后台服务",
- NotificationManager.IMPORTANCE_LOW
- );
+ NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "NFC 后台服务", NotificationManager.IMPORTANCE_LOW);
channel.setSound(null, null);
channel.setVibrationPattern(null);
nm.createNotificationChannel(channel);
@@ -172,15 +224,16 @@ public class AutoNFCService extends Service {
.setChannelId(CHANNEL_ID)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("NFC 服务运行中")
- .setContentText("正在监听卡片")
+ .setContentText("等待卡片扫描")
.setContentIntent(pi)
.build();
}
- @Override
- public void onDestroy() {
- super.onDestroy();
- LogUtils.d(TAG, "onDestroy() -> 服务已停止");
+ // ========================= Binder =========================
+ public class LocalBinder extends Binder {
+ public AutoNFCService getService() {
+ return AutoNFCService.this;
+ }
}
}