diff --git a/app/src/main/java/com/ouxuan/oxface/OXFaceOnlineActivity.java b/app/src/main/java/com/ouxuan/oxface/OXFaceOnlineActivity.java index d416be4..91011f0 100644 --- a/app/src/main/java/com/ouxuan/oxface/OXFaceOnlineActivity.java +++ b/app/src/main/java/com/ouxuan/oxface/OXFaceOnlineActivity.java @@ -30,6 +30,9 @@ import android.widget.Toast; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; +import java.util.ArrayList; +import java.util.List; + import com.baidu.idl.face.main.finance.activity.BaseActivity; import com.baidu.idl.face.main.finance.callback.CameraDataCallback; import com.baidu.idl.face.main.finance.callback.FaceDetectCallBack; @@ -213,6 +216,18 @@ public class OXFaceOnlineActivity extends BaseActivity implements View.OnClickLi // 新增语音播放管理器 private VoicePlayerManager voicePlayerManager; + // 动态人数检测相关变量 + private boolean isDynamicPeopleDetectionEnabled = false; // 是否开启动态人数检测 + private boolean isDynamicPeopleDetectionRunning = false; // 动态检测是否正在运行 + private List peopleDetectionResults = new ArrayList<>(); // 检测结果数组 + private int gateLoopCountPeopleTimes = 0; // 防尾随记次 + private Handler dynamicDetectionHandler = new Handler(Looper.getMainLooper()); // 动态检测Handler + private Runnable dynamicDetectionRunnable; // 动态检测任务 + private static final int MAX_DETECTION_COUNT = 100; // 最大检测次数 + private int currentDetectionCount = 0; // 当前检测次数 + private boolean hasPlayedPeopleAnomalyVoice = false; // 是否已经播放过人数异常语音提醒 + private boolean hasShownPeopleAnomalyDialog = false; // 是否已经显示过人数异常弹窗 + // 方案二:添加状态标识控制订单选择页面的显示,避免重复弹出 private static boolean isOrderSelectionActivityShowing = false; // 订单选择页面是否正在显示 private static boolean isOrderVerificationResultActivityShowing = false; // 订单核销结果页面是否正在显示 @@ -451,6 +466,8 @@ public class OXFaceOnlineActivity extends BaseActivity implements View.OnClickLi @Override public void onVerificationSuccess(com.ouxuan.oxface.network.api.PadApiService.CheckOrderResult data, int verificationType) { + // 人脸识别开始,停止动态人数检测 + onFaceRecognitionStarted(); // 直接处理页面跳转逻辑,不在这里开门 orderVerificationResultHandler.handleVerificationSuccess(data, verificationType); } @@ -923,6 +940,26 @@ public class OXFaceOnlineActivity extends BaseActivity implements View.OnClickLi LogManager.logInfo(TAG, "AB门检测已关闭,跳过设置门状态监听器(触发时机1)"); } + // 设置门状态变化回调(用于动态人数检测) + gateABController.setGateStateChangeCallback(new GateABController.GateStateChangeCallback() { + @Override + public void onGateClosed() { + // 门关闭时开始动态人数检测 - 直接调用方法避免递归 + if (!isDynamicPeopleDetectionRunning) { + LogManager.logInfo(TAG, "检测到门关闭,开始动态人数检测"); + startDynamicPeopleDetection(); + } else { + LogManager.logInfo(TAG, "动态人数检测已在运行,跳过门关闭触发"); + } + } + + @Override + public void onGateOpened() { + // 门开启时的处理(暂时不需要) + LogManager.logInfo(TAG, "门开启事件触发"); + } + }); + // 设置弹窗操作监听器 gateUnavailableDialog.setDialogListener(new GateUnavailableDialog.GateUnavailableDialogListener() { @Override @@ -1969,6 +2006,14 @@ public class OXFaceOnlineActivity extends BaseActivity implements View.OnClickLi return; } else { LogManager.logInfo(TAG, "AB门人数棈测通过,继续人脸识别流程"); + + // AB门检查通过后,开始动态人数检测(防止尾随进入) + if (checkDynamicPeopleDetectionEnabled()) { + LogManager.logInfo(TAG, "AB门检查通过,开始动态人数检测"); + startDynamicPeopleDetection(); + } else { + LogManager.logInfo(TAG, "动态人数检测功能未开启"); + } } } catch (Exception e) { @@ -3208,4 +3253,304 @@ public class OXFaceOnlineActivity extends BaseActivity implements View.OnClickLi return false; } } + + /** + * 检查是否开启动态人数检测功能 + */ + private boolean checkDynamicPeopleDetectionEnabled() { + try { + VenueSceneUtils.GateConfig gateConfig = VenueSceneUtils.getGateConfig(this); + return gateConfig != null && gateConfig.gateCameraDetectHardLevel; + } catch (Exception e) { + LogManager.logError(TAG, "检查动态人数检测开关失败", e); + return false; + } + } + + /** + * 开始动态人数检测 + */ + private void startDynamicPeopleDetection() { + if (!checkDynamicPeopleDetectionEnabled()) { + LogManager.logInfo(TAG, "动态人数检测功能未开启"); + return; + } + + if (isDynamicPeopleDetectionRunning) { + LogManager.logInfo(TAG, "动态人数检测已在运行中"); + return; + } + + LogManager.logInfo(TAG, "开始动态人数检测"); + + // 初始化检测状态 + isDynamicPeopleDetectionRunning = true; + peopleDetectionResults.clear(); + currentDetectionCount = 0; + gateLoopCountPeopleTimes = 0; + hasPlayedPeopleAnomalyVoice = false; // 重置语音提醒标志 + hasShownPeopleAnomalyDialog = false; // 重置弹窗显示标志 + + // 创建检测任务 + dynamicDetectionRunnable = new Runnable() { + @Override + public void run() { + if (!isDynamicPeopleDetectionRunning || currentDetectionCount >= MAX_DETECTION_COUNT) { + stopDynamicPeopleDetection(); + return; + } + + currentDetectionCount++; + LogManager.logDebug(TAG, "执行动态人数检测,第" + currentDetectionCount + "次"); + + // 获取门内人数 + if (gateABController != null) { + gateABController.get485PeopleNum(new GateABController.PeopleNumCallback() { + @Override + public void onSuccess(int peopleNum) { + LogManager.logDebug(TAG, "检测到门内人数: " + peopleNum); + + // 记录检测结果 + peopleDetectionResults.add(peopleNum); + //输出peopleDetectionResults + LogManager.logDebug(TAG, "当前人数检测结果列表: " + peopleDetectionResults.toString()); + + // 如果人数>1,增加计数 + if (peopleNum > 1) { + gateLoopCountPeopleTimes++; + LogManager.logWarning(TAG, "检测到人数异常,当前计数: " + gateLoopCountPeopleTimes); + } + + // 检查是否门内已清空 + if (isGateEmpty(currentDetectionCount, peopleDetectionResults)) { + LogManager.logInfo(TAG, "检测到门内已清空,停止动态检测"); + stopDynamicPeopleDetection(); + return; + } + + // 检查并处理人数异常 - 传入当前检测到的人数 + checkPeopleCountAnomaly(peopleNum); + + // 计算下次检测间隔 + long nextInterval = calculateNextDetectionInterval(currentDetectionCount); + + // 安排下次检测 + if (isDynamicPeopleDetectionRunning && currentDetectionCount < MAX_DETECTION_COUNT) { + dynamicDetectionHandler.postDelayed(dynamicDetectionRunnable, nextInterval); + } else { + stopDynamicPeopleDetection(); + } + } + + @Override + public void onError(String errorMessage) { + LogManager.logError(TAG, "获取门内人数失败: " + errorMessage); + + // 即使失败也继续检测,但记录0 + peopleDetectionResults.add(0); + + // 计算下次检测间隔 + long nextInterval = calculateNextDetectionInterval(currentDetectionCount); + + // 安排下次检测 + if (isDynamicPeopleDetectionRunning && currentDetectionCount < MAX_DETECTION_COUNT) { + dynamicDetectionHandler.postDelayed(dynamicDetectionRunnable, nextInterval); + } else { + stopDynamicPeopleDetection(); + } + } + }); + } else { + LogManager.logError(TAG, "GateABController未初始化,无法进行人数检测"); + stopDynamicPeopleDetection(); + } + } + }; + + // 开始第一次检测 + dynamicDetectionHandler.post(dynamicDetectionRunnable); + } + + /** + * 停止动态人数检测 + */ + private void stopDynamicPeopleDetection() { + if (!isDynamicPeopleDetectionRunning) { + return; + } + + LogManager.logInfo(TAG, "停止动态人数检测,总检测次数: " + currentDetectionCount + ", 异常计数: " + gateLoopCountPeopleTimes); + + isDynamicPeopleDetectionRunning = false; + + // 移除待执行的任务 + if (dynamicDetectionRunnable != null) { + dynamicDetectionHandler.removeCallbacks(dynamicDetectionRunnable); + } + + // 最终检查人数 + checkPeopleCountAnomalyFinal(peopleDetectionResults); + } + + /** + * 计算下次检测间隔时间(毫秒) + */ + private long calculateNextDetectionInterval(int currentCount) { + long interval; + if (currentCount < 5) { + interval = 50; // 开始阶段:50ms + } else if (currentCount < 10) { + interval = 300; // 5-10次:300ms + } else if (currentCount < 15) { + interval = 600; // 10-15次:600ms + } else if (currentCount < 20) { + interval = 1000; // 15-20次:1000ms + } else if (currentCount < 30) { + interval = 1500; // 20-30次:1500ms + } else { + interval = 1500; // 30次以后:1500ms + } + return interval; + } + + /** + * 检查门内是否已清空 + */ + private boolean isGateEmpty(int currentIndex, List results) { + if (currentIndex >= 3 && results.size() >= 3) { + int recentSum = 0; + int startIndex = Math.max(0, currentIndex - 3); + for (int i = startIndex; i <= currentIndex && i < results.size(); i++) { + recentSum += results.get(i); + } + if (recentSum < 1) { + LogManager.logInfo(TAG, "最近4次检测总人数为0,判断门内已清空"); + return true; + } + } + return false; + } + + /** + * 检查人数异常并播放语音 + */ + private void checkPeopleCountAnomaly(int currentPeopleCount) { + if (currentPeopleCount > 1) { + // 当前检测到人数异常,只在第一次时播放提醒语音和显示弹窗 + if (!hasPlayedPeopleAnomalyVoice) { + hasPlayedPeopleAnomalyVoice = true; + hasShownPeopleAnomalyDialog = true; + playVoiceReminder("010"); // 请离场,确认门内外只有1人后再重新进入AB门 + showMultiplePeopleDetectedDialog(); // 显示人数异常弹窗 + LogManager.logInfo(TAG, "首次检测到人数异常,播放语音提醒并显示弹窗"); + } else { + LogManager.logDebug(TAG, "检测到人数异常,但已播放过语音提醒,跳过"); + } + } + } + + /** + * 最终检查人数异常 + */ + private void checkPeopleCountAnomalyFinal(List results) { + if (results.isEmpty()) { + return; + } + + // 检查是否所有检测结果都小于2 + boolean allLessThanTwo = true; + for (int num : results) { + if (num >= 2) { + allLessThanTwo = false; + break; + } + } + + if (allLessThanTwo) { + // 重置计数器 + gateLoopCountPeopleTimes = 0; + LogManager.logInfo(TAG, "所有检测结果都正常,重置异常计数器"); + } else { + // 增加计数器 + gateLoopCountPeopleTimes++; + LogManager.logWarning(TAG, "检测到异常人数,最终异常计数: " + gateLoopCountPeopleTimes); + + // 如果异常严重,显示弹窗 + if (gateLoopCountPeopleTimes >= 3) { + showMultiplePeopleDetectedDialog(); + } + } + } + + /** + * 显示多人检测弹窗 + */ + private void showMultiplePeopleDetectedDialog() { + runOnUiThread(() -> { + try { + if (gateUnavailableDialog != null) { + // 显示进场场景人数异常弹窗,使用当前检测到的人数 + int currentPeopleCount = peopleDetectionResults.isEmpty() ? 0 : peopleDetectionResults.get(peopleDetectionResults.size() - 1); + gateUnavailableDialog.updatePeopleCountError(false, currentPeopleCount); + LogManager.logWarning(TAG, "显示多人检测异常弹窗,当前人数: " + currentPeopleCount); + } else { + LogManager.logError(TAG, "GateUnavailableDialog未初始化,无法显示人数异常弹窗"); + } + } catch (Exception e) { + LogManager.logError(TAG, "显示多人检测弹窗失败", e); + } + }); + } + + /** + * 播放语音提醒 + */ + private void playVoiceReminder(String voiceType) { + try { + if (voicePlayerManager != null) { + // 根据voiceType字符串找到对应的VoiceType枚举 + VoiceType voiceEnum = null; + switch (voiceType) { + case "010": + // 暂时使用ONE_PERSON_ENTRY作为人数异常提醒 + voiceEnum = VoiceType.ONE_PERSON_ENTRY; + break; + default: + LogManager.logWarning(TAG, "未知的语音类型: " + voiceType); + return; + } + + if (voiceEnum != null) { + voicePlayerManager.playVoice(voiceEnum); + LogManager.logInfo(TAG, "播放语音提醒: " + voiceType + " -> " + voiceEnum.getDescription()); + } + } else { + LogManager.logWarning(TAG, "VoicePlayerManager未初始化,无法播放语音"); + } + } catch (Exception e) { + LogManager.logError(TAG, "播放语音提醒失败", e); + } + } + + /** + * 当门关闭时触发动态人数检测 + */ + public void onGateClosed() { + // 如果动态检测已经在运行(通过AB门检查启动的),则不再重复启动 + if (isDynamicPeopleDetectionRunning) { + LogManager.logInfo(TAG, "动态人数检测已在运行,跳过门关闭触发"); + return; + } + + LogManager.logInfo(TAG, "检测到门关闭,开始动态人数检测"); + startDynamicPeopleDetection(); + } + + /** + * 当人脸识别开始时停止动态检测 + */ + public void onFaceRecognitionStarted() { + LogManager.logInfo(TAG, "人脸识别开始,停止动态人数检测"); + stopDynamicPeopleDetection(); + } } \ No newline at end of file diff --git a/app/src/main/java/com/ouxuan/oxface/abgate/GateUnavailableDialog.java b/app/src/main/java/com/ouxuan/oxface/abgate/GateUnavailableDialog.java index 6f854c9..0fe62ec 100644 --- a/app/src/main/java/com/ouxuan/oxface/abgate/GateUnavailableDialog.java +++ b/app/src/main/java/com/ouxuan/oxface/abgate/GateUnavailableDialog.java @@ -353,6 +353,7 @@ public class GateUnavailableDialog { public void updatePeopleCountError(boolean isLeaveScene, int peopleCount) { // 检查是否需要显示人数异常弹窗 // 对于进场场景,只有当人数大于等于2时才需要显示弹窗 + // 对于离场场景,只有当人数大于等于1时才需要显示弹窗 if (!isLeaveScene && peopleCount < 2) { LogManager.logInfo(TAG, "进场场景人数小于2人,不更新人数异常弹窗,人数: " + peopleCount); return; diff --git a/app/src/main/java/com/ouxuan/oxface/device/GateABController.java b/app/src/main/java/com/ouxuan/oxface/device/GateABController.java index e33c856..f76bc01 100644 --- a/app/src/main/java/com/ouxuan/oxface/device/GateABController.java +++ b/app/src/main/java/com/ouxuan/oxface/device/GateABController.java @@ -115,6 +115,7 @@ public class GateABController { // AB门状态实例和监听器 private GateABState currentGateState; private GateABStateListener gateStateListener; + private GateStateChangeCallback gateStateChangeCallback; /** * AB门配置类 @@ -1019,6 +1020,14 @@ public class GateABController { } /** + * 门状态变化回调接口 + */ + public interface GateStateChangeCallback { + void onGateClosed(); + void onGateOpened(); + } + + /** * 获取UDP控制器实例(供外部使用) * @return UDP控制器实例 */ @@ -1035,6 +1044,14 @@ public class GateABController { } /** + * 设置门状态变化回调 + * @param callback 状态变化回调 + */ + public void setGateStateChangeCallback(GateStateChangeCallback callback) { + this.gateStateChangeCallback = callback; + } + + /** * 获取当前AB门状态 * @return 当前门状态 */ @@ -1055,6 +1072,7 @@ public class GateABController { boolean shouldShowDialog = false; boolean oldShouldShow = false; boolean newShouldShow = false; + boolean oldGateAOpen = false; // 保存旧的A门状态,用于检测门关闭事件 // 检查状态是否发生变化 if (currentGateState.gateAOpen != gateAOpen || @@ -1062,6 +1080,9 @@ public class GateABController { currentGateState.udpConnected != udpConnected) { stateChanged = true; + // 保存旧的A门状态,用于检测门关闭事件 + oldGateAOpen = currentGateState.gateAOpen; + // 检查是否需要触发门禁不可用弹窗 oldShouldShow = currentGateState.shouldShowUnavailableDialog(); @@ -1092,6 +1113,18 @@ public class GateABController { currentGateState.lastUpdateTime = System.currentTimeMillis(); currentGateState.errorMessage = errorMessage != null ? errorMessage : ""; + // 检测门关闭事件(用于触发动态人数检测)- 只检测A门 + if (stateChanged && gateStateChangeCallback != null) { + // 使用前面保存的旧状态进行比较 + boolean gateAClosed = oldGateAOpen && !gateAOpen; // A门从开启变为关闭 + + if (gateAClosed) { + // A门从开启变为关闭 + LogManager.logInfo(TAG, "检测到A门关闭事件(A门从开启变为关闭),触发动态人数检测"); + gateStateChangeCallback.onGateClosed(); + } + } + LogManager.logInfo(TAG, "更新AB门状态: " + currentGateState.toString()); // Fix 9.2: 当需要显示门禁不可用弹窗时,先通过广播关闭OrderSelectionActivity,再处理弹窗 diff --git a/app/src/main/java/com/ouxuan/oxface/utils/LogManager.java b/app/src/main/java/com/ouxuan/oxface/utils/LogManager.java index 0645811..42959ab 100644 --- a/app/src/main/java/com/ouxuan/oxface/utils/LogManager.java +++ b/app/src/main/java/com/ouxuan/oxface/utils/LogManager.java @@ -134,6 +134,7 @@ public class LogManager { if (instance != null) { instance.addLogEntry("DEBUG", tag, message, null); } + android.util.Log.d(tag,message); //切换为logcat输入日志 } /** diff --git a/app/src/main/java/com/ouxuan/oxface/utils/VenueSceneUtils.java b/app/src/main/java/com/ouxuan/oxface/utils/VenueSceneUtils.java index 3b2143e..f91d58e 100644 --- a/app/src/main/java/com/ouxuan/oxface/utils/VenueSceneUtils.java +++ b/app/src/main/java/com/ouxuan/oxface/utils/VenueSceneUtils.java @@ -197,11 +197,16 @@ public class VenueSceneUtils { padConfig.getExtension().getPadSetting() != null && padConfig.getExtension().getPadSetting().isGateAbUdp(); + gateConfig.gateCameraDetectHardLevel = padConfig.getExtension() != null && + padConfig.getExtension().getPadSetting() != null && + padConfig.getExtension().getPadSetting().isGateCameraDetectHardLevel(); + Log.d(TAG, "门禁配置 - EnterOpenEnable: " + gateConfig.gateEnterOpenEnable + ", OpenEnable: " + gateConfig.gateOpenEnable + ", AbGpio: " + gateConfig.gateAbGpio + ", AbUdp: " + gateConfig.gateAbUdp + - ", AbEnable: " + gateConfig.gateAbEnable); + ", AbEnable: " + gateConfig.gateAbEnable + + ", CameraDetectHardLevel: " + gateConfig.gateCameraDetectHardLevel); return gateConfig; } else { @@ -223,6 +228,7 @@ public class VenueSceneUtils { public boolean gateAbGpio = false; // 门禁AB GPIO public boolean gateAbUdp = false; // 门禁AB UDP public boolean gateAbEnable = false; // ab门总开关 + public boolean gateCameraDetectHardLevel = false; // 人数检测严格判定开关 @Override public String toString() { @@ -232,6 +238,7 @@ public class VenueSceneUtils { ", gateAbGpio=" + gateAbGpio + ", gateAbUdp=" + gateAbUdp + ", gateAbEnable=" + gateAbEnable + + ", gateCameraDetectHardLevel=" + gateCameraDetectHardLevel + '}'; } }