655 lines
22 KiB
Kotlin
655 lines
22 KiB
Kotlin
|
|
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)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 尝试静默恢复
|
|||
|
|
*/
|
|||
|
|
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)
|
|||
|
|
|
|||
|
|
// 检查权限数据是否仍然有效
|
|||
|
|
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)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|