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; + } } }