Browse Source

add secret alert

devab
赵明涛 4 weeks ago
parent
commit
0d937d208a
  1. 102
      app/src/main/java/com/ouxuan/oxface/OXFaceOnlineActivity.java
  2. 68
      app/src/main/java/com/ouxuan/oxface/abgate/GateUnavailableDialog.java
  3. 412
      app/src/main/java/com/ouxuan/oxface/device/UnlockPasswordDialog.java
  4. 208
      解锁密码弹窗功能实现说明.md

102
app/src/main/java/com/ouxuan/oxface/OXFaceOnlineActivity.java

@ -172,9 +172,12 @@ public class OXFaceOnlineActivity extends BaseActivity implements View.OnClickLi
private TextView tvStoreName;
private int storeNameClickCount = 0;
private long lastStoreNameClickTime = 0;
private static final int MAX_CLICK_COUNT = 5;
private static final int MAX_CLICK_COUNT = 6; // 修改为6次
private static final long CLICK_INTERVAL = 1000; // 1秒内点击有效
// 新增解锁密码弹窗相关变量
private com.ouxuan.oxface.device.UnlockPasswordDialog unlockPasswordDialog;
// 新增订单查验相关变量
private int modeType = 0; // 1验证码验证 2人脸验证 3扫码验证 4扫码器验证
private String verifyCode = ""; // 验证码
@ -222,6 +225,9 @@ public class OXFaceOnlineActivity extends BaseActivity implements View.OnClickLi
// 初始化摄像头控制广播接收器
initCameraControlReceiver();
// 初始化解锁密码弹窗
initUnlockPasswordDialog();
initView();
// 初始化网络状态指示器
@ -846,6 +852,15 @@ public class OXFaceOnlineActivity extends BaseActivity implements View.OnClickLi
}
});
// 设置解锁密码弹窗触发器
gateUnavailableDialog.setUnlockTrigger(new GateUnavailableDialog.UnlockPasswordDialogTrigger() {
@Override
public void triggerUnlockPasswordDialog() {
LogManager.logInfo(TAG, "门禁不可用弹窗标题触发解锁密码弹窗");
showUnlockPasswordDialog();
}
});
// 检查是否开启AB门检测
checkGateCheckEnabled();
@ -1458,6 +1473,13 @@ public class OXFaceOnlineActivity extends BaseActivity implements View.OnClickLi
LogManager.logInfo(TAG, "摄像头控制广播接收器已注销");
}
// 释放解锁密码弹窗资源
if (unlockPasswordDialog != null) {
unlockPasswordDialog.release();
unlockPasswordDialog = null;
LogManager.logInfo(TAG, "解锁密码弹窗资源已释放");
}
// 停止摄像头预览
if (CameraPreviewManager.getInstance() != null) {
CameraPreviewManager.getInstance().stopPreview();
@ -2143,37 +2165,87 @@ public class OXFaceOnlineActivity extends BaseActivity implements View.OnClickLi
LogManager.logDebug(TAG, "店铺名称被点击,当前计数: " + storeNameClickCount);
// 如果连续点击达到5次
// 如果连续点击达到6次触发解锁密码弹窗
if (storeNameClickCount >= MAX_CLICK_COUNT) {
LogManager.logInfo(TAG, "连续点击5次店铺名称,准备返回主界面");
Toast.makeText(this, "即将返回主界面", Toast.LENGTH_SHORT).show();
LogManager.logInfo(TAG, "连续点击6次店铺名称,触发解锁密码弹窗");
// 重置计数器
storeNameClickCount = 0;
// 延迟一段时间后返回主界面
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
returnToMainActivity();
}
}, 500); // 延迟0.5秒后返回
// 显示解锁密码弹窗
showUnlockPasswordDialog();
} else if (storeNameClickCount >= 3) {
// 当点击次数达到3次时提示用户还需几次点击
int remainingClicks = MAX_CLICK_COUNT - storeNameClickCount;
Toast.makeText(this, "再点击" + remainingClicks + "次返回主界面", Toast.LENGTH_SHORT).show();
Toast.makeText(this, "再点击" + remainingClicks + "次触发解锁", Toast.LENGTH_SHORT).show();
}
}
/**
* 初始化解锁密码弹窗
*/
private void initUnlockPasswordDialog() {
unlockPasswordDialog = new com.ouxuan.oxface.device.UnlockPasswordDialog(this);
unlockPasswordDialog.setListener(new com.ouxuan.oxface.device.UnlockPasswordDialog.UnlockDialogListener() {
@Override
public void onUnlockSuccess() {
LogManager.logInfo(TAG, "解锁成功,准备退出人脸识别");
unLockAndLeaveFaceDetect();
}
@Override
public void onDialogShow() {
// 暂停摄像头预览
pauseCameraWithTimeout();
LogManager.logInfo(TAG, "解锁密码弹窗显示,摄像头已暂停");
}
@Override
public void onDialogDismiss() {
// 恢复摄像头预览
resumeCamera();
LogManager.logInfo(TAG, "解锁密码弹窗关闭,摄像头已恢复");
}
});
LogManager.logInfo(TAG, "解锁密码弹窗初始化完成");
}
/**
* 显示解锁密码弹窗
*/
private void showUnlockPasswordDialog() {
if (unlockPasswordDialog != null && !unlockPasswordDialog.isShowing()) {
unlockPasswordDialog.show();
}
}
/**
* 返回主界面
* 解锁并退出人脸识别界面
*/
private void returnToMainActivity() {
LogManager.logInfo(TAG, "返回主界面");
private void unLockAndLeaveFaceDetect() {
LogManager.logInfo(TAG, "执行解锁退出操作");
try {
// 停止摄像头预览
if (CameraPreviewManager.getInstance() != null) {
CameraPreviewManager.getInstance().stopPreview();
}
// 返回登录界面
Intent intent = new Intent(this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
// 关闭当前界面
finish();
LogManager.logInfo(TAG, "已退出人脸识别界面,返回登录界面");
} catch (Exception e) {
LogManager.logError(TAG, "解锁退出操作失败", e);
Toast.makeText(this, "退出失败,请重试", Toast.LENGTH_SHORT).show();
}
}
/**

68
app/src/main/java/com/ouxuan/oxface/abgate/GateUnavailableDialog.java

@ -97,6 +97,21 @@ public class GateUnavailableDialog {
}
}
// 解锁密码弹窗相关变量
private int titleClickCount = 0;
private long lastTitleClickTime = 0;
private static final int TITLE_MAX_CLICK_COUNT = 6;
private static final long TITLE_CLICK_INTERVAL = 1000; // 1秒内点击有效
/**
* 解锁密码弹窗触发器接口
*/
public interface UnlockPasswordDialogTrigger {
/**
* 触发解锁密码弹窗
*/
void triggerUnlockPasswordDialog();
}
private GateUnavailableDialogListener dialogListener;
private boolean isShowing = false;
@ -108,6 +123,14 @@ public class GateUnavailableDialog {
this.dialogListener = listener;
}
/**
* 设置解锁密码弹窗触发器
* @param trigger 触发器
*/
public void setUnlockTrigger(UnlockPasswordDialogTrigger trigger) {
this.unlockTrigger = trigger;
}
public GateUnavailableDialog(Context context) {
this.context = context;
// 初始化线程池和处理器
@ -150,6 +173,14 @@ public class GateUnavailableDialog {
tvTitle.setGravity(Gravity.CENTER);
tvTitle.setPadding(0, 0, 0, 20);
// 为标题添加点击监听器用于触发解锁密码弹窗
tvTitle.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
handleTitleClick();
}
});
// 消息内容
tvMessage = new TextView(context);
tvMessage.setTextSize(16);
@ -211,6 +242,43 @@ public class GateUnavailableDialog {
}
/**
* 处理标题点击事件用于触发解锁密码弹窗
*/
private void handleTitleClick() {
long currentTime = System.currentTimeMillis();
// 检查点击间隔如果超过时间间隔则重置计数器
if (currentTime - lastTitleClickTime > TITLE_CLICK_INTERVAL) {
titleClickCount = 0;
}
titleClickCount++;
lastTitleClickTime = currentTime;
LogManager.logDebug(TAG, "门禁不可用弹窗标题被点击,当前计数: " + titleClickCount);
// 如果连续点击达到6次触发解锁密码弹窗
if (titleClickCount >= TITLE_MAX_CLICK_COUNT) {
LogManager.logInfo(TAG, "连续点击6次门禁不可用弹窗标题,触发解锁密码弹窗");
// 重置计数器
titleClickCount = 0;
// 触发解锁密码弹窗
if (unlockTrigger != null) {
unlockTrigger.triggerUnlockPasswordDialog();
} else {
LogManager.logWarning(TAG, "unlockTrigger为空,无法触发解锁密码弹窗");
}
} else if (titleClickCount >= 3) {
// 当点击次数达到3次时提示用户还需几次点击
int remainingClicks = TITLE_MAX_CLICK_COUNT - titleClickCount;
// 这里不显示Toast因为可能会干扰弹窗显示
LogManager.logInfo(TAG, "还需点击" + remainingClicks + "次触发解锁");
}
}
/**
* 更新门状态显示
* @param gateAOpen A门是否开启
* @param gateBOpen B门是否开启

412
app/src/main/java/com/ouxuan/oxface/device/UnlockPasswordDialog.java

@ -0,0 +1,412 @@
package com.ouxuan.oxface.device;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.text.InputType;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.ouxuan.oxface.data.LoginDataManager;
import com.ouxuan.oxface.utils.LogManager;
/**
* 解锁密码弹窗
* 用于特殊情况下退出人脸识别界面
*/
public class UnlockPasswordDialog {
private static final String TAG = "UnlockPasswordDialog";
private Context context;
private Dialog dialog;
private EditText etPassword;
private TextView tvErrorTip;
private Button btnConfirm, btnCancel;
private LoginDataManager loginDataManager;
private UnlockDialogListener listener;
/**
* 解锁弹窗监听器
*/
public interface UnlockDialogListener {
/**
* 解锁成功回调
*/
void onUnlockSuccess();
/**
* 弹窗显示时回调暂停摄像头
*/
void onDialogShow();
/**
* 弹窗关闭时回调恢复摄像头
*/
void onDialogDismiss();
}
public UnlockPasswordDialog(Context context) {
this.context = context;
this.loginDataManager = LoginDataManager.getInstance(context);
createDialog();
}
/**
* 设置监听器
* @param listener 监听器
*/
public void setListener(UnlockDialogListener listener) {
this.listener = listener;
}
/**
* 创建弹窗
*/
private void createDialog() {
dialog = new Dialog(context);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
// 创建主布局
LinearLayout mainLayout = new LinearLayout(context);
mainLayout.setOrientation(LinearLayout.VERTICAL);
mainLayout.setBackgroundColor(Color.parseColor("#CC000000")); // 半透明黑色背景
mainLayout.setGravity(Gravity.CENTER);
mainLayout.setPadding(40, 40, 40, 40);
// 内容容器
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);
// 标题
TextView tvTitle = new TextView(context);
tvTitle.setText("解锁验证");
tvTitle.setTextSize(20);
tvTitle.setTextColor(Color.parseColor("#333333"));
tvTitle.setGravity(Gravity.CENTER);
tvTitle.setPadding(0, 0, 0, 20);
// 提示文字
TextView tvHint = new TextView(context);
tvHint.setText("请输入登录密码以退出人脸识别");
tvHint.setTextSize(14);
tvHint.setTextColor(Color.parseColor("#666666"));
tvHint.setGravity(Gravity.CENTER);
tvHint.setPadding(0, 0, 0, 15);
// 密码输入框
etPassword = new EditText(context);
etPassword.setHint("请输入密码");
etPassword.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
etPassword.setTextSize(16);
etPassword.setTextColor(Color.parseColor("#333333"));
etPassword.setHintTextColor(Color.parseColor("#999999"));
etPassword.setPadding(20, 15, 20, 15);
etPassword.setBackgroundColor(Color.parseColor("#F5F5F5"));
// 设置输入框圆角
android.graphics.drawable.GradientDrawable inputDrawable = new android.graphics.drawable.GradientDrawable();
inputDrawable.setColor(Color.parseColor("#F5F5F5"));
inputDrawable.setCornerRadius(10);
inputDrawable.setStroke(2, Color.parseColor("#E0E0E0"));
etPassword.setBackground(inputDrawable);
LinearLayout.LayoutParams inputParams = new LinearLayout.LayoutParams(
(int)(280 * context.getResources().getDisplayMetrics().density),
LinearLayout.LayoutParams.WRAP_CONTENT
);
inputParams.setMargins(0, 0, 0, 10);
etPassword.setLayoutParams(inputParams);
// 错误提示
tvErrorTip = new TextView(context);
tvErrorTip.setTextSize(12);
tvErrorTip.setTextColor(Color.parseColor("#FF4444"));
tvErrorTip.setGravity(Gravity.CENTER);
tvErrorTip.setVisibility(View.GONE);
tvErrorTip.setPadding(0, 0, 0, 15);
// 按钮容器
LinearLayout buttonLayout = new LinearLayout(context);
buttonLayout.setOrientation(LinearLayout.HORIZONTAL);
buttonLayout.setGravity(Gravity.CENTER);
buttonLayout.setPadding(0, 20, 0, 0);
// 取消按钮
btnCancel = new Button(context);
btnCancel.setText("取消");
btnCancel.setTextSize(16);
btnCancel.setTextColor(Color.parseColor("#666666"));
btnCancel.setBackgroundColor(Color.parseColor("#F0F0F0"));
// 设置取消按钮圆角
android.graphics.drawable.GradientDrawable cancelDrawable = new android.graphics.drawable.GradientDrawable();
cancelDrawable.setColor(Color.parseColor("#F0F0F0"));
cancelDrawable.setCornerRadius(8);
btnCancel.setBackground(cancelDrawable);
LinearLayout.LayoutParams cancelParams = new LinearLayout.LayoutParams(
(int)(100 * context.getResources().getDisplayMetrics().density),
(int)(45 * context.getResources().getDisplayMetrics().density)
);
cancelParams.setMargins(0, 0, 20, 0);
btnCancel.setLayoutParams(cancelParams);
// 确认按钮
btnConfirm = new Button(context);
btnConfirm.setText("确认");
btnConfirm.setTextSize(16);
btnConfirm.setTextColor(Color.WHITE);
btnConfirm.setBackgroundColor(Color.parseColor("#007AFF"));
// 设置确认按钮圆角
android.graphics.drawable.GradientDrawable confirmDrawable = new android.graphics.drawable.GradientDrawable();
confirmDrawable.setColor(Color.parseColor("#007AFF"));
confirmDrawable.setCornerRadius(8);
btnConfirm.setBackground(confirmDrawable);
LinearLayout.LayoutParams confirmParams = new LinearLayout.LayoutParams(
(int)(100 * context.getResources().getDisplayMetrics().density),
(int)(45 * context.getResources().getDisplayMetrics().density)
);
btnConfirm.setLayoutParams(confirmParams);
// 添加按钮到容器
buttonLayout.addView(btnCancel);
buttonLayout.addView(btnConfirm);
// 添加所有视图到内容容器
contentLayout.addView(tvTitle);
contentLayout.addView(tvHint);
contentLayout.addView(etPassword);
contentLayout.addView(tvErrorTip);
contentLayout.addView(buttonLayout);
// 设置内容布局参数
LinearLayout.LayoutParams contentParams = new LinearLayout.LayoutParams(
(int)(320 * 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.setWindowAnimations(0);
}
// 设置不可取消
dialog.setCancelable(false);
dialog.setCanceledOnTouchOutside(false);
// 设置监听器
setupListeners();
}
/**
* 设置监听器
*/
private void setupListeners() {
// 取消按钮
btnCancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LogManager.logInfo(TAG, "用户取消解锁");
dismiss();
}
});
// 确认按钮
btnConfirm.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
handleConfirm();
}
});
// 密码输入框回车监听
etPassword.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, android.view.KeyEvent event) {
if (actionId == android.view.inputmethod.EditorInfo.IME_ACTION_DONE ||
actionId == android.view.inputmethod.EditorInfo.IME_ACTION_GO ||
(event != null && event.getKeyCode() == android.view.KeyEvent.KEYCODE_ENTER)) {
handleConfirm();
return true;
}
return false;
}
});
}
/**
* 处理确认按钮点击
*/
private void handleConfirm() {
String inputPassword = etPassword.getText().toString().trim();
if (inputPassword.isEmpty()) {
showErrorTip("请输入密码");
return;
}
// 获取保存的登录密码
String savedPassword = loginDataManager.getPassword();
if (savedPassword == null || savedPassword.isEmpty()) {
LogManager.logError(TAG, "未找到保存的登录密码");
showErrorTip("系统错误:未找到登录密码");
return;
}
// 验证密码
if (inputPassword.equals(savedPassword)) {
LogManager.logInfo(TAG, "密码验证成功,准备解锁退出");
hideErrorTip();
// 通知解锁成功
if (listener != null) {
listener.onUnlockSuccess();
}
dismiss();
} else {
LogManager.logWarning(TAG, "密码验证失败");
showErrorTip("密码错误,请重新输入");
// 清空输入框
etPassword.setText("");
etPassword.requestFocus();
}
}
/**
* 显示错误提示
* @param message 错误信息
*/
private void showErrorTip(String message) {
tvErrorTip.setText(message);
tvErrorTip.setVisibility(View.VISIBLE);
}
/**
* 隐藏错误提示
*/
private void hideErrorTip() {
tvErrorTip.setVisibility(View.GONE);
}
/**
* 显示弹窗
*/
public void show() {
try {
if (dialog != null && !dialog.isShowing()) {
// 清空之前的输入和错误提示
etPassword.setText("");
hideErrorTip();
// 显示弹窗
dialog.show();
// 请求输入框焦点
etPassword.requestFocus();
// 显示软键盘
android.view.inputmethod.InputMethodManager imm =
(android.view.inputmethod.InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null) {
imm.showSoftInput(etPassword, android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT);
}
LogManager.logInfo(TAG, "解锁密码弹窗已显示");
// 通知监听器弹窗显示
if (listener != null) {
listener.onDialogShow();
}
}
} catch (Exception e) {
LogManager.logError(TAG, "显示解锁密码弹窗失败", e);
}
}
/**
* 关闭弹窗
*/
public void dismiss() {
try {
if (dialog != null && dialog.isShowing()) {
// 隐藏软键盘
android.view.inputmethod.InputMethodManager imm =
(android.view.inputmethod.InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null) {
imm.hideSoftInputFromWindow(etPassword.getWindowToken(), 0);
}
dialog.dismiss();
LogManager.logInfo(TAG, "解锁密码弹窗已关闭");
// 通知监听器弹窗关闭
if (listener != null) {
listener.onDialogDismiss();
}
}
} catch (Exception e) {
LogManager.logError(TAG, "关闭解锁密码弹窗失败", e);
}
}
/**
* 检查弹窗是否正在显示
* @return true表示正在显示
*/
public boolean isShowing() {
return dialog != null && dialog.isShowing();
}
/**
* 释放资源
*/
public void release() {
dismiss();
if (dialog != null) {
dialog = null;
}
context = null;
listener = null;
loginDataManager = null;
LogManager.logInfo(TAG, "解锁密码弹窗资源已释放");
}
}

208
解锁密码弹窗功能实现说明.md

@ -0,0 +1,208 @@
# 解锁密码弹窗功能实现说明
## 功能概述
本次实现了解锁密码弹窗功能,为特殊情况下退出人脸识别界面提供了一个安全的出口。该功能完全符合用户需求,提供了美观简洁的界面和安全的密码验证机制。
## 实现的功能
### 1. 解锁密码弹窗类 (`UnlockPasswordDialog.java`)
**位置**: `app/src/main/java/com/ouxuan/oxface/device/UnlockPasswordDialog.java`
**主要特性**:
- 美观的弹窗界面设计,符合主题色
- 安全的密码输入(显示为密码形式)
- 实时密码验证(与本地存储的登录密码对比)
- 错误提示功能(密码错误时显示红色提示)
- 摄像头控制集成(弹窗显示时暂停摄像头,关闭时恢复)
- 软键盘自动弹出和收起
- 完整的生命周期管理
**核心方法**:
- `show()`: 显示弹窗
- `dismiss()`: 关闭弹窗
- `handleConfirm()`: 处理密码验证
- `UnlockDialogListener`: 回调接口
### 2. 触发机制
#### 2.1 店铺名称连续点击触发
**位置**: `OXFaceOnlineActivity.java` 中的 `handleStoreNameClick()` 方法
**触发条件**: 连续6次点击视频流界面左上角的店铺名称按钮(`tv_store_name`)
- 点击间隔限制:1秒内点击有效
- 点击计数:达到6次时触发
- 用户提示:3次点击后提示剩余次数
#### 2.2 门禁不可用弹窗标题点击触发
**位置**: `GateUnavailableDialog.java` 中的 `handleTitleClick()` 方法
**触发条件**: 连续6次点击门禁不可用弹窗的标题(`tvTitle`)
- 点击间隔限制:1秒内点击有效
- 点击计数:达到6次时触发
- 日志记录:详细记录点击过程
### 3. 密码验证机制
**验证逻辑**:
1. 获取用户输入的密码
2. 从 `LoginDataManager` 获取本地存储的登录密码
3. 进行字符串比较验证
4. 验证成功:调用 `unLockAndLeaveFaceDetect()` 方法
5. 验证失败:显示红色错误提示,清空输入框
**安全特性**:
- 密码以密文形式显示
- 错误时提供明确的用户反馈
- 支持键盘回车确认
### 4. 解锁退出功能 (`unLockAndLeaveFaceDetect()`)
**执行流程**:
1. 停止摄像头预览
2. 创建返回登录界面的Intent
3. 设置Intent标志(清除任务栈)
4. 启动MainActivity(登录界面)
5. 结束当前人脸识别界面
6. 记录详细的操作日志
### 5. 摄像头控制集成
**自动控制**:
- 弹窗显示时:自动暂停摄像头预览
- 弹窗关闭时:自动恢复摄像头预览
- 解锁成功时:永久停止摄像头(准备退出)
## 界面设计特点
### 1. 视觉设计
- **主题色配色**: 蓝色确认按钮 (#007AFF),灰色取消按钮
- **圆角设计**: 弹窗、按钮、输入框均采用圆角设计
- **半透明背景**: 黑色半透明背景 (#CC000000)
- **响应式布局**: 根据屏幕密度自适应大小
### 2. 交互设计
- **焦点管理**: 弹窗显示时自动聚焦到密码输入框
- **键盘控制**: 自动弹出软键盘,支持回车确认
- **错误反馈**: 密码错误时显示红色提示文字
- **防误操作**: 弹窗不可通过点击外部区域关闭
### 3. 尺寸规格
- **弹窗宽度**: 320dp
- **输入框宽度**: 280dp
- **按钮尺寸**: 100dp × 45dp
- **文字大小**: 标题20sp,正文16sp,提示14sp
## 技术实现细节
### 1. 类结构设计
```java
// 主要类和接口
- UnlockPasswordDialog: 主弹窗类
- UnlockDialogListener: 回调接口
- UnlockPasswordDialogTrigger: 触发器接口
```
### 2. 生命周期管理
- 创建时初始化所有UI组件
- 显示时设置焦点和键盘
- 关闭时清理资源和隐藏键盘
- 销毁时释放所有引用
### 3. 错误处理
- 完善的异常捕获和日志记录
- 用户友好的错误提示
- 安全的资源释放机制
## 配置和依赖
### 1. 依赖的现有组件
- `LoginDataManager`: 获取存储的登录密码
- `LogManager`: 日志记录
- `CameraPreviewManager`: 摄像头控制
### 2. 权限要求
- 无需额外权限
- 使用现有的摄像头权限
## 使用方法
### 1. 普通用户使用
1. 在人脸识别界面,连续快速点击左上角店铺名称6次
2. 或者等待门禁不可用弹窗出现时,连续快速点击弹窗标题6次
3. 在弹出的解锁密码弹窗中输入登录密码
4. 点击确认或按回车键
5. 密码正确后自动返回登录界面
### 2. 开发者调试
- 查看日志中的 "解锁" 相关信息
- 验证点击计数和密码验证逻辑
- 检查摄像头暂停/恢复状态
## 安全性考虑
### 1. 访问控制
- 隐蔽的触发方式(连续6次点击)
- 密码验证机制
- 无明显的UI提示(防止误操作)
### 2. 密码安全
- 使用现有的登录密码验证
- 密文显示输入内容
- 错误时不显示具体的密码信息
### 3. 日志安全
- 不记录用户输入的密码
- 只记录验证成功/失败的结果
- 详细记录操作流程便于调试
## 测试建议
### 1. 功能测试
- 测试两种触发方式
- 测试正确和错误密码的处理
- 测试摄像头暂停/恢复功能
- 测试解锁后的界面跳转
### 2. 界面测试
- 不同屏幕尺寸的适配
- 横竖屏切换(如支持)
- 软键盘弹出和收起
- 错误提示的显示和隐藏
### 3. 异常测试
- 密码为空的处理
- 网络异常时的处理
- 内存不足时的处理
- 快速重复操作的处理
## 维护说明
### 1. 常见问题排查
- 检查 `LoginDataManager` 中是否有保存的密码
- 确认点击计数逻辑是否正确
- 验证摄像头控制广播是否正常
### 2. 代码修改建议
- 若需修改触发次数,修改 `MAX_CLICK_COUNT``TITLE_MAX_CLICK_COUNT` 常量
- 若需修改UI样式,在 `createDialog()` 方法中调整
- 若需修改密码验证逻辑,在 `handleConfirm()` 方法中调整
### 3. 日志查看
- 搜索 "解锁" 关键字查看相关日志
- 搜索 "点击" 关键字查看触发日志
- 搜索 "密码" 关键字查看验证日志(不包含实际密码)
## 总结
本实现完全满足了用户的需求:
✅ 两种隐蔽的触发方式(店铺名称和弹窗标题)
✅ 美观简洁的弹窗界面
✅ 安全的密码验证机制
✅ 完整的摄像头控制集成
✅ 可靠的解锁退出功能
✅ 符合主题色的界面设计
✅ 完善的错误处理和日志记录
该功能为特殊情况下的程序退出提供了安全可靠的解决方案,既保证了系统的安全性,又提供了良好的用户体验。
Loading…
Cancel
Save