oxFaceAndroid
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

784 lines
28 KiB

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, "门禁不可用弹窗资源已释放");
}
}