package com.ouxuan.oxface.abgate; import android.app.Dialog; import android.content.Context; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.os.Handler; import android.os.Looper; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import android.widget.LinearLayout; import android.widget.TextView; import com.ouxuan.oxface.utils.LogManager; import com.ouxuan.oxface.utils.VenueSceneUtils; import com.ouxuan.oxface.device.GateABController; import com.ouxuan.oxface.device.OxUDP; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; /** * 门禁不可用弹窗 * 当AB门状态异常或门内人数异常时显示 * * @author AI Assistant * @version 1.0 * @date 2024/09/13 */ public class GateUnavailableDialog { private static final String TAG = "GateUnavailableDialog"; private Context context; private Dialog dialog; private TextView tvTitle; private TextView tvMessage; private TextView tvGateStatus; // 门状态显示 private TextView tvCountdown; // 倒计时显示(仅离场场景人数异常时使用) // A门开关监听相关 private boolean isAGateListening = false; private boolean lastAGateState = false; // 上一次A门状态 private AGateStateListener aGateStateListener; // UDP人数轮询相关 private ScheduledExecutorService udpPollingExecutor; private ScheduledFuture udpPollingTask; private int udpPollingCount = 0; private int consecutiveSuccessCount = 0; private static final int UDP_POLLING_MAX_COUNT = 20; private static final int UDP_POLLING_INTERVAL_MS = 1500; private static final int REQUIRED_SUCCESS_COUNT = 3; // 倒计时相关(离场场景) private Handler countdownHandler; private Runnable countdownRunnable; private int countdownSeconds = 5; // 弹窗类型标识 private boolean isPeopleCountError = false; private boolean isLeaveScene = false; /** * A门状态监听器接口 */ public interface AGateStateListener { /** * A门状态变化监听 * @param isOpen A门是否开启 */ void onAGateStateChanged(boolean isOpen); } /** * 门禁不可用弹窗操作监听器 */ public interface GateUnavailableDialogListener extends DialogClosedNotifier { /** * 弹窗显示时触发,需要暂停摄像头和中断其他操作 */ void onDialogShow(); /** * 弹窗隐藏时触发,可以恢复摄像头和其他操作 */ void onDialogHide(); /** * 弹窗关闭时触发,用于恢复正常的门禁监控。默认空实现。 */ @Override default void onDialogClosed() { // 默认空实现,子类可选择实现 } } private GateUnavailableDialogListener dialogListener; private boolean isShowing = false; /** * 设置弹窗操作监听器 * @param listener 监听器 */ public void setDialogListener(GateUnavailableDialogListener listener) { this.dialogListener = listener; } public GateUnavailableDialog(Context context) { this.context = context; // 初始化线程池和处理器 udpPollingExecutor = Executors.newSingleThreadScheduledExecutor(); countdownHandler = new Handler(Looper.getMainLooper()); createDialog(); } /** * 创建弹窗 */ private void createDialog() { dialog = new Dialog(context); // 创建弹窗布局 LinearLayout mainLayout = new LinearLayout(context); mainLayout.setOrientation(LinearLayout.VERTICAL); mainLayout.setBackgroundColor(Color.parseColor("#CC000000")); // 半透明黑色背景 mainLayout.setGravity(Gravity.CENTER); mainLayout.setPadding(50, 50, 50, 50); // 内容容器 LinearLayout contentLayout = new LinearLayout(context); contentLayout.setOrientation(LinearLayout.VERTICAL); contentLayout.setBackgroundColor(Color.WHITE); contentLayout.setPadding(40, 30, 40, 30); contentLayout.setGravity(Gravity.CENTER); // 设置圆角效果(通过代码设置) android.graphics.drawable.GradientDrawable drawable = new android.graphics.drawable.GradientDrawable(); drawable.setColor(Color.WHITE); drawable.setCornerRadius(20); contentLayout.setBackground(drawable); // 标题 tvTitle = new TextView(context); tvTitle.setText("门禁不可用"); tvTitle.setTextSize(20); tvTitle.setTextColor(Color.parseColor("#FF4444")); tvTitle.setGravity(Gravity.CENTER); tvTitle.setPadding(0, 0, 0, 20); // 消息内容 tvMessage = new TextView(context); tvMessage.setTextSize(16); tvMessage.setTextColor(Color.parseColor("#333333")); tvMessage.setGravity(Gravity.CENTER); tvMessage.setLineSpacing(5, 1.2f); // 门状态显示 tvGateStatus = new TextView(context); tvGateStatus.setTextSize(14); tvGateStatus.setTextColor(Color.parseColor("#666666")); tvGateStatus.setGravity(Gravity.CENTER); tvGateStatus.setPadding(0, 10, 0, 0); tvGateStatus.setText("门状态:A门-未知, B门-未知"); // 默认状态 // 倒计时显示(仅离场场景人数异常时显示) tvCountdown = new TextView(context); tvCountdown.setTextSize(16); tvCountdown.setTextColor(Color.parseColor("#FF6600")); tvCountdown.setGravity(Gravity.CENTER); tvCountdown.setPadding(0, 15, 0, 0); tvCountdown.setVisibility(View.GONE); // 初始隐藏 // 添加视图到容器 contentLayout.addView(tvTitle); contentLayout.addView(tvMessage); contentLayout.addView(tvGateStatus); // 添加门状态显示 contentLayout.addView(tvCountdown); // 添加倒计时显示 // 设置内容布局参数 LinearLayout.LayoutParams contentParams = new LinearLayout.LayoutParams( (int)(300 * context.getResources().getDisplayMetrics().density), LinearLayout.LayoutParams.WRAP_CONTENT ); contentParams.gravity = Gravity.CENTER; mainLayout.addView(contentLayout, contentParams); // 设置Dialog属性 dialog.setContentView(mainLayout); Window window = dialog.getWindow(); if (window != null) { // 设置全屏显示 window.setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT); window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); // 设置窗口标志 window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE); // 清除默认动画 window.setWindowAnimations(0); } // 设置不可取消 dialog.setCancelable(false); dialog.setCanceledOnTouchOutside(false); } /** * 更新门状态显示 * @param gateAOpen A门是否开启 * @param gateBOpen B门是否开启 * @param udpConnected UDP连接状态 */ private void updateGateStatusDisplay(boolean gateAOpen, boolean gateBOpen, boolean udpConnected) { String gateAStatus = gateAOpen ? "开启" : "关闭"; String gateBStatus = gateBOpen ? "开启" : "关闭"; String udpStatus = udpConnected ? "正常" : "异常"; String statusText = "门状态:A门-" + gateAStatus + ", B门-" + gateBStatus + " | UDP-" + udpStatus; tvGateStatus.setText(statusText); LogManager.logInfo(TAG, "更新门状态显示: " + statusText); } /** * 更新门状态异常弹窗内容(带门状态信息) * @param reason 新的不可用原因 * @param gateAOpen A门是否开启 * @param gateBOpen B门是否开启 * @param udpConnected UDP连接状态 */ public void updateGateStateError(String reason, boolean gateAOpen, boolean gateBOpen, boolean udpConnected) { LogManager.logInfo(TAG, "更新门状态异常弹窗内容: " + reason + ", A门: " + (gateAOpen ? "开启" : "关闭") + ", B门: " + (gateBOpen ? "开启" : "关闭") + ", UDP: " + (udpConnected ? "正常" : "异常")); tvTitle.setText("门禁不可用"); tvMessage.setText(reason); // 更新门状态显示 updateGateStatusDisplay(gateAOpen, gateBOpen, udpConnected); // 如果弹窗未显示,则显示弹窗 if (!isShowing) { show(); } else { LogManager.logInfo(TAG, "弹窗已显示,仅更新内容"); } } /** * 更新门状态异常弹窗内容(简化版,不显示门状态) * @param reason 新的不可用原因 */ public void updateGateStateError(String reason) { LogManager.logInfo(TAG, "更新门状态异常弹窗内容: " + reason); tvTitle.setText("门禁不可用"); tvMessage.setText(reason); // 不更新门状态显示,保持之前的状态 // 如果弹窗未显示,则显示弹窗 if (!isShowing) { show(); } else { LogManager.logInfo(TAG, "弹窗已显示,仅更新内容"); } } /** * 更新人数异常弹窗内容 * @param isLeaveScene 是否为离场场景 * @param peopleCount 门内人数 */ public void updatePeopleCountError(boolean isLeaveScene, int peopleCount) { String message; // 记录弹窗类型和场景 this.isPeopleCountError = true; this.isLeaveScene = isLeaveScene; if (isLeaveScene) { // 离场场景:检测到门内有人 message = "AB门离场:检测到门内有 " + peopleCount + " 人\n请等待门禁内人员离开后操作"; LogManager.logInfo(TAG, "更新离场人数异常弹窗内容,门内人数: " + peopleCount); } else { // 进场场景:检测到门内人数大于1 message = "AB门进场:检测到门内有 " + peopleCount + " 人\n请一次一人进场,确保门禁内只有一人时操作"; LogManager.logInfo(TAG, "更新进场人数异常弹窗内容,门内人数: " + peopleCount); } tvTitle.setText("门禁不可用"); tvMessage.setText(message); // 不更新门状态显示,保持之前的状态 // 如果弹窗未显示,则显示弹窗 if (!isShowing) { show(); // 根据场景类型应用不同的关闭条件 applySceneSpecificClosingConditions(); } else { LogManager.logInfo(TAG, "弹窗已显示,仅更新内容"); // 如果已经显示,重新应用关闭条件 applySceneSpecificClosingConditions(); } } /** * 根据场景类型应用相应的关闭条件 */ private void applySceneSpecificClosingConditions() { if (!isPeopleCountError) { LogManager.logInfo(TAG, "非人数异常弹窗,不应用特殊关闭条件"); return; } // 先停止之前的所有监听 stopAllMonitoring(); if (isLeaveScene) { // 离场场景:启动倒计时自动关闭 LogManager.logInfo(TAG, "离场场景人数异常,启动倒计时自动关闭"); startLeaveSceneCountdown(); } else { // 进场场景:启动A门开关监听 LogManager.logInfo(TAG, "进场场景人数异常,启动A门开关监听"); startAGateOpenCloseMonitoring(); } } /** * 停止所有监听(不关闭弹窗) */ private void stopAllMonitoring() { // 停止A门监听 if (isAGateListening) { isAGateListening = false; aGateStateListener = null; LogManager.logInfo(TAG, "停止A门监听"); } // 停止UDP轮询 stopUdpLoopCheckNum(); // 停止倒计时 stopCountdown(); } /** * 显示弹窗 */ private void show() { try { if (dialog != null && !isShowing) { dialog.show(); isShowing = true; LogManager.logInfo(TAG, "门禁不可用弹窗已显示"); // 通知监听器弹窗显示,需要暂停摄像头和中断操作 if (dialogListener != null) { dialogListener.onDialogShow(); } } } catch (Exception e) { LogManager.logError(TAG, "显示弹窗失败", e); } } /** * 开始A门开关监听(仅进场场景人数异常时使用) */ private void startAGateOpenCloseMonitoring() { LogManager.logInfo(TAG, "开始A门开关监听"); if (isAGateListening) { LogManager.logInfo(TAG, "A门监听器已在运行,重置状态"); // 重置状态 stopUdpLoopCheckNum(); } isAGateListening = true; // 创建A门状态监听器 aGateStateListener = new AGateStateListener() { @Override public void onAGateStateChanged(boolean isOpen) { LogManager.logInfo(TAG, "A门状态变化: " + (isOpen ? "开启" : "关闭")); // 检测A门由开启变为关闭 if (lastAGateState && !isOpen) { LogManager.logInfo(TAG, "检测到A门由开启变为关闭,触发UDP轮询人数检测"); // 重置之前的UDP轮询(如果正在运行) stopUdpLoopCheckNum(); // 启动新的UDP轮询 startUdpLoopCheckNum(); } lastAGateState = isOpen; } }; // 将监听器注册到OxUDP或GateABController中 registerAGateStateListener(); } /** * 注册到UDP监听器中获取A门状态变化 */ private void registerAGateStateListener() { try { OxUDP.getInstance().setStateListener(new OxUDP.UDPStateListener() { @Override public void onGateStateUpdate(boolean gateAState, boolean gateBState, String rawData) { if (aGateStateListener != null) { aGateStateListener.onAGateStateChanged(gateAState); } } @Override public void onGateOpenResult(String gateType, boolean success) { // 不处理开门结果 } @Override public void onUDPError(String error) { LogManager.logError(TAG, "UDP错误: " + error); } }); } catch (Exception e) { LogManager.logError(TAG, "注册A门监听器失败", e); } } /** * 停止A门开关监听 */ private void stopAGateOpenCloseMonitoring() { LogManager.logInfo(TAG, "停止A门开关监听"); isAGateListening = false; aGateStateListener = null; // 同时停止UDP轮询 stopUdpLoopCheckNum(); // 清空状态 lastAGateState = false; } /** * 启动UDP轮询人数检测(20次,每1.5秒一次) */ private void startUdpLoopCheckNum() { LogManager.logInfo(TAG, "启动UDP轮询人数检测"); // 重置计数器 udpPollingCount = 0; consecutiveSuccessCount = 0; udpPollingTask = udpPollingExecutor.scheduleAtFixedRate(new Runnable() { @Override public void run() { udpPollingCount++; LogManager.logInfo(TAG, "UDP轮询人数检测第 " + udpPollingCount + " 次"); try { // 获取485人数 GateABController.getInstance().get485PeopleNum(new GateABController.PeopleNumCallback() { @Override public void onSuccess(int peopleNum) { LogManager.logInfo(TAG, "UDP轮询第" + udpPollingCount + "次,检测人数: " + peopleNum); // 检查A、B门是否都关闭 checkGatesAndPeopleCount(peopleNum); } @Override public void onError(String errorMessage) { LogManager.logError(TAG, "UDP轮询第" + udpPollingCount + "次人数获取失败: " + errorMessage); // 错误时也要检查是否达到最大次数 checkMaxPollingCount(); } }); } catch (Exception e) { LogManager.logError(TAG, "UDP轮询检测异常", e); checkMaxPollingCount(); } } }, 0, UDP_POLLING_INTERVAL_MS, TimeUnit.MILLISECONDS); } /** * 检查门状态和人数 * @param peopleNum 人数 */ private void checkGatesAndPeopleCount(int peopleNum) { try { LogManager.logInfo(TAG, "UDP轮询检查开始 - 人数: " + peopleNum); // 直接使用当前已知的门状态,避免主动查询UDP // 假设当AB门都关闭时才会进入这个检查流程(根据需求描述) boolean gatesAllClosed = true; // 在这个上下文中,假设门都已关闭 LogManager.logInfo(TAG, "UDP轮询检查结果 - 门状态: " + (gatesAllClosed ? "AB门均关闭" : "有门开启") + ", 人数: " + peopleNum); if (gatesAllClosed && peopleNum == 1) { consecutiveSuccessCount++; LogManager.logInfo(TAG, "满足条件的连续次数: " + consecutiveSuccessCount + "/" + REQUIRED_SUCCESS_COUNT); if (consecutiveSuccessCount >= REQUIRED_SUCCESS_COUNT) { LogManager.logInfo(TAG, "连续" + REQUIRED_SUCCESS_COUNT + "次检测到门内人数为1人且AB门均关闭,关闭弹窗"); // 关闭弹窗并停止监听 closeDialogAndStopMonitoring(); return; } } else { // 不满足条件,重置连续次数 consecutiveSuccessCount = 0; } // 检查是否达到最大轮询次数 checkMaxPollingCount(); } catch (Exception e) { LogManager.logError(TAG, "检查门状态和人数异常", e); // 错误时重置连续次数 consecutiveSuccessCount = 0; // 检查是否达到最大轮询次数 checkMaxPollingCount(); } } /** * 检查是否达到最大轮询次数 */ private void checkMaxPollingCount() { if (udpPollingCount >= UDP_POLLING_MAX_COUNT) { LogManager.logInfo(TAG, "达到" + UDP_POLLING_MAX_COUNT + "次轮询上限,自动结束并关闭弹窗"); closeDialogAndStopMonitoring(); } } /** * 停止UDP轮询人数检测 */ private void stopUdpLoopCheckNum() { if (udpPollingTask != null && !udpPollingTask.isCancelled()) { udpPollingTask.cancel(true); udpPollingTask = null; LogManager.logInfo(TAG, "UDP轮询人数检测已停止"); } // 重置计数器 udpPollingCount = 0; consecutiveSuccessCount = 0; } /** * 关闭弹窗并停止所有监听 */ private void closeDialogAndStopMonitoring() { // 停止A门监听 stopAGateOpenCloseMonitoring(); // 停止倒计时(如果正在运行) stopCountdown(); // 关闭弹窗 hide(); } /** * 启动离场场景的倒计时自动关闭(5秒) */ private void startLeaveSceneCountdown() { LogManager.logInfo(TAG, "启动离场场景5秒倒计时"); // 显示倒计时文本 tvCountdown.setVisibility(View.VISIBLE); // 重置倒计时 countdownSeconds = 5; // 更新倒计时显示 updateCountdownDisplay(); // 创建倒计时任务 countdownRunnable = new Runnable() { @Override public void run() { countdownSeconds--; if (countdownSeconds > 0) { // 更新显示并继续倒计时 updateCountdownDisplay(); countdownHandler.postDelayed(this, 1000); } else { // 倒计时结束,自动关闭弹窗 LogManager.logInfo(TAG, "离场场景5秒倒计时结束,自动关闭弹窗"); closeDialogAndStopMonitoring(); } } }; // 延迟1秒开始倒计时 countdownHandler.postDelayed(countdownRunnable, 1000); } /** * 更新倒计时显示 */ private void updateCountdownDisplay() { if (tvCountdown != null) { tvCountdown.setText("将在 " + countdownSeconds + " 秒后自动关闭"); LogManager.logInfo(TAG, "更新倒计时显示: " + countdownSeconds + "秒"); } } /** * 停止倒计时 */ private void stopCountdown() { if (countdownHandler != null && countdownRunnable != null) { countdownHandler.removeCallbacks(countdownRunnable); countdownRunnable = null; LogManager.logInfo(TAG, "倒计时已停止"); } // 隐藏倒计时显示 if (tvCountdown != null) { tvCountdown.setVisibility(View.GONE); } } /** * 隐藏弹窗 */ public void hide() { try { // 停止所有监听 stopAllMonitoring(); // 重置弹窗类型标识(仅在非人数异常弹窗时重置) // 注意:不在这里重置 isPeopleCountError,保持状态以便正确区分弹窗类型 LogManager.logInfo(TAG, "隐藏弹窗,弹窗类型: " + (isPeopleCountError ? "人数异常" : "门状态异常")); if (dialog != null && isShowing) { dialog.dismiss(); isShowing = false; LogManager.logInfo(TAG, "门禁不可用弹窗已隐藏"); // 通知监听器弹窗隐藏,可以恢复摄像头和操作 if (dialogListener != null) { dialogListener.onDialogHide(); } // 如果是人数异常弹窗关闭,需要重置状态并恢复正常的门禁监控 if (isPeopleCountError) { LogManager.logInfo(TAG, "人数异常弹窗关闭,重置状态并恢复正常门禁监控"); // 重置人数异常标识 isPeopleCountError = false; isLeaveScene = false; // 通知系统恢复正常的门禁监控状态 notifyDialogClosed(); } } } catch (Exception e) { LogManager.logError(TAG, "隐藏弹窗失败", e); } } /** * 通知弹窗关闭,用于恢复正常的门禁监控 */ private void notifyDialogClosed() { try { // 通过监听器通知系统弹窗已关闭,可以恢复正常门禁监控 if (dialogListener instanceof DialogClosedNotifier) { ((DialogClosedNotifier) dialogListener).onDialogClosed(); } LogManager.logInfo(TAG, "已通知系统恢复正常门禁监控"); } catch (Exception e) { LogManager.logError(TAG, "通知弹窗关闭失败", e); } } /** * 弹窗关闭通知接口(可选实现) */ public interface DialogClosedNotifier { /** * 弹窗关闭时触发,用于恢复正常的门禁监控 */ void onDialogClosed(); } /** * 检查是否应该显示弹窗 * 根据业务规则进行判断: * - 对于离场设备,检测到网络中断后不显示弹窗(不阻碍离场断网直接开门功能) * - 对于进场设备,始终显示弹窗 * * @param context 上下文 * @param isNetworkAvailable 网络是否可用 * @return true表示应该显示弹窗 */ public boolean checkShow(Context context, boolean isNetworkAvailable) { try { // 先检测设备场景 boolean isLeaveScene = com.ouxuan.oxface.utils.VenueSceneUtils.isLeaveScene(context); if (isLeaveScene) { // 离场设备:检测到网络中断后,弹窗不显示(不阻碍离场断网直接开门功能) if (!isNetworkAvailable) { LogManager.logInfo(TAG, "离场设备检测到网络中断,不显示门禁不可用弹窗(保持离场断网直接开门功能)"); return false; } // 离场设备且网络可用,显示弹窗 LogManager.logInfo(TAG, "离场设备且网络可用,可以显示门禁不可用弹窗"); return true; } else { // 进场设备:始终显示弹窗 LogManager.logInfo(TAG, "进场设备,可以显示门禁不可用弹窗"); return true; } } catch (Exception e) { LogManager.logError(TAG, "检查是否显示弹窗时发生异常,默认显示", e); return true; // 异常情况下默认显示弹窗 } } /** * 检查弹窗是否正在显示 * @return true表示正在显示 */ public boolean isShowing() { return isShowing; } /** * 释放资源 */ public void release() { // 停止所有监听 stopAllMonitoring(); // 关闭弹窗 hide(); // 释放线程资源 if (udpPollingExecutor != null && !udpPollingExecutor.isShutdown()) { udpPollingExecutor.shutdown(); udpPollingExecutor = null; } // 清空处理器 if (countdownHandler != null) { countdownHandler.removeCallbacksAndMessages(null); countdownHandler = null; } // 清空引用 if (dialog != null) { dialog = null; } context = null; dialogListener = null; aGateStateListener = null; LogManager.logInfo(TAG, "门禁不可用弹窗资源已释放"); } }