Files
and-bak/app/src/main/java/com/hikoncont/manager/Android15MediaProjectionManager.kt
2026-02-11 16:59:49 +08:00

870 lines
34 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}
}
}