NFC调用Termux流程基本通顺。

This commit is contained in:
2026-03-16 16:45:45 +08:00
parent 528935fab7
commit e0ac8f1f60
5 changed files with 309 additions and 91 deletions

View File

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

View File

@@ -6,7 +6,8 @@
<uses-permission android:name="android.permission.NFC"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-feature
android:name="android.hardware.nfc"
android:required="true"/>

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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<zhangsken@qq.com>
* 创建时间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;
}
}
}