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

668 lines
22 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.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<PermissionStateListener>()
// 系统状态监听
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
2026-02-11 16:59:49 +08:00
*/
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 中无有效对象,尝试从权限数据重新创建(仅一次)
2026-02-11 16:59:49 +08:00
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<String, Any> {
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)
}
}
}