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

870 lines
34 KiB
Kotlin
Raw Normal View History

2026-02-11 16:59:49 +08:00
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)
}
}
}