From 528935fab72614daaeca97388117f8291ba1c31c Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Mon, 16 Mar 2026 14:50:42 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=95=B0=E6=8D=AE=E6=A8=A1?= =?UTF-8?q?=E5=9E=8BNfcTermuxCmd=E7=B1=BB=E7=9A=84NFC=E5=8D=A1=E5=86=99?= =?UTF-8?q?=E5=85=A5=E5=92=8C=E8=AF=BB=E5=8F=96=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- autonfc/build.properties | 4 +- autonfc/src/main/AndroidManifest.xml | 41 +-- .../winboll/studio/autonfc/MainActivity.java | 71 +++-- .../studio/autonfc/models/NfcTermuxCmd.java | 62 ++++- .../autonfc/nfc/AutoNFCBootReceiver.java | 28 -- .../studio/autonfc/nfc/AutoNFCService.java | 105 +++++-- .../autonfc/nfc/NFCInterfaceActivity.java | 260 ++++++++---------- .../studio/autonfc/nfc/NfcProxyActivity.java | 97 ------- .../winboll/studio/autonfc/nfc/NfcUtils.java | 136 +++++++++ .../res/layout/activity_nfc_interface.xml | 134 ++++++--- autonfc/src/main/res/menu/main_menu.xml | 6 - 11 files changed, 537 insertions(+), 407 deletions(-) delete mode 100644 autonfc/src/main/java/cc/winboll/studio/autonfc/nfc/AutoNFCBootReceiver.java delete mode 100644 autonfc/src/main/java/cc/winboll/studio/autonfc/nfc/NfcProxyActivity.java create mode 100644 autonfc/src/main/java/cc/winboll/studio/autonfc/nfc/NfcUtils.java diff --git a/autonfc/build.properties b/autonfc/build.properties index c3fb692..e5b98d6 100644 --- a/autonfc/build.properties +++ b/autonfc/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Mon Mar 16 03:39:59 GMT 2026 +#Mon Mar 16 06:46:34 GMT 2026 stageCount=0 libraryProject= baseVersion=15.11 publishVersion=15.0.0 -buildCount=27 +buildCount=45 baseBetaVersion=15.0.1 diff --git a/autonfc/src/main/AndroidManifest.xml b/autonfc/src/main/AndroidManifest.xml index 9d8ea1c..6f36135 100644 --- a/autonfc/src/main/AndroidManifest.xml +++ b/autonfc/src/main/AndroidManifest.xml @@ -3,16 +3,8 @@ xmlns:android="http://schemas.android.com/apk/res/android" package="cc.winboll.studio.autonfc"> - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - + 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 ac0a295..178b0f6 100644 --- a/autonfc/src/main/java/cc/winboll/studio/autonfc/MainActivity.java +++ b/autonfc/src/main/java/cc/winboll/studio/autonfc/MainActivity.java @@ -1,10 +1,14 @@ package cc.winboll.studio.autonfc; import android.app.PendingIntent; +import android.content.ComponentName; +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; import android.view.MenuItem; import android.view.View; @@ -18,10 +22,29 @@ import cc.winboll.studio.libappbase.ToastUtils; public class MainActivity extends AppCompatActivity { - private static final String TAG = "MainActivity"; + public static final String TAG = "MainActivity"; private NfcAdapter mNfcAdapter; private PendingIntent mPendingIntent; + private AutoNFCService mService; + private boolean mBound = false; + + // 服务连接 + private ServiceConnection mConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + AutoNFCService.LocalBinder binder = (AutoNFCService.LocalBinder) service; + mService = binder.getService(); + mBound = true; + LogUtils.d(TAG, "onServiceConnected: 服务已绑定"); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + mBound = false; + LogUtils.d(TAG, "onServiceDisconnected: 服务已断开"); + } + }; @Override protected void onCreate(Bundle savedInstanceState) { @@ -39,6 +62,26 @@ public class MainActivity extends AppCompatActivity { LogUtils.d(TAG, "onCreate() -> NFC 监听已绑定到 MainActivity"); } + @Override + protected void onStart() { + super.onStart(); + // 绑定服务 + Intent intent = new Intent(this, AutoNFCService.class); + bindService(intent, mConnection, Context.BIND_AUTO_CREATE); + LogUtils.d(TAG, "onStart: 绑定服务"); + } + + @Override + protected void onStop() { + super.onStop(); + // 解绑服务,系统会自动停止服务 + if (mBound) { + unbindService(mConnection); + mBound = false; + LogUtils.d(TAG, "onStop: 解绑服务"); + } + } + @Override protected void onResume() { super.onResume(); @@ -61,18 +104,14 @@ public class MainActivity extends AppCompatActivity { @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); + //ToastUtils.show("onNewIntent"); LogUtils.d(TAG, "onNewIntent() -> 检测到 NFC 卡片"); - Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); - if (tag == null) { - LogUtils.e(TAG, "onNewIntent() -> Tag 为空"); - return; + if (mBound && mService != null) { + mService.handleNfcIntent(intent); + } else { + LogUtils.e(TAG, "服务未绑定,无法处理 NFC"); } - - // 转发给服务处理 - Intent serviceIntent = new Intent(this, AutoNFCService.class); - serviceIntent.putExtras(intent); - startService(serviceIntent); } @Override @@ -85,18 +124,6 @@ public class MainActivity extends AppCompatActivity { public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); - if (id == R.id.menu_start_service) { - startService(new Intent(this, AutoNFCService.class)); - ToastUtils.show("NFC 服务已启动"); - return true; - } - - if (id == R.id.menu_stop_service) { - stopService(new Intent(this, AutoNFCService.class)); - ToastUtils.show("NFC 服务已停止"); - return true; - } - if (id == R.id.menu_log) { LogActivity.startLogActivity(this); return true; diff --git a/autonfc/src/main/java/cc/winboll/studio/autonfc/models/NfcTermuxCmd.java b/autonfc/src/main/java/cc/winboll/studio/autonfc/models/NfcTermuxCmd.java index 92ccf8b..faab4cb 100644 --- a/autonfc/src/main/java/cc/winboll/studio/autonfc/models/NfcTermuxCmd.java +++ b/autonfc/src/main/java/cc/winboll/studio/autonfc/models/NfcTermuxCmd.java @@ -5,10 +5,62 @@ package cc.winboll.studio.autonfc.models; * @Date 2026/03/16 09:38 */ public class NfcTermuxCmd { - public String script; // 要执行的预制脚本名(如 auth.sh) - public String[] args; // 脚本参数 - public String workDir; // 工作目录 - public boolean background; // 是否后台执行 - public String resultDir; // 结果输出目录(可为 null) + + private String script; // 要执行的预制脚本名(如 auth.sh) + private String[] args; // 脚本参数 + private String workDir; // 工作目录 + private boolean background; // 是否后台执行 + private String resultDir; // 结果输出目录(可为 null) + + public NfcTermuxCmd() { + } + + public NfcTermuxCmd(String script, String[] args, String workDir, boolean background, String resultDir) { + this.script = script; + this.args = args; + this.workDir = workDir; + this.background = background; + this.resultDir = resultDir; + } + + public String getScript() { + return script; + } + + public void setScript(String script) { + this.script = script; + } + + public String[] getArgs() { + return args; + } + + public void setArgs(String[] args) { + this.args = args; + } + + public String getWorkDir() { + return workDir; + } + + public void setWorkDir(String workDir) { + this.workDir = workDir; + } + + public boolean isBackground() { + return background; + } + + public void setBackground(boolean background) { + this.background = background; + } + + public String getResultDir() { + return resultDir; + } + + public void setResultDir(String resultDir) { + this.resultDir = resultDir; + } } diff --git a/autonfc/src/main/java/cc/winboll/studio/autonfc/nfc/AutoNFCBootReceiver.java b/autonfc/src/main/java/cc/winboll/studio/autonfc/nfc/AutoNFCBootReceiver.java deleted file mode 100644 index 3ea86c5..0000000 --- a/autonfc/src/main/java/cc/winboll/studio/autonfc/nfc/AutoNFCBootReceiver.java +++ /dev/null @@ -1,28 +0,0 @@ -package cc.winboll.studio.autonfc.nfc; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import cc.winboll.studio.libappbase.LogUtils; -/* - * 源码说明与描述: - * 开机自启动广播接收器,用于开机自动启动 NFC 监听服务 - * - * 作者:豆包&ZhanGSKen - * 创建时间:2026-03-16 16:10:00 - * 最后编辑时间:2026-03-16 18:20:00 - */ -public class AutoNFCBootReceiver extends BroadcastReceiver { - - private static final String TAG = "AutoNFCBootReceiver"; - - @Override - public void onReceive(Context context, Intent intent) { - if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) { - LogUtils.d(TAG, "开机完成,启动 AutoNFC 监听服务"); - Intent service = new Intent(context, AutoNFCService.class); - context.startService(service); - } - } -} - 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 37d6723..d6bb1b3 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 @@ -12,12 +12,15 @@ import android.nfc.NdefRecord; import android.nfc.NfcAdapter; import android.nfc.Tag; import android.nfc.tech.Ndef; +import android.os.Binder; import android.os.Build; import android.os.IBinder; 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; /* * 源码说明与描述: @@ -33,6 +36,15 @@ public class AutoNFCService extends Service { private static final int NOTIFICATION_ID = 0x1001; private static final String CHANNEL_ID = "NFC_SERVICE_CHANNEL"; + // Binder + private final IBinder mBinder = new LocalBinder(); + + public class LocalBinder extends Binder { + public AutoNFCService getService() { + return AutoNFCService.this; + } + } + @Override public void onCreate() { super.onCreate(); @@ -41,26 +53,64 @@ public class AutoNFCService extends Service { } @Override - public int onStartCommand(Intent intent, int flags, int startId) { - LogUtils.d(TAG, "onStartCommand() -> 服务运行"); - - if (intent != null) { - handleNfcIntent(intent); - } - return START_STICKY; + public IBinder onBind(Intent intent) { + LogUtils.d(TAG, "onBind: 服务被绑定"); + return mBinder; } - // 处理 MainActivity 转发的 NFC 数据 - private void handleNfcIntent(Intent intent) { - String action = intent.getAction(); - if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action) - || NfcAdapter.ACTION_TECH_DISCOVERED.equals(action) - || NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) { + @Override + public boolean onUnbind(Intent intent) { + LogUtils.d(TAG, "onUnbind: 服务解绑,停止前台服务"); + stopForeground(true); + stopSelf(); + return super.onUnbind(intent); + } - LogUtils.d(TAG, "handleNfcIntent() -> 收到 NFC 卡片"); - Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); - parseNdefData(tag); + // 对外暴露给 Activity 调用 + public void handleNfcIntent(Intent intent) { + ToastUtils.show(TAG + " : handleNfcIntent"); + LogUtils.d(TAG, "handleNfcIntent() start"); + + if (intent == null) { + LogUtils.e(TAG, "handleNfcIntent() intent is null"); + return; } + + String action = intent.getAction(); + 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)) { + + LogUtils.d(TAG, "handleNfcIntent() -> 匹配 NFC 动作,开始处理"); + + Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); + if (tag == null) { + LogUtils.e(TAG, "handleNfcIntent() tag is null"); + return; + } + + LogUtils.d(TAG, "handleNfcIntent() tag id = " + bytesToHexString(tag.getId())); + LogUtils.d(TAG, "handleNfcIntent() tag 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 String bytesToHexString(byte[] bytes) { + if (bytes == null || bytes.length == 0) return ""; + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) { + sb.append(String.format("%02X", b)); + } + return sb.toString(); } private void parseNdefData(Tag tag) { @@ -106,9 +156,9 @@ public class AutoNFCService extends Service { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel( - CHANNEL_ID, - "NFC 后台服务", - NotificationManager.IMPORTANCE_LOW + CHANNEL_ID, + "NFC 后台服务", + NotificationManager.IMPORTANCE_LOW ); channel.setSound(null, null); channel.setVibrationPattern(null); @@ -119,12 +169,12 @@ public class AutoNFCService extends Service { PendingIntent pi = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); return new Notification.Builder(this) - .setChannelId(CHANNEL_ID) - .setSmallIcon(R.mipmap.ic_launcher) - .setContentTitle("NFC 服务运行中") - .setContentText("正在监听卡片") - .setContentIntent(pi) - .build(); + .setChannelId(CHANNEL_ID) + .setSmallIcon(R.mipmap.ic_launcher) + .setContentTitle("NFC 服务运行中") + .setContentText("正在监听卡片") + .setContentIntent(pi) + .build(); } @Override @@ -132,10 +182,5 @@ public class AutoNFCService extends Service { super.onDestroy(); LogUtils.d(TAG, "onDestroy() -> 服务已停止"); } - - @Override - public IBinder onBind(Intent intent) { - return null; - } } diff --git a/autonfc/src/main/java/cc/winboll/studio/autonfc/nfc/NFCInterfaceActivity.java b/autonfc/src/main/java/cc/winboll/studio/autonfc/nfc/NFCInterfaceActivity.java index 868860a..28b6f18 100644 --- a/autonfc/src/main/java/cc/winboll/studio/autonfc/nfc/NFCInterfaceActivity.java +++ b/autonfc/src/main/java/cc/winboll/studio/autonfc/nfc/NFCInterfaceActivity.java @@ -3,38 +3,30 @@ package cc.winboll.studio.autonfc.nfc; import android.app.Activity; import android.app.PendingIntent; import android.content.Intent; -import android.nfc.NdefMessage; -import android.nfc.NdefRecord; import android.nfc.NfcAdapter; import android.nfc.Tag; -import android.nfc.tech.Ndef; import android.os.Bundle; +import android.view.View; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; -import android.view.View; import cc.winboll.studio.autonfc.R; +import cc.winboll.studio.autonfc.models.NfcTermuxCmd; import cc.winboll.studio.libappbase.LogUtils; -import java.nio.charset.Charset; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; -/* - * 源码说明与描述: - * NFC 操作界面(自主读卡 + 显示内容与时间 + 写入数据) - * 不再接收 AutoNFCService 消息 - * - * 作者:豆包&ZhanGSKen - * 创建时间:2026-03-16 16:10:00 - * 最后编辑时间:2026-03-17 10:45:00 - */ - public class NFCInterfaceActivity extends Activity { - private static final String TAG = "NFCInterfaceActivity"; + public static final String TAG = "NFCInterfaceActivity"; + + private EditText et_script; + private EditText et_args; + private EditText et_workDir; + private EditText et_background; + private EditText et_resultDir; - private EditText etInput; private TextView tvResult; private TextView tvStatus; @@ -45,22 +37,23 @@ public class NFCInterfaceActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - LogUtils.d(TAG, "onCreate()"); setContentView(R.layout.activity_nfc_interface); - initView(); initNfc(); } private void initView() { - LogUtils.d(TAG, "initView()"); - etInput = findViewById(R.id.et_input); + et_script = findViewById(R.id.et_script); + et_args = findViewById(R.id.et_args); + et_workDir = findViewById(R.id.et_workDir); + et_background = findViewById(R.id.et_background); + et_resultDir = findViewById(R.id.et_resultDir); + tvResult = findViewById(R.id.tv_result); tvStatus = findViewById(R.id.tv_status); } private void initNfc() { - LogUtils.d(TAG, "initNfc()"); mNfcAdapter = NfcAdapter.getDefaultAdapter(this); if (mNfcAdapter == null) { @@ -68,15 +61,12 @@ public class NFCInterfaceActivity extends Activity { return; } if (!mNfcAdapter.isEnabled()) { - tvStatus.setText("请在设置中开启NFC"); + tvStatus.setText("请开启NFC"); return; } - mNfcPendingIntent = PendingIntent.getActivity( - this, 0, - new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), - PendingIntent.FLAG_UPDATE_CURRENT - ); + Intent nfcIntent = new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + mNfcPendingIntent = PendingIntent.getActivity(this, 0, nfcIntent, PendingIntent.FLAG_UPDATE_CURRENT); tvStatus.setText("NFC已启动,等待卡片靠近"); } @@ -84,167 +74,157 @@ public class NFCInterfaceActivity extends Activity { @Override protected void onResume() { super.onResume(); - LogUtils.d(TAG, "onResume() -> 启用前台读卡"); - enableForegroundDispatch(); - } - - @Override - protected void onPause() { - super.onPause(); - LogUtils.d(TAG, "onPause() -> 停止前台读卡"); - disableForegroundDispatch(); - mCurrentTag = null; - } - - private void enableForegroundDispatch() { if (mNfcAdapter != null && mNfcAdapter.isEnabled()) { mNfcAdapter.enableForegroundDispatch(this, mNfcPendingIntent, null, null); } } - private void disableForegroundDispatch() { + @Override + protected void onPause() { + super.onPause(); if (mNfcAdapter != null) { mNfcAdapter.disableForegroundDispatch(this); } + mCurrentTag = null; } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); - LogUtils.d(TAG, "onNewIntent() -> 卡片已靠近"); - mCurrentTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); - tvStatus.setText("卡片已连接"); + if (mCurrentTag == null) return; - // 自动读取旧数据 - readOldNfcData(mCurrentTag); + tvStatus.setText("卡片已连接,解析中..."); + readNfc(); } - /** - * 读取卡片原有数据并显示(带时间) - */ - private void readOldNfcData(Tag tag) { - LogUtils.d(TAG, "readOldNfcData(tag=" + tag + ")"); - - if (tag == null) { - showToast("标签为空"); - return; - } - - Ndef ndef = Ndef.get(tag); - if (ndef == null) { - showToast("不支持NDEF"); - return; - } - + // ------------------------------------------------------------------------- + // 读取 NFC(完全委托给工具类) + // ------------------------------------------------------------------------- + private void readNfc() { try { - ndef.connect(); - NdefMessage msg = ndef.getNdefMessage(); - if (msg == null) { - tvResult.setText("无旧数据"); - ndef.close(); + NfcTermuxCmd cmd = NfcUtils.readTag(mCurrentTag); + if (cmd == null) { + tvStatus.setText("读取成功:标签为空"); + tvResult.setText(""); + // 清空窗体 + clearUiFields(); 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; + // 核心改动:读取成功后,同时更新详情显示 和 窗体输入框 + updateUiWithCmd(cmd); - String content = new String( - payload, start, payload.length - start, - Charset.forName("UTF-8") - ); - - // 附加时间 - String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA).format(new Date()); - String show = "读取时间:" + time + "\n\n内容:\n" + content; - tvResult.setText(show); - - showToast("读取成功"); - } - ndef.close(); } catch (Exception e) { - LogUtils.e(TAG, "读取失败", e); - tvResult.setText("读取失败:" + e.getMessage()); - showToast("读取失败"); + LogUtils.e(TAG, "readNfc 失败", e); + tvStatus.setText("读取失败:" + e.getMessage()); + // 出错时清空窗体 + clearUiFields(); } } - /** - * 写入按钮点击 - */ - public void onWriteClick(View view) { - LogUtils.d(TAG, "onWriteClick()"); - String text = etInput.getText().toString().trim(); + // ------------------------------------------------------------------------- + // 新增:根据读取到的 Cmd 填充 UI(详情 + 窗体) + // ------------------------------------------------------------------------- + private void updateUiWithCmd(NfcTermuxCmd cmd) { + if (cmd == null) return; - if (text.isEmpty()) { - showToast("请输入内容"); - return; - } + String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA).format(new Date()); + String show = "【读取时间】 " + time + "\n\n" + + "【解析结果】\n" + + "script: " + cmd.getScript() + "\n" + + "args: " + (cmd.getArgs() != null ? String.join(", ", cmd.getArgs()) : "[]") + "\n" + + "workDir: " + cmd.getWorkDir() + "\n" + + "background: " + cmd.isBackground() + "\n" + + "resultDir: " + cmd.getResultDir(); + + tvResult.setText(show); + tvStatus.setText("读取成功!"); + + // 👇 关键逻辑:自动填入窗体(每次读取后都会覆盖输入框) + et_script.setText(cmd.getScript() != null ? cmd.getScript() : ""); + et_args.setText(cmd.getArgs() != null ? String.join(",", cmd.getArgs()) : ""); + et_workDir.setText(cmd.getWorkDir() != null ? cmd.getWorkDir() : ""); + et_background.setText(String.valueOf(cmd.isBackground())); + et_resultDir.setText(cmd.getResultDir() != null ? cmd.getResultDir() : ""); + } + + // ------------------------------------------------------------------------- + // 辅助:清空所有输入框 + // ------------------------------------------------------------------------- + private void clearUiFields() { + et_script.setText(""); + et_args.setText(""); + et_workDir.setText(""); + et_background.setText(""); + et_resultDir.setText(""); + } + + // ------------------------------------------------------------------------- + // 写入按钮(委托给工具类) + // ------------------------------------------------------------------------- + public void onWriteClick(View view) { if (mCurrentTag == null) { showToast("请先靠近卡片"); return; } - writeNfc(mCurrentTag, text); - } - - /** - * 写入NDEF - */ - private void writeNfc(Tag tag, String text) { - LogUtils.d(TAG, "writeNfc(text=" + text + ")"); - tvStatus.setText("正在写入..."); - try { - Ndef ndef = Ndef.get(tag); - if (ndef == null) { - showToast("不支持NDEF"); - tvStatus.setText("不支持NDEF"); - return; - } - - ndef.connect(); - NdefRecord record = createTextRecord(text, true); - NdefMessage msg = new NdefMessage(new NdefRecord[]{record}); - ndef.writeNdefMessage(msg); - ndef.close(); + NfcTermuxCmd cmd = buildCmdFromUI(); + NfcUtils.writeTag(mCurrentTag, cmd); tvStatus.setText("写入成功!"); showToast("写入成功"); + readNfc(); // 写入后重读,此时会自动填入窗体 - // 写入后重新读取 - readOldNfcData(tag); } catch (Exception e) { LogUtils.e(TAG, "写入失败", e); - tvStatus.setText("写入失败"); + tvStatus.setText("写入失败:" + e.getMessage()); showToast("写入失败"); } } - /** - * 创建文本记录 - */ - private NdefRecord createTextRecord(String text, boolean isUtf8) { - byte[] langBytes = "en".getBytes(Charset.forName("US-ASCII")); - byte[] textBytes = text.getBytes(Charset.forName(isUtf8 ? "UTF-8" : "UTF-16")); - int status = isUtf8 ? 0 : 0x80; - status |= langBytes.length & 0x3F; + // ------------------------------------------------------------------------- + // 填充调试数据 + // ------------------------------------------------------------------------- + public void onFillTestDataClick(View view) { + String testJson = "{\"script\":\"BuildWinBoLLProject.sh\",\"args\":[\"DebugTemp\"],\"workDir\":null,\"background\":true,\"resultDir\":null}"; + try { + NfcTermuxCmd cmd = NfcUtils.jsonToCmd(testJson); + et_script.setText(cmd.getScript()); + et_args.setText(cmd.getArgs() != null ? String.join(",", cmd.getArgs()) : ""); + et_workDir.setText(cmd.getWorkDir() != null ? cmd.getWorkDir() : ""); + et_background.setText(String.valueOf(cmd.isBackground())); + et_resultDir.setText(cmd.getResultDir() != null ? cmd.getResultDir() : ""); - byte[] data = new byte[1 + langBytes.length + textBytes.length]; - data[0] = (byte) status; - System.arraycopy(langBytes, 0, data, 1, langBytes.length); - System.arraycopy(textBytes, 0, data, 1 + langBytes.length, textBytes.length); + showToast("调试数据已填入"); + } catch (Exception e) { + showToast("解析失败"); + } + } - return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, - NdefRecord.RTD_TEXT, new byte[0], data); + // ------------------------------------------------------------------------- + // 从 UI 构建 NfcTermuxCmd + // ------------------------------------------------------------------------- + private NfcTermuxCmd buildCmdFromUI() { + String script = et_script.getText().toString().trim(); + String argsStr = et_args.getText().toString().trim(); + String workDir = et_workDir.getText().toString().trim(); + String bgStr = et_background.getText().toString().trim(); + String resultDir = et_resultDir.getText().toString().trim(); + + NfcTermuxCmd cmd = new NfcTermuxCmd(); + cmd.setScript(script); + cmd.setArgs(argsStr.isEmpty() ? new String[0] : argsStr.split(",")); + cmd.setWorkDir(workDir.isEmpty() ? null : workDir); + cmd.setBackground("true".equalsIgnoreCase(bgStr)); + cmd.setResultDir(resultDir.isEmpty() ? null : resultDir); + + return cmd; } private void showToast(String msg) { Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); } - } diff --git a/autonfc/src/main/java/cc/winboll/studio/autonfc/nfc/NfcProxyActivity.java b/autonfc/src/main/java/cc/winboll/studio/autonfc/nfc/NfcProxyActivity.java deleted file mode 100644 index cd0b218..0000000 --- a/autonfc/src/main/java/cc/winboll/studio/autonfc/nfc/NfcProxyActivity.java +++ /dev/null @@ -1,97 +0,0 @@ -package cc.winboll.studio.autonfc.nfc; - -/* - * 源码说明与描述: - * NFC 透明代理 Activity,用于在后台捕获 NFC 卡片事件并转发给 AutoNFCService - * 界面为 1 像素透明,不影响用户操作 - * - * 作者:豆包&ZhanGSKen - * 创建时间:2026-03-17 14:00:00 - * 最后编辑时间:2026-03-17 18:30:00 - */ -import android.app.Activity; -import android.app.PendingIntent; -import android.content.Intent; -import android.nfc.NfcAdapter; -import android.os.Bundle; -import android.view.Window; -import android.view.WindowManager; -import cc.winboll.studio.libappbase.LogUtils; - -public class NfcProxyActivity extends Activity { - - private static final String TAG = "NfcProxyActivity"; - - private NfcAdapter mNfcAdapter; - private PendingIntent mPendingIntent; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - LogUtils.d(TAG, "onCreate() -> 启动 NFC 透明代理"); - - // 设置为 1 像素透明窗口,用户不可见 - Window window = getWindow(); - window.setLayout(1, 1); - window.setBackgroundDrawableResource(android.R.color.transparent); - window.setFlags( - WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, - WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - ); - - // 初始化 NFC - mNfcAdapter = NfcAdapter.getDefaultAdapter(this); - Intent intent = new Intent(this, getClass()) - .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); - mPendingIntent = PendingIntent.getActivity(this, 0, intent, 0); - } - - @Override - protected void onResume() { - super.onResume(); - LogUtils.d(TAG, "onResume() -> 启用前台分发"); - - // 强制获取焦点,确保 enableForegroundDispatch 生效 - getWindow().getDecorView().requestFocus(); - getWindow().setFlags( - WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, - 0 // 临时允许获取焦点 - ); - - if (mNfcAdapter != null) { - mNfcAdapter.enableForegroundDispatch(this, mPendingIntent, null, null); - } - - // 恢复为不可聚焦,避免拦截用户操作 - getWindow().setFlags( - WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, - WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - ); - } - - @Override - protected void onPause() { - super.onPause(); - LogUtils.d(TAG, "onPause() -> 关闭前台分发"); - if (mNfcAdapter != null) { - mNfcAdapter.disableForegroundDispatch(this); - } - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - LogUtils.d(TAG, "onNewIntent() -> 捕获到 NFC 卡片事件"); - - // 转发给 AutoNFCService 处理 - Intent serviceIntent = new Intent(this, AutoNFCService.class); - serviceIntent.putExtras(intent); - startService(serviceIntent); - } -} - diff --git a/autonfc/src/main/java/cc/winboll/studio/autonfc/nfc/NfcUtils.java b/autonfc/src/main/java/cc/winboll/studio/autonfc/nfc/NfcUtils.java new file mode 100644 index 0000000..65ac597 --- /dev/null +++ b/autonfc/src/main/java/cc/winboll/studio/autonfc/nfc/NfcUtils.java @@ -0,0 +1,136 @@ +package cc.winboll.studio.autonfc.nfc; + +/** + * @Author 豆包&ZhanGSKen + * @Date 2026/03/16 14:26 + */ +import android.nfc.NdefMessage; +import android.nfc.NdefRecord; +import android.nfc.Tag; +import android.nfc.tech.Ndef; +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; +import cc.winboll.studio.autonfc.models.NfcTermuxCmd; +import cc.winboll.studio.libappbase.LogUtils; +import java.nio.charset.Charset; +import java.util.Locale; + +public class NfcUtils { + + public static final String TAG = "NfcUtils"; + private static Gson sGson = new Gson(); + + // ------------------------------------------------------------------------- + // 读取 NFC 标签并解析为 NfcTermuxCmd + // ------------------------------------------------------------------------- + public static NfcTermuxCmd readTag(Tag tag) throws Exception { + if (tag == null) { + LogUtils.e(TAG, "readTag: tag is null"); + return null; + } + + Ndef ndef = Ndef.get(tag); + if (ndef == null) { + LogUtils.e(TAG, "readTag: 不支持 NDEF"); + return null; + } + + try { + ndef.connect(); + NdefMessage msg = ndef.getNdefMessage(); + if (msg == null) return null; + + NdefRecord[] records = msg.getRecords(); + if (records == null || records.length == 0) return null; + + byte[] payload = records[0].getPayload(); + int status = payload[0] & 0xFF; + int langLen = status & 0x3F; + int start = 1 + langLen; + + if (start >= payload.length) return null; + + String json = new String(payload, start, payload.length - start, Charset.forName("UTF-8")); + LogUtils.d(TAG, "readTag: 提取 JSON -> " + json); + + return sGson.fromJson(json, NfcTermuxCmd.class); + } finally { + if (ndef != null && ndef.isConnected()) { + ndef.close(); + } + } + } + + // ------------------------------------------------------------------------- + // 写入 NfcTermuxCmd 到 NFC 标签 + // ------------------------------------------------------------------------- + public static void writeTag(Tag tag, NfcTermuxCmd cmd) throws Exception { + if (tag == null) throw new Exception("tag is null"); + + String json = sGson.toJson(cmd); + writeJson(tag, json); + } + + // ------------------------------------------------------------------------- + // 写入原始 JSON 字符串到 NFC + // ------------------------------------------------------------------------- + public static void writeJson(Tag tag, String json) throws Exception { + if (tag == null) throw new Exception("tag is null"); + + Ndef ndef = Ndef.get(tag); + if (ndef == null) throw new Exception("标签不支持 NDEF"); + + try { + ndef.connect(); + int maxSize = ndef.getMaxSize(); + int realSize = json.getBytes(Charset.forName("UTF-8")).length; + + if (realSize > maxSize) { + throw new Exception("数据过大 (" + realSize + ") > 容量 (" + maxSize + ")"); + } + + NdefRecord record = createTextRecord(json, true); + NdefMessage msg = new NdefMessage(new NdefRecord[]{record}); + + ndef.writeNdefMessage(msg); + LogUtils.d(TAG, "writeJson: 写入成功"); + } finally { + if (ndef != null && ndef.isConnected()) { + ndef.close(); + } + } + } + + // ------------------------------------------------------------------------- + // 创建 NFC 文本记录 + // ------------------------------------------------------------------------- + public static NdefRecord createTextRecord(String text, boolean isUtf8) { + byte[] langBytes = "en".getBytes(Charset.forName("US-ASCII")); + byte[] textBytes = text.getBytes(Charset.forName(isUtf8 ? "UTF-8" : "UTF-16")); + + int status = isUtf8 ? 0 : 0x80; + status |= langBytes.length & 0x3F; + + byte[] data = new byte[1 + langBytes.length + textBytes.length]; + data[0] = (byte) status; + System.arraycopy(langBytes, 0, data, 1, langBytes.length); + System.arraycopy(textBytes, 0, data, 1 + langBytes.length, textBytes.length); + + return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[0], data); + } + + // ------------------------------------------------------------------------- + // 辅助:JSON -> NfcTermuxCmd + // ------------------------------------------------------------------------- + public static NfcTermuxCmd jsonToCmd(String json) throws JsonSyntaxException { + return sGson.fromJson(json, NfcTermuxCmd.class); + } + + // ------------------------------------------------------------------------- + // 辅助:NfcTermuxCmd -> JSON + // ------------------------------------------------------------------------- + public static String cmdToJson(NfcTermuxCmd cmd) { + return sGson.toJson(cmd); + } +} + diff --git a/autonfc/src/main/res/layout/activity_nfc_interface.xml b/autonfc/src/main/res/layout/activity_nfc_interface.xml index fa95874..b7e1800 100644 --- a/autonfc/src/main/res/layout/activity_nfc_interface.xml +++ b/autonfc/src/main/res/layout/activity_nfc_interface.xml @@ -1,46 +1,104 @@ - + - - + - + -