870 lines
34 KiB
Kotlin
870 lines
34 KiB
Kotlin
package com.hikoncont.manager
|
||
|
||
import android.content.Context
|
||
import android.content.Intent
|
||
import android.hardware.display.DisplayManager
|
||
import android.media.projection.MediaProjection
|
||
import android.media.projection.MediaProjectionManager
|
||
import android.os.Build
|
||
import android.os.Handler
|
||
import android.os.Looper
|
||
import android.util.Log
|
||
import androidx.annotation.RequiresApi
|
||
import com.hikoncont.MediaProjectionHolder
|
||
import kotlinx.coroutines.*
|
||
|
||
/**
|
||
* Android 15专用的MediaProjection管理器
|
||
*
|
||
* Android 15引入了更严格的MediaProjection管理:
|
||
* 1. 状态栏芯片允许用户随时停止屏幕共享
|
||
* 2. 锁屏时自动停止MediaProjection
|
||
* 3. MediaProjection.Callback.onStop()在权限被系统停止时调用
|
||
* 4. 每个权限会话只能调用一次createVirtualDisplay()
|
||
* 5. Intent只能使用一次,不能重复传递给getMediaProjection()
|
||
* 6. 每次都需要用户同意
|
||
*/
|
||
@RequiresApi(35) // Android 15 = API 35
|
||
class Android15MediaProjectionManager(
|
||
private val context: Context,
|
||
private val onPermissionLost: () -> Unit,
|
||
private val onPermissionRecovered: () -> Unit
|
||
) {
|
||
|
||
companion object {
|
||
private const val TAG = "Android15MediaProjection"
|
||
private const val MAX_RECOVERY_ATTEMPTS = 3
|
||
private const val RECOVERY_DELAY_MS = 2000L
|
||
private const val MIN_RECOVERY_INTERVAL = 60000L // 最小恢复间隔:60秒
|
||
|
||
// ✅ 新增:权限稳定期配置
|
||
private const val PERMISSION_STABLE_PERIOD = 45000L // 45秒稳定期
|
||
private const val KEEPALIVE_CHECK_THRESHOLD = 10000L // 10秒内视为保活检查
|
||
private const val RECOVERY_COOLDOWN_PERIOD = 120000L // 2分钟恢复冷却期
|
||
private const val MAX_CONSECUTIVE_STOPS = 3 // 最大连续停止次数
|
||
}
|
||
|
||
private var mediaProjection: MediaProjection? = null
|
||
private var mediaProjectionManager: MediaProjectionManager? = null
|
||
private var recoveryAttempts = 0
|
||
private var isRecovering = false
|
||
private var lastRecoveryTime = 0L // 上次权限恢复时间
|
||
private val recoveryScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||
|
||
// ✅ Android 15新增:Session管理
|
||
private var currentSessionId: String? = null
|
||
private var sessionUsed = false
|
||
private var lastIntentData: Intent? = null
|
||
private var lastResultCode: Int? = null
|
||
|
||
// 连接开始时间记录
|
||
private var connectionStartTime = 0L
|
||
|
||
// ✅ 权限稳定性管理 - 核心修复
|
||
private var isPermissionStable = false
|
||
private var stopAllMonitoring = false
|
||
private var permissionGrantedTime = 0L // 权限获取时间
|
||
private var consecutiveStopCount = 0 // 连续停止计数
|
||
private var lastStopTime = 0L // 上次停止时间
|
||
private var isInStablePeriod = false // 是否在稳定期内
|
||
|
||
// 防重复发送权限稳定广播的标记
|
||
@Volatile private var stableBroadcastSent = false
|
||
|
||
/**
|
||
* Android 15 MediaProjection停止回调 - 优化版本
|
||
*
|
||
* 核心修复:
|
||
* 1. 增加权限稳定期检测,避免频繁触发恢复
|
||
* 2. 改进保活检查识别逻辑
|
||
* 3. 添加连续停止计数,防止无限循环
|
||
* 4. 实现渐进式恢复策略
|
||
*/
|
||
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
|
||
|
||
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)
|
||
return
|
||
}
|
||
|
||
// ✅ 核心修复2:检测无限循环并强制停止
|
||
if (consecutiveStopCount > MAX_CONSECUTIVE_STOPS && timeSinceLastStop < 30000) {
|
||
Log.w(TAG, "⚠️ 检测到可能的无限循环:连续${consecutiveStopCount}次停止,强制标记为稳定")
|
||
forceMarkAsStable()
|
||
return
|
||
}
|
||
|
||
// ✅ 核心修复3:如果权限已稳定,停止处理
|
||
if (isPermissionStable || stopAllMonitoring) {
|
||
Log.i(TAG, "🛡️ 权限已稳定或停止监听,跳过onStop处理")
|
||
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)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* ✅ 新增:处理系统保活检查
|
||
*/
|
||
private fun handleKeepAliveCheck(connectionTime: Long, timeSinceLastStop: Long) {
|
||
Log.i(TAG, "🛡️ 处理系统保活检查 - 连接时长: ${connectionTime}ms, 距上次: ${timeSinceLastStop}ms")
|
||
|
||
// 只清理本地MediaProjection引用,不触发任何恢复机制
|
||
mediaProjection?.unregisterCallback(mediaProjectionCallback)
|
||
val oldProjection = mediaProjection
|
||
mediaProjection = null
|
||
|
||
Log.d(TAG, "🧹 保活检查:仅清理本地引用 ${oldProjection?.hashCode()} -> null")
|
||
|
||
// 🚨 关键:不清理MediaProjectionHolder中的数据
|
||
// 🚨 关键:不启动任何恢复机制
|
||
// 🚨 关键:不调用onPermissionLost()
|
||
|
||
logPermissionState("保活检查处理完成")
|
||
|
||
// 🛡️ 核心修复:完全跳过恢复检查,避免触发权限弹窗
|
||
Log.i(TAG, "🛡️ [权限保活] 保活检查处理完成,完全跳过恢复机制避免权限弹窗")
|
||
|
||
// 不再进行任何恢复检查或操作
|
||
// 注释掉原来的恢复检查代码,避免触发权限弹窗
|
||
/*
|
||
// 短暂延迟后检查是否需要静默恢复
|
||
recoveryScope.launch {
|
||
delay(2000) // 等待2秒,让系统完成保活检查
|
||
|
||
// 只有在真的需要时才恢复
|
||
val hasPermissionData = MediaProjectionHolder.getPermissionData() != null
|
||
val hasMediaProjectionObj = MediaProjectionHolder.getMediaProjection() != null
|
||
|
||
if (hasPermissionData && !hasMediaProjectionObj && mediaProjection == null) {
|
||
Log.d(TAG, "🔄 保活检查后静默恢复MediaProjection对象")
|
||
attemptSilentRecovery()
|
||
} else {
|
||
Log.d(TAG, "✅ 保活检查后无需恢复,状态正常")
|
||
}
|
||
}
|
||
*/
|
||
}
|
||
|
||
/**
|
||
* ✅ 新增:处理连续快速停止
|
||
*/
|
||
private fun handleRapidConsecutiveStops() {
|
||
Log.w(TAG, "🔄 处理连续快速停止,可能存在权限冲突")
|
||
|
||
// 强制进入冷却期
|
||
lastRecoveryTime = System.currentTimeMillis()
|
||
|
||
// 清理本地状态
|
||
mediaProjection?.unregisterCallback(mediaProjectionCallback)
|
||
mediaProjection = null
|
||
|
||
Log.i(TAG, "❄️ 进入强制冷却期,2分钟内不进行任何恢复尝试")
|
||
|
||
// 延迟检查权限状态
|
||
recoveryScope.launch {
|
||
delay(5000) // 等待5秒
|
||
|
||
val permissionData = MediaProjectionHolder.getPermissionData()
|
||
if (permissionData != null) {
|
||
Log.i(TAG, "🛡️ 权限数据仍然存在,可能只是临时冲突")
|
||
// 不立即恢复,等待冷却期结束
|
||
} else {
|
||
Log.w(TAG, "❌ 权限数据丢失,可能是真正的权限问题")
|
||
// 也不立即恢复,避免无限循环
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* ✅ 新增:强制标记为稳定状态
|
||
*/
|
||
private fun forceMarkAsStable() {
|
||
Log.w(TAG, "🛡️ 强制标记权限为稳定状态,停止所有监听和恢复")
|
||
|
||
isPermissionStable = true
|
||
stopAllMonitoring = true
|
||
isInStablePeriod = true
|
||
|
||
// 停止所有恢复协程
|
||
try {
|
||
recoveryScope.cancel()
|
||
} catch (e: Exception) {
|
||
Log.w(TAG, "停止恢复协程失败", e)
|
||
}
|
||
|
||
// 清理回调
|
||
mediaProjection?.unregisterCallback(mediaProjectionCallback)
|
||
|
||
// 重置计数器
|
||
consecutiveStopCount = 0
|
||
|
||
Log.i(TAG, "✅ 已强制标记为稳定,所有恢复机制已停止")
|
||
}
|
||
|
||
/**
|
||
* ✅ 新增:处理疑似权限丢失
|
||
*/
|
||
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()
|
||
}
|
||
|
||
/**
|
||
* ✅ 改进:判断MediaProjection停止原因 - 更精确的判断逻辑
|
||
*/
|
||
private fun determineStopReason(stopTime: Long, connectionDuration: Long, timeSinceLastStop: Long): String {
|
||
val hasPermissionData = MediaProjectionHolder.getPermissionData() != null
|
||
|
||
return when {
|
||
// 连续快速停止,可能是权限冲突
|
||
consecutiveStopCount >= 3 && timeSinceLastStop < 5000 -> {
|
||
Log.w(TAG, "🔄 检测到连续快速停止(${consecutiveStopCount}次),间隔${timeSinceLastStop}ms")
|
||
"RAPID_CONSECUTIVE_STOPS"
|
||
}
|
||
|
||
// 系统保活检查 - 更严格的判断条件
|
||
connectionDuration < KEEPALIVE_CHECK_THRESHOLD && hasPermissionData && timeSinceLastStop > 1000 -> {
|
||
Log.i(TAG, "⚡ 系统保活检查:连接时长${connectionDuration}ms < ${KEEPALIVE_CHECK_THRESHOLD}ms")
|
||
"SYSTEM_KEEPALIVE_CHECK"
|
||
}
|
||
|
||
// 用户主动停止(状态栏点击)
|
||
connectionDuration > 30000 && hasPermissionData -> {
|
||
Log.i(TAG, "🔴 用户主动停止:连接时长${connectionDuration}ms > 30s")
|
||
"USER_STOPPED_VIA_STATUS_BAR"
|
||
}
|
||
|
||
// 锁屏自动终止
|
||
connectionDuration > 5000 && connectionDuration < 60000 && hasPermissionData -> {
|
||
Log.i(TAG, "🔒 可能的锁屏停止:连接时长${connectionDuration}ms在5-60秒之间")
|
||
"DEVICE_LOCKED"
|
||
}
|
||
|
||
// 权限数据丢失
|
||
!hasPermissionData -> {
|
||
Log.w(TAG, "🚫 权限数据丢失")
|
||
"PERMISSION_LOST"
|
||
}
|
||
|
||
else -> {
|
||
Log.w(TAG, "❓ 未知停止原因:连接${connectionDuration}ms,权限存在=$hasPermissionData")
|
||
"UNKNOWN"
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 权限状态详细日志
|
||
*/
|
||
private fun logPermissionState(context: String) {
|
||
val permissionData = MediaProjectionHolder.getPermissionData()
|
||
val mediaProjectionObj = MediaProjectionHolder.getMediaProjection()
|
||
val permissionStats = MediaProjectionHolder.getPermissionStats()
|
||
val localProjection = mediaProjection
|
||
|
||
Log.d(TAG, """
|
||
📊📊📊 权限状态检查 [$context] 📊📊📊
|
||
🔧 本地Manager状态:
|
||
- isPermissionStable: $isPermissionStable
|
||
- stopAllMonitoring: $stopAllMonitoring
|
||
- isRecovering: $isRecovering
|
||
- recoveryAttempts: $recoveryAttempts
|
||
- 本地MediaProjection: ${localProjection?.hashCode()}
|
||
🗂️ MediaProjectionHolder状态:
|
||
- 权限数据存在: ${permissionData != null}
|
||
- MediaProjection对象: ${mediaProjectionObj?.hashCode()}
|
||
- 数据有效性: ${MediaProjectionHolder.isPermissionDataValid()}
|
||
- 权限统计: $permissionStats
|
||
⏰ 时间信息:
|
||
- connectionStartTime: $connectionStartTime
|
||
- 当前时间: ${System.currentTimeMillis()}
|
||
- 连接时长: ${System.currentTimeMillis() - connectionStartTime}ms
|
||
""".trimIndent())
|
||
}
|
||
|
||
/**
|
||
* ✅ 新增:渐进式恢复机制 - 替代激进的智能恢复
|
||
*/
|
||
private fun startProgressiveRecovery() {
|
||
Log.i(TAG, "🔄 启动渐进式权限恢复")
|
||
logPermissionState("渐进式恢复开始")
|
||
|
||
// 🛡️ 核心修复:检查是否为权限保活状态,避免在保活时启动恢复
|
||
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 // 重置停止计数
|
||
onPermissionRecovered()
|
||
return@launch
|
||
}
|
||
|
||
// 第四阶段:延迟后再次尝试
|
||
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
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* ✅ 新增:处理恢复失败
|
||
*/
|
||
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()
|
||
}
|
||
|
||
/**
|
||
* ✅ 静默权限恢复(改进版本)
|
||
*/
|
||
private suspend fun attemptSilentRecovery(): Boolean {
|
||
return try {
|
||
Log.i(TAG, "🤫 尝试静默权限恢复")
|
||
logPermissionState("静默恢复开始")
|
||
|
||
// 🛡️ 核心修复:检查是否为权限保活状态,避免在保活时进行恢复
|
||
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()}")
|
||
|
||
// 更新连接时间
|
||
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
|
||
}
|
||
|
||
} catch (e: Exception) {
|
||
Log.e(TAG, "❌ 静默恢复异常", e)
|
||
return false
|
||
}
|
||
}
|
||
|
||
/**
|
||
* ✅ 改进:用户主动停止处理
|
||
*/
|
||
private fun handleUserStoppedSharing() {
|
||
Log.i(TAG, "🔴 处理用户主动停止屏幕共享")
|
||
|
||
// 标记为稳定状态,停止所有恢复
|
||
isPermissionStable = true
|
||
stopAllMonitoring = true
|
||
|
||
// 清理MediaProjection对象
|
||
mediaProjection?.unregisterCallback(mediaProjectionCallback)
|
||
mediaProjection = null
|
||
|
||
// 停止恢复协程
|
||
try {
|
||
recoveryScope.cancel()
|
||
} catch (e: Exception) {
|
||
Log.w(TAG, "停止恢复协程失败", e)
|
||
}
|
||
|
||
// 通知屏幕捕获管理器停止
|
||
onPermissionLost()
|
||
|
||
Log.i(TAG, "✅ 用户主动停止处理完成,已标记为稳定状态")
|
||
}
|
||
|
||
/**
|
||
* ✅ 改进:设备锁屏处理
|
||
*/
|
||
private fun handleDeviceLocked() {
|
||
Log.i(TAG, "🔒 处理设备锁屏导致的屏幕共享停止")
|
||
|
||
// 清理MediaProjection对象但保留权限数据
|
||
mediaProjection?.unregisterCallback(mediaProjectionCallback)
|
||
mediaProjection = null
|
||
|
||
// 暂停屏幕捕获但不标记为稳定(解锁后可恢复)
|
||
onPermissionLost()
|
||
|
||
Log.i(TAG, "✅ 设备锁屏处理完成,权限数据保留待解锁恢复")
|
||
|
||
// 可以考虑监听解锁广播来自动恢复
|
||
// 这里暂不实现,等待用户主动操作
|
||
}
|
||
|
||
/**
|
||
* ✅ 新增:设置权限获取时间
|
||
*/
|
||
fun setPermissionGrantedTime() {
|
||
permissionGrantedTime = System.currentTimeMillis()
|
||
isInStablePeriod = true
|
||
consecutiveStopCount = 0
|
||
lastStopTime = 0L
|
||
|
||
Log.i(TAG, "🛡️ 权限获取时间已设置: $permissionGrantedTime,进入稳定期")
|
||
|
||
// 在稳定期结束后自动标记为稳定
|
||
recoveryScope.launch {
|
||
delay(PERMISSION_STABLE_PERIOD)
|
||
if (!isPermissionStable && !stopAllMonitoring) {
|
||
Log.i(TAG, "✅ 权限稳定期结束,标记为稳定状态")
|
||
isPermissionStable = true
|
||
isInStablePeriod = false
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* ✅ 新增:重置权限状态(用于重新开始权限申请)
|
||
*/
|
||
fun resetPermissionState() {
|
||
Log.i(TAG, "🔄 重置Android 15权限状态")
|
||
isPermissionStable = false
|
||
stopAllMonitoring = false
|
||
isInStablePeriod = false
|
||
recoveryAttempts = 0
|
||
isRecovering = false
|
||
consecutiveStopCount = 0
|
||
lastStopTime = 0L
|
||
permissionGrantedTime = 0L
|
||
lastRecoveryTime = 0L
|
||
}
|
||
|
||
/**
|
||
* ✅ 改进:智能恢复策略 - 避免频繁重新授权
|
||
*/
|
||
private fun triggerPermissionReRequest() {
|
||
try {
|
||
// 🛡️ 核心修复:检查是否为权限保活状态,避免在保活时触发权限重新申请
|
||
val hasPermissionData = MediaProjectionHolder.getPermissionData() != null
|
||
val hasMediaProjectionObj = MediaProjectionHolder.getMediaProjection() != null
|
||
|
||
if (hasPermissionData && hasMediaProjectionObj) {
|
||
Log.i(TAG, "🛡️ [权限保活] 检测到权限和对象都存在,这可能是保活检查,跳过权限重新申请避免弹窗")
|
||
Log.i(TAG, "🛡️ [权限保活] 权限数据存在: $hasPermissionData, MediaProjection对象存在: $hasMediaProjectionObj")
|
||
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)
|
||
putExtra("ANDROID_15_RECOVERY", true)
|
||
putExtra("PERMISSION_LOST_RECOVERY", true)
|
||
}
|
||
context.startActivity(intent)
|
||
|
||
Log.i(TAG, "✅ 已启动MediaProjection权限重新申请")
|
||
|
||
} catch (e: Exception) {
|
||
Log.e(TAG, "❌ 触发权限重新申请失败", e)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* ✅ 新增:创建MediaProjection并注册回调
|
||
*/
|
||
fun createMediaProjectionWithCallback(resultCode: Int, resultData: Intent): MediaProjection? {
|
||
return try {
|
||
Log.i(TAG, "🏭 创建MediaProjection并注册回调")
|
||
|
||
if (mediaProjectionManager == null) {
|
||
mediaProjectionManager = context.getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
|
||
}
|
||
|
||
val projection = mediaProjectionManager?.getMediaProjection(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创建失败")
|
||
return null
|
||
}
|
||
|
||
} catch (e: Exception) {
|
||
Log.e(TAG, "❌ 创建MediaProjection异常", e)
|
||
return null
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 初始化管理器
|
||
*/
|
||
fun initialize() {
|
||
Log.i(TAG, "🔧 初始化Android 15 MediaProjection管理器")
|
||
|
||
if (mediaProjectionManager == null) {
|
||
mediaProjectionManager = context.getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
|
||
}
|
||
|
||
Log.i(TAG, "✅ Android 15 MediaProjection管理器初始化完成")
|
||
}
|
||
|
||
/**
|
||
* 标记权限为稳定状态并停止所有监听
|
||
*/
|
||
fun markPermissionStable() {
|
||
Log.i(TAG, "🛡️ 手动标记Android 15权限为稳定状态")
|
||
isPermissionStable = true
|
||
stopAllMonitoring = true
|
||
isInStablePeriod = false
|
||
|
||
// 取消回调监听
|
||
mediaProjection?.unregisterCallback(mediaProjectionCallback)
|
||
|
||
// 取消恢复协程
|
||
try {
|
||
recoveryScope.cancel()
|
||
} catch (e: Exception) {
|
||
Log.w(TAG, "取消恢复协程失败", e)
|
||
}
|
||
|
||
Log.i(TAG, "✅ Android 15权限监听已停止")
|
||
}
|
||
|
||
/**
|
||
* 获取当前MediaProjection对象
|
||
*/
|
||
fun getMediaProjection(): MediaProjection? {
|
||
return mediaProjection
|
||
}
|
||
|
||
/**
|
||
* 检查MediaProjection是否有效
|
||
*/
|
||
fun isMediaProjectionValid(): Boolean {
|
||
return mediaProjection != null && !isRecovering
|
||
}
|
||
|
||
/**
|
||
* 检查权限是否稳定
|
||
*/
|
||
fun isPermissionStable(): Boolean {
|
||
return isPermissionStable
|
||
}
|
||
|
||
/**
|
||
* Android 15新增:标记session为已使用(在createVirtualDisplay时调用)
|
||
*/
|
||
fun markSessionUsed() {
|
||
sessionUsed = true
|
||
Log.i(TAG, "🔒 Android 15 session已标记为使用: $currentSessionId")
|
||
}
|
||
|
||
/**
|
||
* Android 15新增:检查session是否已被使用
|
||
*/
|
||
fun isSessionUsed(): Boolean {
|
||
return sessionUsed
|
||
}
|
||
|
||
/**
|
||
* 手动停止MediaProjection(用户主动停止)
|
||
*/
|
||
fun stopMediaProjection() {
|
||
try {
|
||
Log.i(TAG, "🛑 用户主动停止MediaProjection")
|
||
|
||
// 标记为稳定状态
|
||
isPermissionStable = true
|
||
stopAllMonitoring = true
|
||
|
||
mediaProjection?.let { projection ->
|
||
projection.unregisterCallback(mediaProjectionCallback)
|
||
projection.stop()
|
||
}
|
||
|
||
mediaProjection = null
|
||
|
||
// 停止恢复协程
|
||
try {
|
||
recoveryScope.cancel()
|
||
} catch (e: Exception) {
|
||
Log.w(TAG, "停止恢复协程失败", e)
|
||
}
|
||
|
||
Log.i(TAG, "✅ MediaProjection已手动停止")
|
||
|
||
} catch (e: Exception) {
|
||
Log.e(TAG, "❌ 手动停止MediaProjection失败", e)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 销毁管理器
|
||
*/
|
||
fun destroy() {
|
||
try {
|
||
Log.i(TAG, "🧹 销毁Android 15 MediaProjection管理器")
|
||
|
||
// 停止所有协程
|
||
recoveryScope.cancel()
|
||
|
||
// 清理MediaProjection
|
||
mediaProjection?.unregisterCallback(mediaProjectionCallback)
|
||
mediaProjection?.stop()
|
||
mediaProjection = null
|
||
mediaProjectionManager = null
|
||
|
||
Log.i(TAG, "✅ Android 15 MediaProjection管理器已销毁")
|
||
|
||
} catch (e: Exception) {
|
||
Log.e(TAG, "❌ 销毁管理器失败", e)
|
||
}
|
||
}
|
||
} |