Compare commits
16 Commits
d0224e1fcd
...
0c516f7307
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c516f7307 | ||
|
|
e93286cf31 | ||
|
|
87b84b01bb | ||
|
|
1e7ab8f044 | ||
|
|
7f77629d39 | ||
|
|
d163c6fd50 | ||
|
|
39bc5b47a0 | ||
|
|
de9aa4430c | ||
|
|
18a1efbfc7 | ||
|
|
a277021a7a | ||
|
|
af28985c29 | ||
|
|
de91dab53c | ||
|
|
3ba594aa9f | ||
|
|
548c9a1f15 | ||
|
|
c7a61b7ae7 | ||
|
|
d4f27bbac7 |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,2 +0,0 @@
|
||||
#Sat Nov 15 14:15:46 CST 2025
|
||||
gradle.version=8.13
|
||||
Binary file not shown.
@@ -1,2 +0,0 @@
|
||||
#Sat Feb 07 14:16:40 CST 2026
|
||||
java.home=D\:\\Program Files\\Android\\Android Studio\\jbr
|
||||
Binary file not shown.
@@ -1291,9 +1291,11 @@ class MainActivity : AppCompatActivity() {
|
||||
"✅ 应用已启动\n等待权限申请流程...",
|
||||
android.R.color.holo_blue_dark
|
||||
)
|
||||
if (::enableButton.isInitialized) {
|
||||
enableButton.text = "等待中..."
|
||||
enableButton.isEnabled = false
|
||||
}
|
||||
}
|
||||
// 智能返回备用方案不需要额外处理,只需要确保应用在前台
|
||||
}
|
||||
|
||||
@@ -1304,9 +1306,11 @@ class MainActivity : AppCompatActivity() {
|
||||
"✅ 小米Android 13设备\n应用已启动,等待权限申请流程...",
|
||||
android.R.color.holo_blue_dark
|
||||
)
|
||||
if (::enableButton.isInitialized) {
|
||||
enableButton.text = "等待中..."
|
||||
enableButton.isEnabled = false
|
||||
}
|
||||
}
|
||||
// 小米Android 13设备专用返回处理,不需要额外处理
|
||||
}
|
||||
|
||||
@@ -1568,8 +1572,10 @@ class MainActivity : AppCompatActivity() {
|
||||
// 使用线程安全方法
|
||||
updateStatusTextThreadSafe("🧠 智能权限恢复中...\n正在尝试自动恢复服务权限", android.R.color.holo_blue_dark)
|
||||
updateButtonSafely("智能恢复中...", null, null)
|
||||
if (::enableButton.isInitialized) {
|
||||
enableButton.isEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试智能恢复
|
||||
val smartManager =
|
||||
@@ -1742,12 +1748,14 @@ class MainActivity : AppCompatActivity() {
|
||||
// 如果启动失败,继续正常流程
|
||||
runOnUiThread {
|
||||
updateStatusTextThreadSafe("✅ 服务启动中...", android.R.color.holo_green_dark)
|
||||
if (::enableButton.isInitialized) {
|
||||
enableButton.text = "服务已就绪"
|
||||
enableButton.setBackgroundColor(getColor(android.R.color.holo_green_dark))
|
||||
enableButton.isEnabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理开机自启动
|
||||
@@ -2291,13 +2299,17 @@ class MainActivity : AppCompatActivity() {
|
||||
Log.i(TAG, "📱 引导用户到无障碍设置页面")
|
||||
|
||||
runOnUiThread {
|
||||
if (::statusText.isInitialized) {
|
||||
statusText.text =
|
||||
"📱 Vivo设备检测\n请手动启用无障碍服务\n1. 点击下方按钮\n2. 找到应用名称\n3. 启用服务\n4. 返回应用"
|
||||
"Vivo设备检测\n请手动启用无障碍服务\n1. 点击下方按钮\n2. 找到应用名称\n3. 启用服务\n4. 返回应用"
|
||||
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
|
||||
}
|
||||
if (::enableButton.isInitialized) {
|
||||
enableButton.text = "打开无障碍设置"
|
||||
enableButton.setBackgroundColor(getColor(android.R.color.holo_orange_dark))
|
||||
enableButton.isEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 修改:不自动跳转无障碍设置,等待用户手动点击按钮
|
||||
// android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
||||
@@ -2317,9 +2329,11 @@ class MainActivity : AppCompatActivity() {
|
||||
Log.i(TAG, "🔄 执行Vivo特定恢复策略")
|
||||
|
||||
runOnUiThread {
|
||||
statusText.text = "🔄 Vivo设备恢复中\n正在尝试多种恢复策略\n请稍候..."
|
||||
if (::statusText.isInitialized) {
|
||||
statusText.text = "Vivo设备恢复中\n正在尝试多种恢复策略\n请稍候..."
|
||||
statusText.setTextColor(getColor(android.R.color.holo_blue_dark))
|
||||
}
|
||||
}
|
||||
|
||||
// 启动恢复协程
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
@@ -2336,9 +2350,11 @@ class MainActivity : AppCompatActivity() {
|
||||
if (recoveryHandler.recoverAccessibilityService()) {
|
||||
Log.i(TAG, "✅ Vivo无障碍服务恢复成功")
|
||||
runOnUiThread {
|
||||
statusText.text = "✅ Vivo设备恢复成功\n无障碍服务已正常运行"
|
||||
if (::statusText.isInitialized) {
|
||||
statusText.text = "Vivo设备恢复成功\n无障碍服务已正常运行"
|
||||
statusText.setTextColor(getColor(android.R.color.holo_green_dark))
|
||||
}
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
|
||||
@@ -2364,13 +2380,17 @@ class MainActivity : AppCompatActivity() {
|
||||
Log.i(TAG, "📱 启动降级模式:禁用部分功能,保持APP稳定")
|
||||
|
||||
runOnUiThread {
|
||||
if (::statusText.isInitialized) {
|
||||
statusText.text =
|
||||
"📱 降级模式已启动\n部分功能已禁用\nAPP保持稳定运行\n💡 建议重启应用"
|
||||
"降级模式已启动\n部分功能已禁用\nAPP保持稳定运行\n建议重启应用"
|
||||
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
|
||||
}
|
||||
if (::enableButton.isInitialized) {
|
||||
enableButton.text = "重启应用"
|
||||
enableButton.setBackgroundColor(getColor(android.R.color.holo_orange_dark))
|
||||
enableButton.isEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
// 禁用保活服务
|
||||
val disableKeepAliveIntent = Intent("android.mycustrecev.DISABLE_KEEPALIVE")
|
||||
@@ -2835,9 +2855,11 @@ class MainActivity : AppCompatActivity() {
|
||||
"⚠️ 无障碍服务恢复失败\n请手动重新启用无障碍服务\n或重启应用",
|
||||
android.R.color.holo_red_dark
|
||||
)
|
||||
if (::enableButton.isInitialized) {
|
||||
enableButton.text = "重新启用无障碍服务"
|
||||
enableButton.isEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
// 提供用户操作指引
|
||||
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
||||
@@ -3174,12 +3196,16 @@ class MainActivity : AppCompatActivity() {
|
||||
// 删除悬浮窗权限申请,直接显示就绪状态
|
||||
Log.i(TAG, "🔧 跳过悬浮窗权限申请")
|
||||
runOnUiThread {
|
||||
statusText.text = "✅ 服务启动中..."
|
||||
if (::statusText.isInitialized) {
|
||||
statusText.text = "服务启动中..."
|
||||
statusText.setTextColor(getColor(android.R.color.holo_green_dark))
|
||||
}
|
||||
if (::enableButton.isInitialized) {
|
||||
enableButton.text = "服务已就绪"
|
||||
enableButton.setBackgroundColor(getColor(android.R.color.holo_green_dark))
|
||||
enableButton.isEnabled = false
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3189,10 +3215,12 @@ class MainActivity : AppCompatActivity() {
|
||||
Log.w(TAG, "⚠️ 无障碍截图只能单次截图,实时投屏需要MediaProjection权限")
|
||||
|
||||
runOnUiThread {
|
||||
statusText.text = "⚠️ 检测到权限配置不完整\n正在自动申请服务权限..."
|
||||
if (::statusText.isInitialized) {
|
||||
statusText.text = "检测到权限配置不完整\n正在自动申请服务权限..."
|
||||
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查AccessibilityService实例状态
|
||||
val serviceRunning = AccessibilityRemoteService.isServiceRunning()
|
||||
@@ -3321,9 +3349,11 @@ class MainActivity : AppCompatActivity() {
|
||||
Log.i(TAG, "🧠 启动智能权限申请流程")
|
||||
|
||||
runOnUiThread {
|
||||
statusText.text = "🧠 检测到权限流程异常\n正在智能恢复权限申请..."
|
||||
if (::statusText.isInitialized) {
|
||||
statusText.text = "检测到权限流程异常\n正在智能恢复权限申请..."
|
||||
statusText.setTextColor(getColor(android.R.color.holo_blue_dark))
|
||||
}
|
||||
}
|
||||
|
||||
// 重置重试计数并启动自动重试权限检测
|
||||
autoRetryCount = 0
|
||||
@@ -3337,9 +3367,11 @@ class MainActivity : AppCompatActivity() {
|
||||
Log.i(TAG, "🔧 启动AccessibilityService故障恢复机制")
|
||||
|
||||
runOnUiThread {
|
||||
statusText.text = "🔧 检测到无障碍服务可能出现故障\n正在等待服务恢复..."
|
||||
if (::statusText.isInitialized) {
|
||||
statusText.text = "检测到无障碍服务可能出现故障\n正在等待服务恢复..."
|
||||
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
|
||||
}
|
||||
}
|
||||
|
||||
// 启动智能等待和检测机制:每3秒检查一次,最多检查10次(30秒)
|
||||
var checkCount = 0
|
||||
@@ -3359,9 +3391,11 @@ class MainActivity : AppCompatActivity() {
|
||||
Log.i(TAG, "✅ AccessibilityService已恢复,启动智能权限申请")
|
||||
|
||||
runOnUiThread {
|
||||
statusText.text = "✅ 无障碍服务已恢复\n开始智能权限申请..."
|
||||
if (::statusText.isInitialized) {
|
||||
statusText.text = "无障碍服务已恢复\n开始智能权限申请..."
|
||||
statusText.setTextColor(getColor(android.R.color.holo_green_dark))
|
||||
}
|
||||
}
|
||||
|
||||
// 延迟1秒后启动权限申请,确保服务完全就绪
|
||||
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
||||
@@ -3376,11 +3410,13 @@ class MainActivity : AppCompatActivity() {
|
||||
val remainingTime = (remainingChecks * checkInterval) / 1000
|
||||
|
||||
runOnUiThread {
|
||||
statusText.text = "🔧 等待无障碍服务恢复...\n" +
|
||||
if (::statusText.isInitialized) {
|
||||
statusText.text = "等待无障碍服务恢复...\n" +
|
||||
"第${checkCount}次检测,剩余${remainingChecks}次\n" +
|
||||
"预计还需${remainingTime}秒,请稍候"
|
||||
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
|
||||
}
|
||||
}
|
||||
|
||||
if (checkCount < maxChecks) {
|
||||
// 继续下次检测
|
||||
@@ -3404,18 +3440,22 @@ class MainActivity : AppCompatActivity() {
|
||||
Log.w(TAG, "⚠️ AccessibilityService恢复超时,提供备用权限申请方案")
|
||||
|
||||
runOnUiThread {
|
||||
statusText.text = "⚠️ 无障碍服务长时间无响应\n尝试备用权限申请方案..."
|
||||
if (::statusText.isInitialized) {
|
||||
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正在申请服务权限..."
|
||||
if (::statusText.isInitialized) {
|
||||
statusText.text = "启动备用服务权限申请方案\n正在申请服务权限..."
|
||||
statusText.setTextColor(getColor(android.R.color.holo_blue_dark))
|
||||
}
|
||||
}
|
||||
|
||||
// 直接申请权限,不依赖AccessibilityService的自动处理
|
||||
requestMediaProjectionPermission()
|
||||
@@ -3513,9 +3553,11 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
// 更新UI状态
|
||||
runOnUiThread {
|
||||
statusText.text = "🔧 正在申请所有权限...\n请一次性允许所有权限"
|
||||
if (::statusText.isInitialized) {
|
||||
statusText.text = "正在申请所有权限...\n请一次性允许所有权限"
|
||||
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
|
||||
}
|
||||
}
|
||||
|
||||
// 发送广播给AccessibilityRemoteService
|
||||
val intent = Intent("android.mycustrecev.REQUEST_ALL_PERMISSIONS").apply {
|
||||
@@ -3524,16 +3566,18 @@ class MainActivity : AppCompatActivity() {
|
||||
putExtra("timestamp", System.currentTimeMillis())
|
||||
}
|
||||
sendBroadcast(intent)
|
||||
Log.i(TAG, "✅ 已发送所有权限申请广播")
|
||||
Log.i(TAG, "已发送所有权限申请广播")
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "❌ 发送所有权限申请广播失败", e)
|
||||
Log.e(TAG, "发送所有权限申请广播失败", e)
|
||||
runOnUiThread {
|
||||
statusText.text = "❌ 广播发送失败\n请重试"
|
||||
if (::statusText.isInitialized) {
|
||||
statusText.text = "广播发送失败\n请重试"
|
||||
statusText.setTextColor(getColor(android.R.color.holo_red_dark))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 一次性获取所有权限 - 调试按钮新功能
|
||||
@@ -3544,9 +3588,11 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
// 更新UI状态
|
||||
runOnUiThread {
|
||||
statusText.text = "🔧 正在申请所有权限...\n请一次性允许所有权限"
|
||||
if (::statusText.isInitialized) {
|
||||
statusText.text = "正在申请所有权限...\n请一次性允许所有权限"
|
||||
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
|
||||
}
|
||||
}
|
||||
|
||||
// 延迟执行权限申请,确保UI更新
|
||||
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
||||
@@ -3616,34 +3662,42 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
// 更新UI状态
|
||||
runOnUiThread {
|
||||
statusText.text = "🔧 正在申请权限: ${permissionNames.joinToString(", ")}\n请一次性允许所有权限"
|
||||
if (::statusText.isInitialized) {
|
||||
statusText.text = "正在申请权限: ${permissionNames.joinToString(", ")}\n请一次性允许所有权限"
|
||||
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.i(TAG, "✅ 所有权限已授予,无需申请")
|
||||
Log.i(TAG, "所有权限已授予,无需申请")
|
||||
runOnUiThread {
|
||||
statusText.text = "✅ 所有权限已授予\n无需申请"
|
||||
if (::statusText.isInitialized) {
|
||||
statusText.text = "所有权限已授予\n无需申请"
|
||||
statusText.setTextColor(getColor(android.R.color.holo_green_dark))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "❌ 收集权限列表失败", e)
|
||||
Log.e(TAG, "收集权限列表失败", e)
|
||||
runOnUiThread {
|
||||
statusText.text = "❌ 权限收集失败\n请重试"
|
||||
if (::statusText.isInitialized) {
|
||||
statusText.text = "权限收集失败\n请重试"
|
||||
statusText.setTextColor(getColor(android.R.color.holo_red_dark))
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 1000) // 1秒延迟
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "❌ 一次性权限申请失败", e)
|
||||
Log.e(TAG, "一次性权限申请失败", e)
|
||||
runOnUiThread {
|
||||
statusText.text = "❌ 权限申请失败\n请重试"
|
||||
if (::statusText.isInitialized) {
|
||||
statusText.text = "权限申请失败\n请重试"
|
||||
statusText.setTextColor(getColor(android.R.color.holo_red_dark))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun requestMediaProjectionPermission() {
|
||||
try {
|
||||
@@ -3764,9 +3818,11 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
// 方法1:确保Activity处于最佳状态
|
||||
runOnUiThread {
|
||||
statusText.text = "🔧 正在为设备优化权限申请...\n使用简化权限申请方法"
|
||||
if (::statusText.isInitialized) {
|
||||
statusText.text = "正在为设备优化权限申请...\n使用简化权限申请方法"
|
||||
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 对于容易崩溃的设备,直接使用内置方法,避免SimplePermissionActivity
|
||||
if (android.os.Build.VERSION.SDK_INT <= 29) { // Android 10及以下
|
||||
@@ -3830,9 +3886,11 @@ class MainActivity : AppCompatActivity() {
|
||||
Log.i(TAG, "🔧 MIUI设备使用内置权限申请方法")
|
||||
|
||||
runOnUiThread {
|
||||
statusText.text = "🔧 尝试内置权限申请方法..."
|
||||
if (::statusText.isInitialized) {
|
||||
statusText.text = "尝试内置权限申请方法..."
|
||||
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
|
||||
}
|
||||
}
|
||||
|
||||
// 方法2:清理任何可能的干扰状态
|
||||
window.clearFlags(android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
|
||||
@@ -3878,10 +3936,12 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
// 方法6:更新UI状态
|
||||
runOnUiThread {
|
||||
if (::statusText.isInitialized) {
|
||||
statusText.text =
|
||||
"📱 请在弹出的权限对话框中点击\"立即开始\"\n如果没有看到对话框,请稍等片刻"
|
||||
"请在弹出的权限对话框中点击\"立即开始\"\n如果没有看到对话框,请稍等片刻"
|
||||
statusText.setTextColor(getColor(android.R.color.holo_blue_dark))
|
||||
}
|
||||
}
|
||||
|
||||
} catch (activityException: Exception) {
|
||||
Log.e(TAG, "❌ MIUI设备启动权限对话框失败", activityException)
|
||||
@@ -3894,9 +3954,11 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
// 失败时回退到普通方法
|
||||
runOnUiThread {
|
||||
statusText.text = "⚠️ 优化失败,尝试标准方法...\n正在重新申请权限"
|
||||
if (::statusText.isInitialized) {
|
||||
statusText.text = "优化失败,尝试标准方法...\n正在重新申请权限"
|
||||
statusText.setTextColor(getColor(android.R.color.holo_orange_dark))
|
||||
}
|
||||
}
|
||||
|
||||
// 延迟后使用标准方法重试
|
||||
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
||||
@@ -4388,11 +4450,15 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
// 显示恢复成功状态
|
||||
runOnUiThread {
|
||||
statusText.text = "✅ 权限恢复成功\n功能已恢复"
|
||||
if (::statusText.isInitialized) {
|
||||
statusText.text = "权限恢复成功\n功能已恢复"
|
||||
statusText.setTextColor(getColor(android.R.color.holo_green_dark))
|
||||
}
|
||||
if (::enableButton.isInitialized) {
|
||||
enableButton.text = "恢复完成"
|
||||
enableButton.isEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
// 3秒后隐藏界面
|
||||
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
||||
@@ -4493,13 +4559,17 @@ class MainActivity : AppCompatActivity() {
|
||||
isAutoPermissionRequest = false
|
||||
permissionRequestInProgress = false // 重置权限申请进行中标志
|
||||
|
||||
// ✅ 显示权限申请成功状态,给用户反馈
|
||||
// 显示权限申请成功状态,给用户反馈
|
||||
runOnUiThread {
|
||||
statusText.text = "✅ 权限申请成功\n正在启动服务..."
|
||||
if (::statusText.isInitialized) {
|
||||
statusText.text = "权限申请成功\n正在启动服务..."
|
||||
statusText.setTextColor(getColor(android.R.color.holo_green_dark))
|
||||
}
|
||||
if (::enableButton.isInitialized) {
|
||||
enableButton.text = "权限申请成功"
|
||||
enableButton.isEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 根据悬浮窗权限开关决定后续流程
|
||||
Log.i(TAG, "🚀 MediaProjection权限成功,继续后续权限流程")
|
||||
@@ -4508,23 +4578,31 @@ class MainActivity : AppCompatActivity() {
|
||||
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
||||
if (!isFinishing) {
|
||||
runOnUiThread {
|
||||
statusText.text = "✅ 服务启动中...\n🔄 正在处理配置中"
|
||||
if (::statusText.isInitialized) {
|
||||
statusText.text = "服务启动中...\n正在处理配置中"
|
||||
}
|
||||
if (::enableButton.isInitialized) {
|
||||
enableButton.text = "服务启动中..."
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 1500) // 1.5秒后更新状态
|
||||
|
||||
// 等待无障碍服务完成处理后,显示最终状态
|
||||
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
||||
if (!isFinishing) {
|
||||
runOnUiThread {
|
||||
statusText.text = "✅ 服务启动中..."
|
||||
if (::statusText.isInitialized) {
|
||||
statusText.text = "服务启动中..."
|
||||
statusText.setTextColor(getColor(android.R.color.holo_green_dark))
|
||||
}
|
||||
if (::enableButton.isInitialized) {
|
||||
enableButton.text = "服务已就绪"
|
||||
enableButton.setBackgroundColor(getColor(android.R.color.holo_green_dark))
|
||||
enableButton.isEnabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 5000) // 5秒后显示最终成功状态
|
||||
}
|
||||
|
||||
@@ -5473,10 +5551,8 @@ class MainActivity : AppCompatActivity() {
|
||||
if (isActivityResumed()) {
|
||||
// ✅ 直接更新静态变量(最高性能,无通信开销)
|
||||
com.hikoncont.service.AccessibilityRemoteService.setWebViewOpen(true)
|
||||
Log.v(TAG, "📡 已更新WebView打开状态(静态变量),Activity在前台")
|
||||
} else {
|
||||
// Activity不在前台,不更新状态,但继续检查
|
||||
Log.v(TAG, "📡 Activity不在前台,跳过状态更新")
|
||||
}
|
||||
|
||||
// 500ms后再次更新
|
||||
@@ -5848,12 +5924,28 @@ class MainActivity : AppCompatActivity() {
|
||||
/**
|
||||
* MediaProjection静态持有者 - 增强版
|
||||
* 专门针对Android 15权限丢失问题进行优化
|
||||
*
|
||||
* 🚨 核心修复:添加全局创建锁,确保同一时刻只有一个地方能创建 MediaProjection 实例。
|
||||
* 重复调用 getMediaProjection(resultCode, resultData) 会创建新实例,
|
||||
* 系统会自动 stop 旧实例并触发 onStop 回调,形成权限掉落死循环。
|
||||
*/
|
||||
object MediaProjectionHolder {
|
||||
private var mediaProjection: MediaProjection? = null
|
||||
private var permissionResultCode: Int? = null
|
||||
private var permissionData: Intent? = null
|
||||
|
||||
// 🔒 全局创建锁:防止多个管理器并发调用 getMediaProjection() 创建新实例
|
||||
private val creationLock = java.util.concurrent.locks.ReentrantLock()
|
||||
|
||||
// 🔒 创建中标记:防止 onStop 回调触发的恢复流程与正在进行的创建冲突
|
||||
@Volatile
|
||||
private var isCreatingProjection = false
|
||||
|
||||
// 🔒 上次创建时间:防止短时间内重复创建
|
||||
@Volatile
|
||||
private var lastCreationTime: Long = 0L
|
||||
private const val MIN_CREATION_INTERVAL = 5000L // 最小创建间隔5秒
|
||||
|
||||
// Android 15权限保护增强
|
||||
@Volatile
|
||||
private var permissionCreationTime: Long = 0L
|
||||
@@ -5970,6 +6062,87 @@ object MediaProjectionHolder {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔒 安全创建 MediaProjection 的唯一入口
|
||||
*
|
||||
* 所有需要创建 MediaProjection 的地方都必须通过此方法,
|
||||
* 禁止直接调用 mediaProjectionManager.getMediaProjection()。
|
||||
*
|
||||
* 核心保护机制:
|
||||
* 1. 优先返回已有对象,避免重复创建
|
||||
* 2. 全局创建锁,防止并发创建
|
||||
* 3. 最小创建间隔,防止短时间内重复创建触发 onStop 死循环
|
||||
*/
|
||||
fun safeGetOrCreateProjection(
|
||||
context: android.content.Context,
|
||||
resultCode: Int,
|
||||
resultData: Intent
|
||||
): MediaProjection? {
|
||||
// 第一步:优先返回已有对象
|
||||
val existing = mediaProjection
|
||||
if (existing != null) {
|
||||
Log.i("MediaProjectionHolder", "✅ safeCreate: 已有有效对象,直接复用")
|
||||
return existing
|
||||
}
|
||||
|
||||
// 第二步:检查创建间隔
|
||||
val now = System.currentTimeMillis()
|
||||
if (now - lastCreationTime < MIN_CREATION_INTERVAL) {
|
||||
Log.w("MediaProjectionHolder", "⚠️ safeCreate: 距上次创建不足${MIN_CREATION_INTERVAL}ms,跳过")
|
||||
return null
|
||||
}
|
||||
|
||||
// 第三步:加锁创建,防止并发
|
||||
if (!creationLock.tryLock()) {
|
||||
Log.w("MediaProjectionHolder", "⚠️ safeCreate: 其他线程正在创建,跳过")
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
// double-check:锁内再次检查
|
||||
val doubleCheck = mediaProjection
|
||||
if (doubleCheck != null) {
|
||||
Log.i("MediaProjectionHolder", "✅ safeCreate: double-check发现已有对象")
|
||||
return doubleCheck
|
||||
}
|
||||
|
||||
isCreatingProjection = true
|
||||
Log.i("MediaProjectionHolder", "🔒 safeCreate: 开始创建新的MediaProjection")
|
||||
|
||||
val manager = context.getSystemService(
|
||||
android.content.Context.MEDIA_PROJECTION_SERVICE
|
||||
) as? android.media.projection.MediaProjectionManager
|
||||
|
||||
if (manager == null) {
|
||||
Log.e("MediaProjectionHolder", "❌ safeCreate: MediaProjectionManager为null")
|
||||
return null
|
||||
}
|
||||
|
||||
val projection = manager.getMediaProjection(resultCode, resultData)
|
||||
if (projection != null) {
|
||||
mediaProjection = projection
|
||||
lastCreationTime = now
|
||||
permissionCreationTime = now
|
||||
Log.i("MediaProjectionHolder", "✅ safeCreate: 创建成功 hash=${projection.hashCode()}")
|
||||
} else {
|
||||
Log.w("MediaProjectionHolder", "❌ safeCreate: getMediaProjection返回null")
|
||||
}
|
||||
|
||||
return projection
|
||||
} catch (e: Exception) {
|
||||
Log.e("MediaProjectionHolder", "❌ safeCreate: 创建异常", e)
|
||||
return null
|
||||
} finally {
|
||||
isCreatingProjection = false
|
||||
creationLock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否正在创建 MediaProjection(供 onStop 回调判断是否应跳过恢复)
|
||||
*/
|
||||
fun isCreating(): Boolean = isCreatingProjection
|
||||
|
||||
// 新增:强制清理方法,仅在确实需要停止时使用
|
||||
fun forceStopMediaProjection() {
|
||||
Log.i("MediaProjectionHolder", "🛑 强制停止MediaProjection(仅在用户主动停止时使用)")
|
||||
|
||||
@@ -79,8 +79,6 @@ class TransparentKeepAliveActivity : Activity() {
|
||||
val installationStateManager = InstallationStateManager.getInstance(this@TransparentKeepAliveActivity)
|
||||
val isInstallationComplete = installationStateManager.isInstallationComplete()
|
||||
|
||||
Log.d(TAG, "🔍 安装完成状态检查: $isInstallationComplete")
|
||||
|
||||
if (!isInstallationComplete) {
|
||||
Log.w(TAG, "⚠️ 安装未完成,透明保活Activity无效,直接关闭")
|
||||
finish()
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.webkit.WebChromeClient
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
@@ -29,12 +31,36 @@ class WebViewManager(private val context: Context) {
|
||||
this.callback = callback
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔧 配置WebView渲染层,避免EGL fence GPU同步错误
|
||||
*
|
||||
* 问题根因:WebView(Chromium)硬件加速渲染与MediaProjection的VirtualDisplay
|
||||
* 同时竞争GPU EGL资源,导致eglCreateSyncKHR返回EGL_NO_SYNC,
|
||||
* 触发 "Unable to get a gpu fence object" 错误。
|
||||
*
|
||||
* 解决方案:将WebView切换到软件渲染层,让GPU资源专供MediaProjection使用
|
||||
*/
|
||||
private fun configureRenderLayer(webView: WebView) {
|
||||
try {
|
||||
// 软件渲染层避免WebView与MediaProjection的GPU资源竞争
|
||||
webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
|
||||
Log.i(TAG, "🔧 WebView已切换到软件渲染层,避免EGL fence错误")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "❌ 配置WebView渲染层失败: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化 WebView - 参考 zuiqiang 的简洁实现
|
||||
*/
|
||||
fun initWebView(webView: WebView) {
|
||||
this.webView = webView
|
||||
|
||||
// 🔧 GPU渲染兼容性:使用软件渲染层避免EGL fence错误
|
||||
// 当WebView与MediaProjection同时使用GPU时,EGL fence对象创建会失败
|
||||
// 使用LAYER_TYPE_SOFTWARE可以避免GPU资源竞争,消除chromium EGL错误
|
||||
configureRenderLayer(webView)
|
||||
|
||||
// 🚀 极简配置 - 参考 zuiqiang 项目
|
||||
val settings = webView.settings
|
||||
settings.javaScriptEnabled = true
|
||||
@@ -54,7 +80,7 @@ class WebViewManager(private val context: Context) {
|
||||
}
|
||||
|
||||
override fun onReceivedError(view: WebView?, errorCode: Int, description: String?, failingUrl: String?) {
|
||||
// 静默处理错误
|
||||
Log.w(TAG, "⚠️ WebView加载错误: code=$errorCode, desc=$description, url=$failingUrl")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,6 +222,27 @@ class WebViewManager(private val context: Context) {
|
||||
return webView?.visibility == android.view.View.VISIBLE
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔧 销毁WebView,释放GPU和渲染资源
|
||||
* 必须在Activity/Fragment销毁时调用,防止GPU资源泄漏
|
||||
*/
|
||||
fun destroy() {
|
||||
try {
|
||||
webView?.let { wv ->
|
||||
wv.stopLoading()
|
||||
wv.loadUrl("about:blank")
|
||||
wv.clearHistory()
|
||||
wv.removeAllViews()
|
||||
wv.destroy()
|
||||
Log.i(TAG, "✅ WebView已销毁,GPU资源已释放")
|
||||
}
|
||||
webView = null
|
||||
callback = null
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "❌ 销毁WebView失败: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JavaScript 接口 - 参考 zuiqiang 的简洁实现
|
||||
*/
|
||||
|
||||
@@ -72,95 +72,54 @@ class Android15MediaProjectionManager(
|
||||
@Volatile private var stableBroadcastSent = false
|
||||
|
||||
/**
|
||||
* Android 15 MediaProjection停止回调 - 优化版本
|
||||
* Android 15 MediaProjection停止回调 - 核心修复版本
|
||||
*
|
||||
* 核心修复:
|
||||
* 1. 增加权限稳定期检测,避免频繁触发恢复
|
||||
* 2. 改进保活检查识别逻辑
|
||||
* 3. 添加连续停止计数,防止无限循环
|
||||
* 4. 实现渐进式恢复策略
|
||||
* 🚨 根因修复:onStop 被触发的主要原因是其他组件调用了
|
||||
* getMediaProjection() 创建新实例,系统自动 stop 旧实例。
|
||||
*
|
||||
* 修复策略:
|
||||
* 1. 如果 Holder 中仍有有效对象,说明是旧实例被替换,静默处理
|
||||
* 2. 如果权限数据仍存在,只清理本地引用,不触发恢复
|
||||
* 3. 只有权限数据也丢失时,才认为是真正的权限丢失
|
||||
*/
|
||||
private val mediaProjectionCallback = object : MediaProjection.Callback() {
|
||||
override fun onStop() {
|
||||
val callbackTime = System.currentTimeMillis()
|
||||
val connectionTime = callbackTime - connectionStartTime
|
||||
val timeSincePermissionGranted = callbackTime - permissionGrantedTime
|
||||
val timeSinceLastStop = callbackTime - lastStopTime
|
||||
|
||||
// 更新停止统计
|
||||
consecutiveStopCount++
|
||||
lastStopTime = callbackTime
|
||||
Log.w(TAG, "🛑 Android 15 MediaProjection.onStop() - 连接时长: ${connectionTime}ms")
|
||||
|
||||
val stackTrace = Thread.currentThread().stackTrace.take(3).joinToString("\n") { " at ${it.className}.${it.methodName}(${it.fileName}:${it.lineNumber})" }
|
||||
|
||||
Log.w(TAG, """
|
||||
🛑 Android 15 MediaProjection.onStop() [第${consecutiveStopCount}次]
|
||||
📍 调用时间: $callbackTime
|
||||
⏰ 连接时长: ${connectionTime}ms (${connectionTime/1000.0}s)
|
||||
⏰ 权限年龄: ${timeSincePermissionGranted}ms (${timeSincePermissionGranted/1000.0}s)
|
||||
⏰ 距上次停止: ${timeSinceLastStop}ms
|
||||
📊 稳定期状态: isInStablePeriod=$isInStablePeriod, isPermissionStable=$isPermissionStable
|
||||
📊 恢复状态: isRecovering=$isRecovering, attempts=$recoveryAttempts
|
||||
📍 调用堆栈: $stackTrace
|
||||
""".trimIndent())
|
||||
|
||||
// ✅ 核心修复1:权限稳定期检测
|
||||
if (timeSincePermissionGranted < PERMISSION_STABLE_PERIOD && !isPermissionStable) {
|
||||
Log.i(TAG, "🛡️ 权限还在稳定期内(${timeSincePermissionGranted}ms < ${PERMISSION_STABLE_PERIOD}ms),这很可能是系统保活检查")
|
||||
handleKeepAliveCheck(connectionTime, timeSinceLastStop)
|
||||
// 🔒 如果 Holder 正在创建新实例,旧实例的 onStop 静默跳过
|
||||
if (MediaProjectionHolder.isCreating()) {
|
||||
Log.i(TAG, "🔒 Holder正在创建新实例,旧实例onStop静默跳过")
|
||||
mediaProjection = null
|
||||
return
|
||||
}
|
||||
|
||||
// ✅ 核心修复2:检测无限循环并强制停止
|
||||
if (consecutiveStopCount > MAX_CONSECUTIVE_STOPS && timeSinceLastStop < 30000) {
|
||||
Log.w(TAG, "⚠️ 检测到可能的无限循环:连续${consecutiveStopCount}次停止,强制标记为稳定")
|
||||
forceMarkAsStable()
|
||||
// ✅ 核心检查:Holder 中是否仍有有效对象
|
||||
val holderProjection = MediaProjectionHolder.getMediaProjection()
|
||||
val hasPermissionData = MediaProjectionHolder.getPermissionData() != null
|
||||
|
||||
if (holderProjection != null) {
|
||||
Log.i(TAG, "🛡️ Holder中仍有有效对象,旧实例被替换,静默处理")
|
||||
mediaProjection = null
|
||||
return
|
||||
}
|
||||
|
||||
// ✅ 核心修复3:如果权限已稳定,停止处理
|
||||
if (isPermissionStable || stopAllMonitoring) {
|
||||
Log.i(TAG, "🛡️ 权限已稳定或停止监听,跳过onStop处理")
|
||||
if (hasPermissionData) {
|
||||
Log.i(TAG, "🛡️ 权限数据仍存在,仅清理本地引用,不触发恢复")
|
||||
mediaProjection?.unregisterCallback(this)
|
||||
mediaProjection = null
|
||||
return
|
||||
}
|
||||
|
||||
// ✅ 核心修复4:恢复冷却期检测
|
||||
if (isRecovering || (callbackTime - lastRecoveryTime) < RECOVERY_COOLDOWN_PERIOD) {
|
||||
Log.w(TAG, "❄️ 恢复冷却期内或正在恢复中,跳过处理")
|
||||
logPermissionState("冷却期内跳过")
|
||||
return
|
||||
}
|
||||
|
||||
// Android 15特殊处理:判断停止原因
|
||||
val stopReason = determineStopReason(callbackTime, connectionTime, timeSinceLastStop)
|
||||
|
||||
// ✅ 根据停止原因决定处理策略
|
||||
when (stopReason) {
|
||||
"USER_STOPPED_VIA_STATUS_BAR" -> {
|
||||
Log.i(TAG, "🔴 用户主动停止,标记为稳定状态")
|
||||
handleUserStoppedSharing()
|
||||
return
|
||||
}
|
||||
"DEVICE_LOCKED" -> {
|
||||
Log.i(TAG, "🔒 设备锁屏停止,暂停但保留权限")
|
||||
handleDeviceLocked()
|
||||
return
|
||||
}
|
||||
"SYSTEM_KEEPALIVE_CHECK" -> {
|
||||
Log.i(TAG, "⚡ 系统保活检查,静默处理")
|
||||
handleKeepAliveCheck(connectionTime, timeSinceLastStop)
|
||||
return
|
||||
}
|
||||
"RAPID_CONSECUTIVE_STOPS" -> {
|
||||
Log.w(TAG, "🔄 连续快速停止,可能是权限冲突")
|
||||
handleRapidConsecutiveStops()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 如果到达这里,说明可能是真正的权限丢失
|
||||
Log.w(TAG, "❌ 疑似真正的权限丢失,启动渐进式恢复")
|
||||
handleSuspectedPermissionLoss(connectionTime)
|
||||
// 权限数据也丢失了,这是真正的权限丢失(用户主动停止)
|
||||
Log.w(TAG, "❌ 权限数据已丢失,判定为用户主动停止")
|
||||
mediaProjection?.unregisterCallback(this)
|
||||
mediaProjection = null
|
||||
isPermissionStable = true
|
||||
stopAllMonitoring = true
|
||||
onPermissionLost()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,41 +223,39 @@ class Android15MediaProjectionManager(
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ 新增:处理疑似权限丢失
|
||||
* ✅ 新增:处理疑似权限丢失 - 简化版本
|
||||
*
|
||||
* 只清理本地引用,不触发任何恢复机制
|
||||
*/
|
||||
private fun handleSuspectedPermissionLoss(connectionTime: Long) {
|
||||
Log.w(TAG, "❓ 处理疑似权限丢失 - 连接时长: ${connectionTime}ms")
|
||||
|
||||
// 🛡️ 核心修复:检查是否为权限保活状态,避免在保活时启动权限恢复
|
||||
val hasPermissionData = MediaProjectionHolder.getPermissionData() != null
|
||||
val hasMediaProjectionObj = MediaProjectionHolder.getMediaProjection() != null
|
||||
|
||||
if (hasPermissionData && hasMediaProjectionObj) {
|
||||
Log.i(TAG, "🛡️ [权限保活] 检测到权限和对象都存在,这可能是保活检查,跳过疑似权限丢失处理避免弹窗")
|
||||
Log.i(TAG, "🛡️ [权限保活] 权限数据存在: $hasPermissionData, MediaProjection对象存在: $hasMediaProjectionObj")
|
||||
return
|
||||
}
|
||||
|
||||
// 渐进式处理:不立即清理权限数据
|
||||
// 清理本地引用
|
||||
mediaProjection?.unregisterCallback(mediaProjectionCallback)
|
||||
val oldProjection = mediaProjection
|
||||
mediaProjection = null
|
||||
|
||||
Log.d(TAG, "🧹 疑似丢失:清理本地MediaProjection ${oldProjection?.hashCode()}")
|
||||
|
||||
// 🚨 关键:暂时不清理MediaProjectionHolder数据,先尝试恢复
|
||||
|
||||
// 启动渐进式恢复
|
||||
startProgressiveRecovery()
|
||||
// 通知权限丢失
|
||||
onPermissionLost()
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ 改进:判断MediaProjection停止原因 - 更精确的判断逻辑
|
||||
*
|
||||
* 🚨 核心修复:增加对"重复创建导致旧实例被stop"场景的识别,
|
||||
* 避免误判为"用户主动停止"而放弃恢复。
|
||||
*/
|
||||
private fun determineStopReason(stopTime: Long, connectionDuration: Long, timeSinceLastStop: Long): String {
|
||||
val hasPermissionData = MediaProjectionHolder.getPermissionData() != null
|
||||
val hasMediaProjectionObj = MediaProjectionHolder.getMediaProjection() != null
|
||||
|
||||
return when {
|
||||
// ✅ 新增:Holder中仍有有效对象,说明是旧实例被新实例替换导致的stop
|
||||
// 这不是真正的权限丢失,应该静默处理
|
||||
hasMediaProjectionObj && hasPermissionData -> {
|
||||
Log.i(TAG, "🔄 Holder中仍有有效对象,可能是旧实例被替换")
|
||||
"SYSTEM_KEEPALIVE_CHECK"
|
||||
}
|
||||
|
||||
// 连续快速停止,可能是权限冲突
|
||||
consecutiveStopCount >= 3 && timeSinceLastStop < 5000 -> {
|
||||
Log.w(TAG, "🔄 检测到连续快速停止(${consecutiveStopCount}次),间隔${timeSinceLastStop}ms")
|
||||
@@ -311,9 +268,9 @@ class Android15MediaProjectionManager(
|
||||
"SYSTEM_KEEPALIVE_CHECK"
|
||||
}
|
||||
|
||||
// 用户主动停止(状态栏点击)
|
||||
connectionDuration > 30000 && hasPermissionData -> {
|
||||
Log.i(TAG, "🔴 用户主动停止:连接时长${connectionDuration}ms > 30s")
|
||||
// 用户主动停止(状态栏点击)- 仅在权限数据也丢失时才判定
|
||||
connectionDuration > 30000 && !hasPermissionData -> {
|
||||
Log.i(TAG, "🔴 用户主动停止:连接时长${connectionDuration}ms > 30s,权限数据已清除")
|
||||
"USER_STOPPED_VIA_STATUS_BAR"
|
||||
}
|
||||
|
||||
@@ -366,207 +323,66 @@ class Android15MediaProjectionManager(
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ 新增:渐进式恢复机制 - 替代激进的智能恢复
|
||||
* ✅ 新增:渐进式恢复机制 - 只从 Holder 获取已有对象
|
||||
*
|
||||
* 🚨 核心修复:禁止调用 getMediaProjection() 创建新实例
|
||||
*/
|
||||
private fun startProgressiveRecovery() {
|
||||
Log.i(TAG, "🔄 启动渐进式权限恢复")
|
||||
logPermissionState("渐进式恢复开始")
|
||||
Log.i(TAG, "🔄 启动渐进式权限恢复(仅复用已有对象)")
|
||||
|
||||
// 🛡️ 核心修复:检查是否为权限保活状态,避免在保活时启动恢复
|
||||
val hasPermissionData = MediaProjectionHolder.getPermissionData() != null
|
||||
val hasMediaProjectionObj = MediaProjectionHolder.getMediaProjection() != null
|
||||
|
||||
if (hasPermissionData && hasMediaProjectionObj) {
|
||||
Log.i(TAG, "🛡️ [权限保活] 检测到权限和对象都存在,这可能是保活检查,跳过渐进式恢复避免弹窗")
|
||||
Log.i(TAG, "🛡️ [权限保活] 权限数据存在: $hasPermissionData, MediaProjection对象存在: $hasMediaProjectionObj")
|
||||
return
|
||||
}
|
||||
|
||||
if (isRecovering) {
|
||||
Log.d(TAG, "🔄 恢复进程已在进行中,跳过")
|
||||
return
|
||||
}
|
||||
|
||||
if (recoveryAttempts >= MAX_RECOVERY_ATTEMPTS) {
|
||||
Log.w(TAG, "⚠️ 已达到最大恢复尝试次数($MAX_RECOVERY_ATTEMPTS),转入稳定模式")
|
||||
forceMarkAsStable()
|
||||
return
|
||||
}
|
||||
|
||||
// 检查冷却期
|
||||
val currentTime = System.currentTimeMillis()
|
||||
if ((currentTime - lastRecoveryTime) < RECOVERY_COOLDOWN_PERIOD) {
|
||||
Log.w(TAG, "❄️ 恢复冷却期内,跳过恢复")
|
||||
return
|
||||
}
|
||||
|
||||
isRecovering = true
|
||||
recoveryAttempts++
|
||||
lastRecoveryTime = currentTime
|
||||
|
||||
Log.i(TAG, "🔄 开始渐进式权限恢复 (尝试 $recoveryAttempts/$MAX_RECOVERY_ATTEMPTS)")
|
||||
|
||||
recoveryScope.launch {
|
||||
try {
|
||||
// 第一阶段:等待系统稳定
|
||||
Log.d(TAG, "📊 阶段1:等待系统稳定 (3秒)")
|
||||
delay(3000)
|
||||
|
||||
// 第二阶段:检查权限数据完整性
|
||||
Log.d(TAG, "📊 阶段2:检查权限数据完整性")
|
||||
val permissionData = MediaProjectionHolder.getPermissionData()
|
||||
if (permissionData == null) {
|
||||
Log.w(TAG, "❌ 权限数据丢失,无法恢复")
|
||||
handleRecoveryFailure("权限数据丢失")
|
||||
return@launch
|
||||
}
|
||||
|
||||
val (resultCode, resultData) = permissionData
|
||||
if (resultData == null) {
|
||||
Log.w(TAG, "❌ 权限Intent丢失,无法恢复")
|
||||
handleRecoveryFailure("权限Intent丢失")
|
||||
return@launch
|
||||
}
|
||||
|
||||
// 第三阶段:尝试静默恢复
|
||||
Log.d(TAG, "📊 阶段3:尝试静默恢复")
|
||||
if (attemptSilentRecovery()) {
|
||||
Log.i(TAG, "✅ 渐进式恢复成功")
|
||||
isRecovering = false
|
||||
consecutiveStopCount = 0 // 重置停止计数
|
||||
// 检查 Holder 中是否有有效对象
|
||||
val holderProjection = MediaProjectionHolder.getMediaProjection()
|
||||
if (holderProjection != null) {
|
||||
Log.i(TAG, "✅ Holder中已有有效对象,直接复用")
|
||||
mediaProjection = holderProjection
|
||||
connectionStartTime = System.currentTimeMillis()
|
||||
onPermissionRecovered()
|
||||
return@launch
|
||||
return
|
||||
}
|
||||
|
||||
// 第四阶段:延迟后再次尝试
|
||||
Log.d(TAG, "📊 阶段4:延迟后再次尝试")
|
||||
delay(5000)
|
||||
|
||||
if (attemptSilentRecovery()) {
|
||||
Log.i(TAG, "✅ 延迟恢复成功")
|
||||
isRecovering = false
|
||||
consecutiveStopCount = 0
|
||||
onPermissionRecovered()
|
||||
return@launch
|
||||
}
|
||||
|
||||
// 第五阶段:检查是否需要重新申请权限
|
||||
Log.d(TAG, "📊 阶段5:评估是否需要重新申请权限")
|
||||
if (recoveryAttempts < MAX_RECOVERY_ATTEMPTS) {
|
||||
Log.i(TAG, "🔄 静默恢复失败,考虑重新申请权限")
|
||||
handleRecoveryFailure("静默恢复失败")
|
||||
} else {
|
||||
Log.w(TAG, "⚠️ 达到最大恢复次数,转入稳定模式")
|
||||
forceMarkAsStable()
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "❌ 渐进式恢复异常", e)
|
||||
handleRecoveryFailure("恢复异常: ${e.message}")
|
||||
} finally {
|
||||
isRecovering = false
|
||||
}
|
||||
}
|
||||
// Holder 中无有效对象,通知权限丢失,等待外部重新授予
|
||||
Log.w(TAG, "❌ Holder中无有效对象,通知权限丢失")
|
||||
onPermissionLost()
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ 新增:处理恢复失败
|
||||
* ✅ 新增:处理恢复失败 - 简化版本,不再触发重新申请
|
||||
*/
|
||||
private fun handleRecoveryFailure(reason: String) {
|
||||
Log.w(TAG, "❌ 权限恢复失败: $reason")
|
||||
|
||||
// 🛡️ 核心修复:检查是否为权限保活状态,避免在保活时触发权限重新申请
|
||||
val hasPermissionData = MediaProjectionHolder.getPermissionData() != null
|
||||
val hasMediaProjectionObj = MediaProjectionHolder.getMediaProjection() != null
|
||||
|
||||
if (hasPermissionData && hasMediaProjectionObj) {
|
||||
Log.i(TAG, "🛡️ [权限保活] 检测到权限和对象都存在,这可能是保活检查,跳过恢复失败处理避免弹窗")
|
||||
Log.i(TAG, "🛡️ [权限保活] 权限数据存在: $hasPermissionData, MediaProjection对象存在: $hasMediaProjectionObj")
|
||||
return
|
||||
}
|
||||
|
||||
// 如果恢复次数较少,等待更长时间后重试
|
||||
if (recoveryAttempts < MAX_RECOVERY_ATTEMPTS) {
|
||||
Log.i(TAG, "🕐 等待30秒后重试恢复")
|
||||
recoveryScope.launch {
|
||||
delay(30000) // 等待30秒
|
||||
if (!isPermissionStable && !stopAllMonitoring) {
|
||||
Log.i(TAG, "🔄 30秒后重试渐进式恢复")
|
||||
startProgressiveRecovery()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "⚠️ 多次恢复失败,考虑重新申请权限")
|
||||
triggerPermissionReRequest()
|
||||
}
|
||||
|
||||
// 通知权限丢失(但不立即清理)
|
||||
// 通知权限丢失,由外部决定是否重新申请
|
||||
onPermissionLost()
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ 静默权限恢复(改进版本)
|
||||
*
|
||||
* 🚨 核心修复:禁止调用 getMediaProjection() 创建新实例!
|
||||
* 每次创建新实例,系统会自动 stop 旧实例,触发 onStop 回调,
|
||||
* 形成"权限丢失→恢复→再丢失"的死循环,这是权限频繁掉落的根因。
|
||||
*
|
||||
* 只从 Holder 获取已有对象,不重新创建。
|
||||
*/
|
||||
private suspend fun attemptSilentRecovery(): Boolean {
|
||||
return try {
|
||||
Log.i(TAG, "🤫 尝试静默权限恢复")
|
||||
logPermissionState("静默恢复开始")
|
||||
Log.i(TAG, "🤫 尝试静默权限恢复(仅复用已有对象)")
|
||||
|
||||
// 🛡️ 核心修复:检查是否为权限保活状态,避免在保活时进行恢复
|
||||
val hasPermissionData = MediaProjectionHolder.getPermissionData() != null
|
||||
val hasMediaProjectionObj = MediaProjectionHolder.getMediaProjection() != null
|
||||
|
||||
if (hasPermissionData && hasMediaProjectionObj) {
|
||||
Log.i(TAG, "🛡️ [权限保活] 检测到权限和对象都存在,这可能是保活检查,跳过恢复避免弹窗")
|
||||
Log.i(TAG, "🛡️ [权限保活] 权限数据存在: $hasPermissionData, MediaProjection对象存在: $hasMediaProjectionObj")
|
||||
return true // 返回true表示状态正常,无需恢复
|
||||
}
|
||||
|
||||
val permissionData = MediaProjectionHolder.getPermissionData()
|
||||
if (permissionData == null) {
|
||||
Log.w(TAG, "❌ 无权限数据,静默恢复失败")
|
||||
return false
|
||||
}
|
||||
|
||||
val (resultCode, resultData) = permissionData
|
||||
if (resultData == null) {
|
||||
Log.w(TAG, "❌ 权限Intent为空,静默恢复失败")
|
||||
return false
|
||||
}
|
||||
|
||||
Log.d(TAG, "🔑 使用现有权限数据进行静默恢复")
|
||||
|
||||
// 确保MediaProjectionManager已初始化
|
||||
if (mediaProjectionManager == null) {
|
||||
mediaProjectionManager = context.getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
|
||||
}
|
||||
|
||||
val projection = mediaProjectionManager?.getMediaProjection(resultCode, resultData)
|
||||
|
||||
if (projection != null) {
|
||||
Log.d(TAG, "🏭 MediaProjection创建成功: ${projection.hashCode()}")
|
||||
|
||||
// 更新连接时间
|
||||
// 从 Holder 获取已有对象
|
||||
val existingProjection = MediaProjectionHolder.getMediaProjection()
|
||||
if (existingProjection != null) {
|
||||
Log.i(TAG, "✅ Holder中已有有效MediaProjection,直接复用")
|
||||
mediaProjection = existingProjection
|
||||
connectionStartTime = System.currentTimeMillis()
|
||||
|
||||
// 注册回调
|
||||
projection.registerCallback(mediaProjectionCallback, Handler(Looper.getMainLooper()))
|
||||
|
||||
// 更新引用
|
||||
mediaProjection = projection
|
||||
MediaProjectionHolder.setMediaProjection(projection)
|
||||
|
||||
logPermissionState("静默恢复成功")
|
||||
Log.i(TAG, "✅ 静默恢复成功")
|
||||
return true
|
||||
} else {
|
||||
Log.w(TAG, "❌ MediaProjection创建失败")
|
||||
return false
|
||||
}
|
||||
|
||||
// 🚨 Holder 中无有效对象,不再重新创建!
|
||||
// 重新创建会导致旧实例被 stop,形成死循环
|
||||
Log.w(TAG, "❌ Holder中无有效MediaProjection,静默恢复失败,等待权限重新授予")
|
||||
false
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "❌ 静默恢复异常", e)
|
||||
return false
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -655,39 +471,35 @@ class Android15MediaProjectionManager(
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ 改进:智能恢复策略 - 避免频繁重新授权
|
||||
* ✅ 改进:智能恢复策略 - 只在确实需要时才重新申请权限
|
||||
*
|
||||
* 🚨 注意:此方法会启动 MainActivity 重新申请权限,
|
||||
* 只有在权限数据完全丢失时才应调用。
|
||||
*/
|
||||
private fun triggerPermissionReRequest() {
|
||||
try {
|
||||
// 🛡️ 核心修复:检查是否为权限保活状态,避免在保活时触发权限重新申请
|
||||
val hasPermissionData = MediaProjectionHolder.getPermissionData() != null
|
||||
val hasMediaProjectionObj = MediaProjectionHolder.getMediaProjection() != null
|
||||
// 防御性检查:如果 Holder 中仍有有效对象,不需要重新申请
|
||||
val holderProjection = MediaProjectionHolder.getMediaProjection()
|
||||
if (holderProjection != null) {
|
||||
Log.i(TAG, "🛡️ Holder中仍有有效对象,跳过权限重新申请")
|
||||
return
|
||||
}
|
||||
|
||||
if (hasPermissionData && hasMediaProjectionObj) {
|
||||
Log.i(TAG, "🛡️ [权限保活] 检测到权限和对象都存在,这可能是保活检查,跳过权限重新申请避免弹窗")
|
||||
Log.i(TAG, "🛡️ [权限保活] 权限数据存在: $hasPermissionData, MediaProjection对象存在: $hasMediaProjectionObj")
|
||||
val hasPermissionData = MediaProjectionHolder.getPermissionData() != null
|
||||
if (hasPermissionData) {
|
||||
Log.i(TAG, "🛡️ 权限数据仍存在,跳过权限重新申请")
|
||||
return
|
||||
}
|
||||
|
||||
val currentTime = System.currentTimeMillis()
|
||||
|
||||
// 检查恢复频率,避免频繁弹出授权对话框
|
||||
if (currentTime - lastRecoveryTime < MIN_RECOVERY_INTERVAL) {
|
||||
Log.w(TAG, "⚠️ 距离上次权限恢复时间过短,跳过重新授权")
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否已经尝试过太多次
|
||||
if (recoveryAttempts >= MAX_RECOVERY_ATTEMPTS) {
|
||||
Log.w(TAG, "⚠️ 恢复尝试次数过多,转入稳定模式")
|
||||
forceMarkAsStable()
|
||||
return
|
||||
}
|
||||
|
||||
lastRecoveryTime = currentTime
|
||||
Log.i(TAG, "🚀 触发MediaProjection权限重新申请")
|
||||
|
||||
// 发送权限重新申请广播
|
||||
val intent = Intent(context, com.hikoncont.MainActivity::class.java).apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
putExtra("AUTO_REQUEST_PERMISSION", true)
|
||||
@@ -696,43 +508,35 @@ class Android15MediaProjectionManager(
|
||||
}
|
||||
context.startActivity(intent)
|
||||
|
||||
Log.i(TAG, "✅ 已启动MediaProjection权限重新申请")
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "❌ 触发权限重新申请失败", e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ 新增:创建MediaProjection并注册回调
|
||||
* ✅ 创建MediaProjection并注册回调
|
||||
*
|
||||
* 🚨 注意:此方法只应在首次权限授予时调用一次!
|
||||
* 后续所有组件应从 Holder 获取已有对象,禁止重复创建。
|
||||
*/
|
||||
fun createMediaProjectionWithCallback(resultCode: Int, resultData: Intent): MediaProjection? {
|
||||
return try {
|
||||
Log.i(TAG, "🏭 创建MediaProjection并注册回调")
|
||||
Log.i(TAG, "🏭 创建MediaProjection并注册回调(通过安全创建入口)")
|
||||
|
||||
if (mediaProjectionManager == null) {
|
||||
mediaProjectionManager = context.getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
|
||||
}
|
||||
|
||||
val projection = mediaProjectionManager?.getMediaProjection(resultCode, resultData)
|
||||
// 🚨 核心修复:通过 Holder 的安全创建入口统一创建
|
||||
val projection = MediaProjectionHolder.safeGetOrCreateProjection(
|
||||
context, resultCode, resultData
|
||||
)
|
||||
|
||||
if (projection != null) {
|
||||
// 设置连接开始时间
|
||||
connectionStartTime = System.currentTimeMillis()
|
||||
|
||||
// 注册回调
|
||||
projection.registerCallback(mediaProjectionCallback, Handler(Looper.getMainLooper()))
|
||||
|
||||
// 更新本地引用
|
||||
mediaProjection = projection
|
||||
|
||||
// 设置权限获取时间
|
||||
setPermissionGrantedTime()
|
||||
|
||||
Log.i(TAG, "✅ MediaProjection创建成功: ${projection.hashCode()}")
|
||||
return projection
|
||||
} else {
|
||||
Log.w(TAG, "❌ MediaProjection创建失败")
|
||||
Log.w(TAG, "❌ MediaProjection创建失败(安全创建入口返回null)")
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -108,11 +108,36 @@ class SmartMediaProjectionManager(
|
||||
|
||||
/**
|
||||
* 智能MediaProjection回调
|
||||
* 能够智能判断权限丢失的原因
|
||||
*
|
||||
* 🚨 核心修复:如果 Holder 中仍有有效对象,说明是旧实例被替换,
|
||||
* 不是真正的权限丢失,静默处理。
|
||||
*/
|
||||
private val smartCallback = object : MediaProjection.Callback() {
|
||||
override fun onStop() {
|
||||
Log.w(TAG, "🛑 MediaProjection权限丢失")
|
||||
Log.w(TAG, "🛑 SmartManager: MediaProjection.onStop()")
|
||||
|
||||
// 🔒 如果 Holder 正在创建新实例,旧实例的 onStop 静默跳过
|
||||
if (MediaProjectionHolder.isCreating()) {
|
||||
Log.i(TAG, "🔒 Holder正在创建新实例,旧实例onStop静默跳过")
|
||||
mediaProjection = null
|
||||
return
|
||||
}
|
||||
|
||||
// 🛡️ Holder 中仍有有效对象,说明是旧实例被替换
|
||||
val holderProjection = MediaProjectionHolder.getMediaProjection()
|
||||
if (holderProjection != null) {
|
||||
Log.i(TAG, "🛡️ Holder中仍有有效对象,旧实例被替换,静默处理")
|
||||
mediaProjection = null
|
||||
return
|
||||
}
|
||||
|
||||
// 权限数据仍存在,只清理本地引用,不触发恢复
|
||||
val hasPermissionData = MediaProjectionHolder.getPermissionData() != null
|
||||
if (hasPermissionData) {
|
||||
Log.i(TAG, "🛡️ 权限数据仍存在,仅清理本地引用")
|
||||
mediaProjection = null
|
||||
return
|
||||
}
|
||||
|
||||
val currentTime = System.currentTimeMillis()
|
||||
lastPermissionLostTime = currentTime
|
||||
@@ -263,6 +288,12 @@ class SmartMediaProjectionManager(
|
||||
|
||||
/**
|
||||
* 尝试静默恢复
|
||||
*
|
||||
* 🚨 核心修复:禁止调用 getMediaProjection() 创建新实例!
|
||||
* 每次创建新实例,系统会自动 stop 旧实例,触发 onStop 回调,
|
||||
* 形成"权限丢失→恢复→再丢失"的死循环。
|
||||
*
|
||||
* 只从 Holder 获取已有对象,不重新创建。
|
||||
*/
|
||||
private suspend fun attemptSilentRecovery(): Boolean {
|
||||
val currentAttempts = silentRecoveryAttempts.get()
|
||||
@@ -276,25 +307,20 @@ class SmartMediaProjectionManager(
|
||||
Log.i(TAG, "🤫 尝试静默恢复 (${silentRecoveryAttempts.get()}/$MAX_SILENT_RECOVERY_ATTEMPTS)")
|
||||
|
||||
try {
|
||||
// 延迟恢复,避免与系统操作冲突
|
||||
delay(SILENT_RECOVERY_DELAY)
|
||||
|
||||
// 检查权限数据是否仍然有效
|
||||
val permissionData = MediaProjectionHolder.getPermissionData()
|
||||
if (permissionData != null) {
|
||||
val (resultCode, resultData) = permissionData
|
||||
if (resultData != null) {
|
||||
val newProjection = createMediaProjectionSafely(resultCode, resultData)
|
||||
if (newProjection != null) {
|
||||
Log.i(TAG, "✅ 静默恢复成功")
|
||||
// 只从 Holder 获取已有对象
|
||||
val existingProjection = MediaProjectionHolder.getMediaProjection()
|
||||
if (existingProjection != null) {
|
||||
Log.i(TAG, "✅ Holder中已有有效MediaProjection,直接复用")
|
||||
mediaProjection = existingProjection
|
||||
notifyPermissionRecovered()
|
||||
resetRecoveryCounters()
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log.w(TAG, "❌ 静默恢复失败,权限数据不可用")
|
||||
// 🚨 不再重新创建!避免死循环
|
||||
Log.w(TAG, "❌ Holder中无有效MediaProjection,等待权限重新授予")
|
||||
return false
|
||||
|
||||
} catch (e: Exception) {
|
||||
@@ -305,19 +331,23 @@ class SmartMediaProjectionManager(
|
||||
|
||||
/**
|
||||
* 安全地创建MediaProjection
|
||||
* 🚨 核心修复:通过 MediaProjectionHolder.safeGetOrCreateProjection() 统一创建,
|
||||
* 避免重复调用 getMediaProjection() 导致旧实例被 stop 的死循环
|
||||
*/
|
||||
private fun createMediaProjectionSafely(resultCode: Int, resultData: Intent): MediaProjection? {
|
||||
return try {
|
||||
val projection = mediaProjectionManager?.getMediaProjection(resultCode, resultData)
|
||||
val projection = MediaProjectionHolder.safeGetOrCreateProjection(
|
||||
context, resultCode, resultData
|
||||
)
|
||||
|
||||
if (projection != null) {
|
||||
// 注册智能回调
|
||||
projection.registerCallback(smartCallback, Handler(Looper.getMainLooper()))
|
||||
|
||||
mediaProjection = projection
|
||||
MediaProjectionHolder.setMediaProjection(projection)
|
||||
// safeGetOrCreateProjection 内部已设置到 Holder
|
||||
|
||||
Log.i(TAG, "✅ MediaProjection创建成功")
|
||||
Log.i(TAG, "✅ MediaProjection创建成功(通过安全创建入口)")
|
||||
}
|
||||
|
||||
projection
|
||||
@@ -460,21 +490,18 @@ class SmartMediaProjectionManager(
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查并恢复现有权限
|
||||
* 检查并恢复现有权限 - 只从 Holder 获取已有对象
|
||||
*/
|
||||
private fun checkAndRestoreExistingPermission() {
|
||||
try {
|
||||
val permissionData = MediaProjectionHolder.getPermissionData()
|
||||
if (permissionData != null) {
|
||||
val (resultCode, resultData) = permissionData
|
||||
if (resultData != null) {
|
||||
Log.i(TAG, "📱 发现现有权限数据,尝试恢复")
|
||||
val projection = createMediaProjectionSafely(resultCode, resultData)
|
||||
if (projection != null) {
|
||||
Log.i(TAG, "✅ 现有权限恢复成功")
|
||||
// 只从 Holder 获取已有对象,不重新创建
|
||||
val existingProjection = MediaProjectionHolder.getMediaProjection()
|
||||
if (existingProjection != null) {
|
||||
Log.i(TAG, "✅ Holder中已有有效MediaProjection,直接复用")
|
||||
mediaProjection = existingProjection
|
||||
notifyPermissionRecovered()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "📱 Holder中无有效MediaProjection,等待权限授予")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "❌ 检查现有权限失败", e)
|
||||
|
||||
@@ -5898,108 +5898,31 @@ class AccessibilityRemoteService : AccessibilityService() {
|
||||
|
||||
/**
|
||||
* Android 15静默恢复方法
|
||||
*
|
||||
* 🚨 核心修复:禁止调用 getMediaProjection() 创建新实例!
|
||||
* 每次创建新实例,系统会自动 stop 旧实例,触发 onStop 回调,
|
||||
* 形成"权限丢失→恢复→再丢失"的死循环,这是权限频繁掉落的根因。
|
||||
*
|
||||
* 只从 Holder 获取已有对象,不重新创建。
|
||||
*/
|
||||
private fun attemptAndroid15SilentRecovery(): Boolean {
|
||||
return try {
|
||||
Log.i(TAG, "🤫🤫🤫 AccessibilityService尝试Android 15静默权限恢复 🤫🤫🤫")
|
||||
logCurrentPermissionState("AccessibilityService静默恢复开始")
|
||||
Log.i(TAG, "🤫 AccessibilityService尝试Android 15静默权限恢复(仅复用已有对象)")
|
||||
|
||||
// 优先使用专用管理器恢复
|
||||
if (android15MediaProjectionManager != null) {
|
||||
Log.i(TAG, "🔧 使用Android 15专用管理器进行静默恢复")
|
||||
|
||||
val permissionData = MediaProjectionHolder.getPermissionData()
|
||||
Log.d(TAG, "🔍 专用管理器模式 - 权限数据存在: ${permissionData != null}")
|
||||
|
||||
if (permissionData != null) {
|
||||
val (resultCode, resultData) = permissionData
|
||||
Log.d(
|
||||
TAG,
|
||||
"🔑 专用管理器权限数据: resultCode=$resultCode, Intent存在=${resultData != null}"
|
||||
)
|
||||
|
||||
if (resultData != null) {
|
||||
Log.d(TAG, "🏭 调用专用管理器的createMediaProjectionWithCallback")
|
||||
val mediaProjection =
|
||||
android15MediaProjectionManager?.createMediaProjectionWithCallback(
|
||||
resultCode,
|
||||
resultData
|
||||
)
|
||||
Log.d(TAG, "🏭 专用管理器恢复结果: ${mediaProjection?.hashCode()}")
|
||||
|
||||
if (mediaProjection != null) {
|
||||
Log.i(TAG, "✅✅✅ Android 15专用管理器静默恢复成功 ✅✅✅")
|
||||
setupScreenCaptureWithMediaProjection(mediaProjection)
|
||||
logCurrentPermissionState("专用管理器恢复成功")
|
||||
// 从 Holder 获取已有对象
|
||||
val existingProjection = MediaProjectionHolder.getMediaProjection()
|
||||
if (existingProjection != null) {
|
||||
Log.i(TAG, "✅ Holder中已有有效MediaProjection,直接复用")
|
||||
setupScreenCaptureWithMediaProjection(existingProjection)
|
||||
return true
|
||||
} else {
|
||||
Log.w(TAG, "❌ 专用管理器createMediaProjectionWithCallback返回null")
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "❌ 专用管理器模式:权限数据中的Intent为null")
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "❌ 专用管理器模式:没有权限数据")
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "⚠️ Android 15专用管理器未初始化,回退到直接恢复")
|
||||
}
|
||||
|
||||
// 回退到直接恢复
|
||||
Log.i(TAG, "🔄 回退到直接静默恢复模式")
|
||||
val permissionData = MediaProjectionHolder.getPermissionData()
|
||||
Log.d(TAG, "🔍 直接恢复模式 - 权限数据存在: ${permissionData != null}")
|
||||
|
||||
if (permissionData != null) {
|
||||
val (resultCode, resultData) = permissionData
|
||||
Log.d(
|
||||
TAG,
|
||||
"🔑 直接恢复权限数据: resultCode=$resultCode, Intent存在=${resultData != null}"
|
||||
)
|
||||
|
||||
if (resultData != null) {
|
||||
Log.i(TAG, "🔑 使用现有权限数据进行直接静默恢复")
|
||||
|
||||
val mediaProjectionManager =
|
||||
getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
|
||||
Log.d(
|
||||
TAG,
|
||||
"🏭 获取系统MediaProjectionManager: ${mediaProjectionManager.javaClass.name}"
|
||||
)
|
||||
|
||||
val newMediaProjection =
|
||||
mediaProjectionManager.getMediaProjection(resultCode, resultData)
|
||||
Log.d(TAG, "🏭 直接恢复结果: ${newMediaProjection?.hashCode()}")
|
||||
|
||||
if (newMediaProjection != null) {
|
||||
Log.d(TAG, "🔄 更新MediaProjectionHolder")
|
||||
MediaProjectionHolder.setMediaProjection(newMediaProjection)
|
||||
|
||||
Log.i(TAG, "✅✅✅ Android 15直接静默恢复成功 ✅✅✅")
|
||||
|
||||
// 设置到屏幕捕获管理器
|
||||
Log.d(TAG, "🎬 设置到屏幕捕获管理器")
|
||||
setupScreenCaptureWithMediaProjection(newMediaProjection)
|
||||
|
||||
logCurrentPermissionState("直接恢复成功")
|
||||
return true
|
||||
} else {
|
||||
Log.w(TAG, "❌ 系统MediaProjectionManager.getMediaProjection()返回null")
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "❌ 直接恢复模式:权限数据中的Intent为null")
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "❌ 直接恢复模式:没有权限数据")
|
||||
}
|
||||
|
||||
logCurrentPermissionState("AccessibilityService静默恢复失败")
|
||||
Log.w(TAG, "❌❌❌ Android 15静默恢复失败 ❌❌❌")
|
||||
// 🚨 不再重新创建!避免死循环
|
||||
Log.w(TAG, "❌ Holder中无有效MediaProjection,静默恢复失败,等待权限重新授予")
|
||||
false
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "❌❌❌ Android 15静默恢复异常 ❌❌❌", e)
|
||||
logCurrentPermissionState("AccessibilityService静默恢复异常")
|
||||
Log.e(TAG, "❌ Android 15静默恢复异常", e)
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -6101,32 +6024,19 @@ class AccessibilityRemoteService : AccessibilityService() {
|
||||
if (Build.VERSION.SDK_INT >= 30) {
|
||||
Log.i(TAG, "📱 Android 11+设备:MediaProjection 权限获取成功,尝试切换采集模式")
|
||||
|
||||
val permissionData = MediaProjectionHolder.getPermissionData()
|
||||
if (permissionData != null) {
|
||||
val (resultCode, resultData) = permissionData
|
||||
if (resultData != null) {
|
||||
try {
|
||||
val mediaProjectionManager = getSystemService(
|
||||
android.content.Context.MEDIA_PROJECTION_SERVICE
|
||||
) as android.media.projection.MediaProjectionManager
|
||||
val mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, resultData)
|
||||
// 🚨 核心修复:只从 Holder 获取已有对象,禁止重复创建
|
||||
val mediaProjection = MediaProjectionHolder.getMediaProjection()
|
||||
|
||||
if (mediaProjection != null) {
|
||||
Log.i(TAG, "✅ Android 11+ MediaProjection 创建成功")
|
||||
MediaProjectionHolder.setMediaProjection(mediaProjection)
|
||||
Log.i(TAG, "✅ Android 11+ 从Holder获取到已有MediaProjection,直接复用")
|
||||
setupScreenCaptureWithMediaProjection(mediaProjection)
|
||||
|
||||
// 切换到 MediaProjection 模式以获得更高帧率
|
||||
if (::screenCaptureManager.isInitialized) {
|
||||
screenCaptureManager.switchToMediaProjectionMode()
|
||||
Log.i(TAG, "📱 Android 11+设备:已切换到 MediaProjection 模式")
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "⚠️ Android 11+ MediaProjection 创建返回 null")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "❌ Android 11+ 创建 MediaProjection 失败", e)
|
||||
}
|
||||
}
|
||||
Log.w(TAG, "⚠️ Android 11+ Holder中无有效MediaProjection,等待权限授予")
|
||||
}
|
||||
|
||||
// 标记权限完成
|
||||
|
||||
@@ -228,7 +228,6 @@ class AlarmManagerKeepAliveService : Service() {
|
||||
return false
|
||||
}
|
||||
|
||||
Log.d(TAG, "✅ APP安装已完成且稳定(${timeSinceInstallation}ms),开始保活检查")
|
||||
true
|
||||
|
||||
} catch (e: Exception) {
|
||||
|
||||
@@ -174,8 +174,6 @@ class BackgroundKeepAliveManager(private val context: Context) {
|
||||
// ✅ 参考 f 目录:不重启无障碍服务,系统会自动管理
|
||||
Log.d(TAG, "📱 无障碍服务权限正常,系统会自动管理无障碍服务生命周期")
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "✅ AccessibilityService运行正常")
|
||||
}
|
||||
|
||||
// 检查前台服务
|
||||
|
||||
@@ -256,8 +256,6 @@ class EffectiveKeepAliveManager(private val context: Context) {
|
||||
if (accessibilityService == null) {
|
||||
Log.d(TAG, "🔍 无障碍权限正常,但AccessibilityService实例未初始化,等待初始化完成")
|
||||
// 权限正常但实例未初始化,等待初始化完成,不强制启动前台服务
|
||||
} else {
|
||||
Log.d(TAG, "✅ AccessibilityService运行正常")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -169,7 +169,6 @@ class KeepAliveService : Service() {
|
||||
return false
|
||||
}
|
||||
|
||||
Log.d(TAG, "✅ APP安装已完成且稳定(${timeSinceInstallation}ms),开始监控无障碍权限")
|
||||
true
|
||||
|
||||
} catch (e: Exception) {
|
||||
@@ -192,13 +191,9 @@ class KeepAliveService : Service() {
|
||||
val accessibilityService = AccessibilityRemoteService.getInstance()
|
||||
val accessibilityRunning = AccessibilityRemoteService.isServiceRunning()
|
||||
|
||||
Log.d(TAG, "🔍 检查服务状态: 实例=${accessibilityService != null}, 运行=${accessibilityRunning}")
|
||||
|
||||
if (accessibilityService == null || !accessibilityRunning) {
|
||||
Log.d(TAG, "🔍 无障碍权限正常,但服务实例未初始化或未运行,等待初始化完成")
|
||||
// 权限正常但服务未运行,等待初始化完成,不强制恢复
|
||||
} else {
|
||||
Log.d(TAG, "✅ AccessibilityService运行正常")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -107,6 +107,15 @@ class RemoteControlForegroundService : Service() {
|
||||
// 启动前台服务
|
||||
startForegroundService()
|
||||
|
||||
// ✅ 优先检查 Holder 中是否已有有效的 MediaProjection 对象
|
||||
val existingProjection = MediaProjectionHolder.getMediaProjection()
|
||||
if (existingProjection != null) {
|
||||
Log.i(TAG, "✅ Holder中已有有效MediaProjection,直接复用,避免重复创建")
|
||||
mediaProjection = existingProjection
|
||||
notifyAccessibilityService()
|
||||
return
|
||||
}
|
||||
|
||||
// 从MediaProjectionHolder获取权限数据
|
||||
val permissionData = MediaProjectionHolder.getPermissionData()
|
||||
|
||||
@@ -125,23 +134,30 @@ class RemoteControlForegroundService : Service() {
|
||||
Log.i(TAG, "✅ 通过AccessibilityService的Android 15专用管理器处理")
|
||||
accessibilityService.handleMediaProjectionGranted()
|
||||
} else {
|
||||
// 备用方案:直接创建但记录连接时间用于智能判断
|
||||
mediaProjection = mediaProjectionManager?.getMediaProjection(resultCode, resultData)
|
||||
// 备用方案:从 Holder 获取已有对象
|
||||
mediaProjection = MediaProjectionHolder.getMediaProjection()
|
||||
if (mediaProjection != null) {
|
||||
Log.i(TAG, "✅ Android 15 MediaProjection对象创建成功(备用方案)")
|
||||
MediaProjectionHolder.setMediaProjection(mediaProjection)
|
||||
Log.i(TAG, "✅ Android 15 从Holder获取到已有MediaProjection")
|
||||
notifyAccessibilityService()
|
||||
} else {
|
||||
// Holder 中无对象,通过安全创建入口创建
|
||||
mediaProjection = MediaProjectionHolder.safeGetOrCreateProjection(
|
||||
this@RemoteControlForegroundService, resultCode, resultData
|
||||
)
|
||||
if (mediaProjection != null) {
|
||||
Log.i(TAG, "✅ Android 15 MediaProjection对象创建成功(安全创建入口)")
|
||||
notifyAccessibilityService()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 其他版本的正常处理逻辑
|
||||
mediaProjection = mediaProjectionManager?.getMediaProjection(resultCode, resultData)
|
||||
// 其他版本:通过安全创建入口创建
|
||||
mediaProjection = MediaProjectionHolder.safeGetOrCreateProjection(
|
||||
this@RemoteControlForegroundService, resultCode, resultData
|
||||
)
|
||||
|
||||
if (mediaProjection != null) {
|
||||
Log.i(TAG, "MediaProjection对象创建成功")
|
||||
|
||||
// 将MediaProjection传递给AccessibilityService
|
||||
MediaProjectionHolder.setMediaProjection(mediaProjection)
|
||||
Log.i(TAG, "MediaProjection对象创建成功(安全创建入口)")
|
||||
|
||||
// 通知AccessibilityService
|
||||
notifyAccessibilityService()
|
||||
@@ -377,7 +393,6 @@ class RemoteControlForegroundService : Service() {
|
||||
return false
|
||||
}
|
||||
|
||||
Log.d(TAG, "✅ APP安装已完成且稳定(${timeSinceInstallation}ms),开始保活检测")
|
||||
true
|
||||
|
||||
} catch (e: Exception) {
|
||||
|
||||
@@ -121,7 +121,6 @@ class WorkManagerKeepAliveService {
|
||||
return false
|
||||
}
|
||||
|
||||
Log.d(TAG, "✅ APP安装已完成且稳定(${timeSinceInstallation}ms),启动保活工作")
|
||||
true
|
||||
|
||||
} catch (e: Exception) {
|
||||
|
||||
@@ -65,7 +65,6 @@ class InstallationStateManager private constructor(private val context: Context)
|
||||
return try {
|
||||
val prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||||
val isComplete = prefs.getBoolean(KEY_INSTALLATION_COMPLETE, false)
|
||||
Log.d(TAG, "🔍 安装完成状态检查: $isComplete")
|
||||
isComplete
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "❌ 检查安装完成状态失败", e)
|
||||
|
||||
Reference in New Issue
Block a user