package com.hikoncont.manager import android.content.Context import android.content.Intent import android.content.IntentFilter import android.content.BroadcastReceiver 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 com.hikoncont.MediaProjectionHolder import com.hikoncont.service.AccessibilityRemoteService import kotlinx.coroutines.* import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicInteger /** * 智能MediaProjection管理器 * * 专门解决Android 15投屏权限丢失问题: * 1. 智能区分用户主动停止和系统自动停止 * 2. 多层级权限保持策略 * 3. 非侵入式权限恢复机制 * 4. 向下兼容所有Android版本 */ class SmartMediaProjectionManager( private val context: Context ) { companion object { private const val TAG = "SmartMediaProjection" // 恢复策略配置 private const val MAX_SILENT_RECOVERY_ATTEMPTS = 5 // 最大静默恢复次数 private const val MAX_USER_RECOVERY_ATTEMPTS = 3 // 最大用户确认恢复次数 private const val SILENT_RECOVERY_DELAY = 3000L // 静默恢复延迟(3秒) private const val USER_RECOVERY_DELAY = 30000L // 用户恢复延迟(30秒) private const val PERMISSION_CHECK_INTERVAL = 10000L // 权限检查间隔(10秒) // Android版本特定配置 private const val ANDROID_15_MIN_RECOVERY_INTERVAL = 120000L // Android 15最小恢复间隔(2分钟) @Volatile private var instance: SmartMediaProjectionManager? = null fun getInstance(context: Context): SmartMediaProjectionManager { return instance ?: synchronized(this) { instance ?: SmartMediaProjectionManager(context.applicationContext).also { instance = it } } } } // 状态管理 private var mediaProjection: MediaProjection? = null private var mediaProjectionManager: MediaProjectionManager? = null private val isInitialized = AtomicBoolean(false) private val isRecovering = AtomicBoolean(false) private val silentRecoveryAttempts = AtomicInteger(0) private val userRecoveryAttempts = AtomicInteger(0) // 时间记录 @Volatile private var lastPermissionLostTime = 0L @Volatile private var lastRecoveryTime = 0L @Volatile private var lastUserInteractionTime = 0L // 协程管理 private val managerScope = CoroutineScope(Dispatchers.Main + SupervisorJob()) private var monitoringJob: Job? = null // 回调接口 interface PermissionStateListener { fun onPermissionLost(reason: LossReason) fun onPermissionRecovered() fun onPermissionFailedToRecover() } enum class LossReason { USER_STOPPED, // 用户主动停止 SYSTEM_LOCK_SCREEN, // 系统锁屏停止 SYSTEM_AUTO_STOP, // 系统其他原因停止 APP_BACKGROUNDED, // 应用后台化 UNKNOWN // 未知原因 } private val listeners = mutableSetOf() // 系统状态监听 private val systemStateReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { when (intent?.action) { Intent.ACTION_SCREEN_OFF -> { Log.i(TAG, "📱 设备锁屏,标记可能的权限丢失原因") lastUserInteractionTime = System.currentTimeMillis() } Intent.ACTION_USER_PRESENT -> { Log.i(TAG, "📱 用户解锁,检查权限状态") checkPermissionStatusAfterUnlock() } "android.mycustrecev.USER_STOPPED_PROJECTION" -> { Log.i(TAG, "👤 收到用户主动停止投屏信号") handleUserStoppedProjection() } } } } /** * 智能MediaProjection回调 * 能够智能判断权限丢失的原因 */ private val smartCallback = object : MediaProjection.Callback() { override fun onStop() { Log.w(TAG, "🛑 MediaProjection权限丢失") val currentTime = System.currentTimeMillis() lastPermissionLostTime = currentTime // 智能判断丢失原因 val lossReason = determineLossReason(currentTime) // 清理当前MediaProjection引用 cleanupCurrentProjection() // 通知监听器 notifyPermissionLost(lossReason) // 根据丢失原因选择恢复策略 selectRecoveryStrategy(lossReason) } } /** * 初始化智能管理器 */ fun initialize(): Boolean { return try { if (isInitialized.get()) { Log.d(TAG, "管理器已初始化") return true } Log.i(TAG, "🚀 初始化智能MediaProjection管理器") // 初始化MediaProjectionManager mediaProjectionManager = context.getSystemService(Context.MEDIA_PROJECTION_SERVICE) as? MediaProjectionManager if (mediaProjectionManager == null) { Log.e(TAG, "❌ 无法获取MediaProjectionManager") return false } // 注册系统状态监听 registerSystemStateReceiver() // 检查现有权限 checkAndRestoreExistingPermission() // 启动权限状态监控 startPermissionMonitoring() isInitialized.set(true) Log.i(TAG, "✅ 智能MediaProjection管理器初始化成功") true } catch (e: Exception) { Log.e(TAG, "❌ 初始化失败", e) false } } /** * 智能判断权限丢失原因 */ private fun determineLossReason(lossTime: Long): LossReason { return when { // 用户最近与状态栏交互(可能点击了停止按钮) lossTime - lastUserInteractionTime < 5000 -> { Log.i(TAG, "🤔 判断为用户主动停止(最近有用户交互)") LossReason.USER_STOPPED } // 锁屏相关的权限丢失 lossTime - lastUserInteractionTime < 2000 -> { Log.i(TAG, "🔒 判断为系统锁屏自动停止") LossReason.SYSTEM_LOCK_SCREEN } // Android 15特有的系统自动停止 Build.VERSION.SDK_INT >= 35 -> { Log.i(TAG, "🤖 Android 15系统自动停止") LossReason.SYSTEM_AUTO_STOP } else -> { Log.i(TAG, "❓ 未知原因的权限丢失") LossReason.UNKNOWN } } } /** * 根据丢失原因选择恢复策略 */ private fun selectRecoveryStrategy(reason: LossReason) { when (reason) { LossReason.USER_STOPPED -> { Log.i(TAG, "👤 用户主动停止,不进行自动恢复") // 用户主动停止,重置恢复计数器但不自动恢复 resetRecoveryCounters() } LossReason.SYSTEM_LOCK_SCREEN -> { Log.i(TAG, "🔒 锁屏导致的权限丢失,等待解锁后恢复") // 等待用户解锁后再恢复 scheduleUnlockRecovery() } LossReason.SYSTEM_AUTO_STOP, LossReason.APP_BACKGROUNDED, LossReason.UNKNOWN -> { Log.i(TAG, "🤖 系统自动停止,启动智能恢复") // 系统原因,启动智能恢复 startSmartRecovery() } } } /** * 启动智能恢复机制 */ private fun startSmartRecovery() { if (isRecovering.get()) { Log.d(TAG, "🔄 恢复进程已在进行中") return } managerScope.launch { try { isRecovering.set(true) // 首先尝试静默恢复 if (attemptSilentRecovery()) { Log.i(TAG, "✅ 静默恢复成功") return@launch } // 静默恢复失败,根据情况决定是否提示用户 if (shouldPromptUserRecovery()) { scheduleUserPromptedRecovery() } else { Log.i(TAG, "🤐 不满足用户提示条件,等待下次机会") } } catch (e: Exception) { Log.e(TAG, "❌ 智能恢复失败", e) } finally { isRecovering.set(false) } } } /** * 尝试静默恢复 * * 🚨 核心修复:优先检查 Holder 中是否已有有效对象, * 避免重复调用 getMediaProjection() 创建新实例导致旧实例被 stop */ private suspend fun attemptSilentRecovery(): Boolean { val currentAttempts = silentRecoveryAttempts.get() if (currentAttempts >= MAX_SILENT_RECOVERY_ATTEMPTS) { Log.w(TAG, "⚠️ 已达到最大静默恢复次数($MAX_SILENT_RECOVERY_ATTEMPTS)") return false } silentRecoveryAttempts.incrementAndGet() Log.i(TAG, "🤫 尝试静默恢复 (${silentRecoveryAttempts.get()}/$MAX_SILENT_RECOVERY_ATTEMPTS)") try { // 延迟恢复,避免与系统操作冲突 delay(SILENT_RECOVERY_DELAY) // ✅ 优先检查 Holder 中是否已有有效的 MediaProjection 对象 val existingProjection = MediaProjectionHolder.getMediaProjection() if (existingProjection != null) { Log.i(TAG, "✅ Holder中已有有效MediaProjection,直接复用") mediaProjection = existingProjection notifyPermissionRecovered() resetRecoveryCounters() return true } // Holder 中无有效对象,尝试从权限数据重新创建(仅一次) 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, "✅ 静默恢复成功") notifyPermissionRecovered() resetRecoveryCounters() return true } } } Log.w(TAG, "❌ 静默恢复失败,权限数据不可用") return false } catch (e: Exception) { Log.e(TAG, "❌ 静默恢复异常", e) return false } } /** * 安全地创建MediaProjection */ private fun createMediaProjectionSafely(resultCode: Int, resultData: Intent): MediaProjection? { return try { val projection = mediaProjectionManager?.getMediaProjection(resultCode, resultData) if (projection != null) { // 注册智能回调 projection.registerCallback(smartCallback, Handler(Looper.getMainLooper())) mediaProjection = projection MediaProjectionHolder.setMediaProjection(projection) Log.i(TAG, "✅ MediaProjection创建成功") } projection } catch (e: Exception) { Log.e(TAG, "❌ 创建MediaProjection失败", e) null } } /** * 判断是否应该提示用户恢复 */ private fun shouldPromptUserRecovery(): Boolean { val currentTime = System.currentTimeMillis() // 检查用户恢复次数 if (userRecoveryAttempts.get() >= MAX_USER_RECOVERY_ATTEMPTS) { Log.w(TAG, "⚠️ 已达到最大用户恢复次数") return false } // 检查最小恢复间隔(Android 15需要更长间隔) val minInterval = if (Build.VERSION.SDK_INT >= 35) { ANDROID_15_MIN_RECOVERY_INTERVAL } else { USER_RECOVERY_DELAY } if (currentTime - lastRecoveryTime < minInterval) { Log.d(TAG, "🕐 未达到最小恢复间隔") return false } // 检查是否有正在运行的AccessibilityService if (!AccessibilityRemoteService.isServiceRunning()) { Log.w(TAG, "⚠️ AccessibilityService未运行,不提示用户恢复") return false } return true } /** * 安排用户提示恢复 */ private fun scheduleUserPromptedRecovery() { managerScope.launch { try { delay(USER_RECOVERY_DELAY) userRecoveryAttempts.incrementAndGet() lastRecoveryTime = System.currentTimeMillis() Log.i(TAG, "🔔 启动用户提示恢复") // 发送恢复请求广播(非侵入式) val intent = Intent("android.mycustrecev.SMART_PERMISSION_RECOVERY").apply { putExtra("recovery_type", "user_prompted") putExtra("attempt", userRecoveryAttempts.get()) putExtra("max_attempts", MAX_USER_RECOVERY_ATTEMPTS) } context.sendBroadcast(intent) } catch (e: Exception) { Log.e(TAG, "❌ 用户提示恢复失败", e) } } } /** * 处理解锁后的权限检查 */ private fun checkPermissionStatusAfterUnlock() { managerScope.launch { try { delay(2000) // 等待系统稳定 if (mediaProjection == null) { Log.i(TAG, "🔓 解锁后检测到权限丢失,启动恢复") startSmartRecovery() } } catch (e: Exception) { Log.e(TAG, "❌ 解锁后权限检查失败", e) } } } /** * 安排解锁恢复 */ private fun scheduleUnlockRecovery() { Log.i(TAG, "⏰ 安排解锁后恢复") // 解锁恢复会在系统状态监听器中触发 } /** * 启动权限状态监控 */ private fun startPermissionMonitoring() { monitoringJob?.cancel() monitoringJob = managerScope.launch { while (isActive) { try { delay(PERMISSION_CHECK_INTERVAL) // 检查MediaProjection是否仍然有效 val currentProjection = mediaProjection if (currentProjection != null) { // 执行轻量级状态检查 try { // 这里可以添加一些检查逻辑来验证MediaProjection是否仍然有效 Log.v(TAG, "📊 权限状态检查:正常") } catch (e: Exception) { Log.w(TAG, "⚠️ 权限状态检查异常,可能已失效", e) if (!isRecovering.get()) { startSmartRecovery() } } } } catch (e: Exception) { Log.e(TAG, "❌ 权限监控异常", e) } } } } /** * 注册系统状态监听 */ private fun registerSystemStateReceiver() { val filter = IntentFilter().apply { addAction(Intent.ACTION_SCREEN_OFF) addAction(Intent.ACTION_USER_PRESENT) addAction("android.mycustrecev.USER_STOPPED_PROJECTION") } context.registerReceiver(systemStateReceiver, filter) Log.i(TAG, "✅ 已注册系统状态监听") } /** * 检查并恢复现有权限 */ 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, "✅ 现有权限恢复成功") notifyPermissionRecovered() } } } } catch (e: Exception) { Log.e(TAG, "❌ 检查现有权限失败", e) } } /** * 处理用户主动停止投屏 */ private fun handleUserStoppedProjection() { Log.i(TAG, "👤 处理用户主动停止投屏") lastUserInteractionTime = System.currentTimeMillis() // 停止所有恢复机制 isRecovering.set(false) resetRecoveryCounters() // 清理MediaProjection cleanupCurrentProjection() } /** * 清理当前MediaProjection */ private fun cleanupCurrentProjection() { try { mediaProjection?.unregisterCallback(smartCallback) mediaProjection = null // 🚨 重要修复:Android 15设备不要清理权限数据! if (android.os.Build.VERSION.SDK_INT >= 35) { Log.i(TAG, "🛡️ Android 15设备:仅清理MediaProjection引用,保留权限数据") // 不清理权限数据,避免Android 15权限丢失 } else { // 其他版本可以选择性清理,但建议保留 MediaProjectionHolder.clearMediaProjection() Log.i(TAG, "MediaProjection引用和权限数据已清理") } } catch (e: Exception) { Log.e(TAG, "❌ 清理MediaProjection失败", e) } } /** * 重置恢复计数器 */ private fun resetRecoveryCounters() { silentRecoveryAttempts.set(0) userRecoveryAttempts.set(0) Log.i(TAG, "🔄 恢复计数器已重置") } /** * 添加权限状态监听器 */ fun addPermissionStateListener(listener: PermissionStateListener) { listeners.add(listener) } /** * 移除权限状态监听器 */ fun removePermissionStateListener(listener: PermissionStateListener) { listeners.remove(listener) } /** * 通知权限丢失 */ private fun notifyPermissionLost(reason: LossReason) { listeners.forEach { listener -> try { listener.onPermissionLost(reason) } catch (e: Exception) { Log.e(TAG, "❌ 通知权限丢失失败", e) } } } /** * 通知权限恢复 */ private fun notifyPermissionRecovered() { listeners.forEach { listener -> try { listener.onPermissionRecovered() } catch (e: Exception) { Log.e(TAG, "❌ 通知权限恢复失败", e) } } } /** * 获取当前MediaProjection */ fun getCurrentMediaProjection(): MediaProjection? { return mediaProjection } /** * 手动设置新的MediaProjection */ fun setMediaProjection(resultCode: Int, resultData: Intent): Boolean { return try { val projection = createMediaProjectionSafely(resultCode, resultData) if (projection != null) { resetRecoveryCounters() notifyPermissionRecovered() true } else { false } } catch (e: Exception) { Log.e(TAG, "❌ 设置MediaProjection失败", e) false } } /** * 手动停止MediaProjection */ fun stopMediaProjection() { try { Log.i(TAG, "🛑 用户手动停止MediaProjection") // 发送用户停止信号 val intent = Intent("android.mycustrecev.USER_STOPPED_PROJECTION") context.sendBroadcast(intent) // 清理资源 mediaProjection?.stop() cleanupCurrentProjection() } catch (e: Exception) { Log.e(TAG, "❌ 停止MediaProjection失败", e) } } /** * 获取管理器状态信息 */ fun getStatusInfo(): Map { return mapOf( "initialized" to isInitialized.get(), "hasPermission" to (mediaProjection != null), "isRecovering" to isRecovering.get(), "silentRecoveryAttempts" to silentRecoveryAttempts.get(), "userRecoveryAttempts" to userRecoveryAttempts.get(), "lastPermissionLostTime" to lastPermissionLostTime, "lastRecoveryTime" to lastRecoveryTime, "androidVersion" to Build.VERSION.SDK_INT ) } /** * 清理资源 */ fun cleanup() { try { Log.i(TAG, "🧹 清理智能MediaProjection管理器") isInitialized.set(false) monitoringJob?.cancel() managerScope.cancel() cleanupCurrentProjection() context.unregisterReceiver(systemStateReceiver) listeners.clear() Log.i(TAG, "✅ 清理完成") } catch (e: Exception) { Log.e(TAG, "❌ 清理失败", e) } } }