Как сохранить выданное разрешение MediaProjection?
Я разрабатываю приложение на kotlin и у меня вопрос по android.media.projection.MediaProjection
.
Как это должно работать:
- Установка приложения
- Выдача разрешений в том числе на запись экрана.
- С сервера инициализируется запрос на просмотр экрана с девайса
Обработка запроса на старт записи экрана от сервера:
private fun handleCommand(command: String) {
when (command) {
"START_SCREEN" -> {
if (isStreaming) {
Log.d(TAG, "Стриминг уже запущен")
return
}
val projectionData = MediaProjectionStorage.loadProjectionData(this)
Log.d(TAG, "Получена команда START_SCREEN, наличие данных: ${projectionData != null}")
if (projectionData != null) {
val (code, intent) = projectionData
Log.d(TAG, "Загружены данные MediaProjection: code=$code, intent=${intent != null}")
startStreaming()
} else {
Log.e(TAG, "Нет данных MediaProjection для запуска стрима")
}
}
"STOP_SCREEN" -> {
stopStreaming()
}
}
}
Запуск стрима:
private fun startStreaming() {
if (!checkCodecSupport()) {
Log.e(TAG, "Device doesn't support required video codec")
return
}
try {
cleanupMediaComponents()
val projectionManager = getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
val savedProjection = MediaProjectionStorage.loadProjectionData(this)
if (savedProjection == null) {
Log.e(TAG, "No saved MediaProjection data found")
return
}
val (code, data) = savedProjection
if (code != Activity.RESULT_OK) {
Log.e(TAG, "Invalid projection result code: $code")
MediaProjectionStorage.clearProjectionData(this)
return
}
mediaProjection = projectionManager.getMediaProjection(code, data!!)
if (mediaProjection == null) {
Log.e(TAG, "Failed to create MediaProjection")
MediaProjectionStorage.clearProjectionData(this)
return
}
Log.d(TAG, "MediaProjection successfully created")
// Rest of your existing setup code...
setupMediaCodec()
surface = mediaCodec?.createInputSurface()
virtualDisplay = mediaProjection?.createVirtualDisplay(
"ScreenCapture",
screenWidth, screenHeight, screenDpi,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
surface, null, null
)
mediaCodec?.start()
isStreaming = true
updateSocketTimeout()
startScreenDataProcessing()
} catch (e: Exception) {
Log.e(TAG, "Error starting screen capture: ${e.message}")
e.printStackTrace()
stopStreaming()
}
}
Остановка стрима:
private fun cleanupMediaComponents() {
try {
virtualDisplay?.release()
mediaCodec?.stop()
mediaCodec?.release()
surface?.release()
virtualDisplay = null
mediaCodec = null
surface = null
mediaProjection = null
} catch (e: Exception) {
Log.e(TAG, "Error in cleanupMediaComponents: ${e.message}")
}
}
private fun stopStreaming() {
if (!isStreaming) return
isStreaming = false
try {
cleanupMediaComponents()
updateSocketTimeout()
} catch (e: Exception) {
Log.e(TAG, "Error stopping stream: ${e.message}")
}
}
После остановки и следующем старте на девайсе вылазит опять запрос на выдачу разрешения.
Я хочу найти подход благодаря которому я смогу сохранить выданное 1 раз разрешение и использовать его на постоянной основе (до удаления приложения).
Вот та часть которая сохраняет вроде как, но на деле, я так понимаю, ничего подобного...
package com.example.audiostreamer.utils
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.util.Log
object MediaProjectionStorage{
private const val TAG = "MediaProjectionPrefs"
private const val PREFS_NAME = "media_projection_data"
private const val KEY_PROJECTION_CODE = "projection_code"
private const val KEY_INTENT_DATA = "intent_data"
// Храним Intent в памяти
private var cachedProjectionIntent: Intent? = null
private var cachedResultCode: Int = Activity.RESULT_CANCELED
fun saveProjectionData(context: Context, resultCode: Int, data: Intent) {
try {
// Кэшируем данные в памяти
cachedProjectionIntent = data
cachedResultCode = resultCode
// Сохраняем в SharedPreferences
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
prefs.edit().apply {
putInt(KEY_PROJECTION_CODE, resultCode)
// Сохраняем Intent как Bundle
data.extras?.let { bundle ->
putString(KEY_INTENT_DATA, android.util.Base64.encodeToString(
bundle.toString().toByteArray(),
android.util.Base64.DEFAULT
))
}
}.apply()
Log.d(TAG, "Successfully saved MediaProjection data")
} catch (e: Exception) {
Log.e(TAG, "Failed to save MediaProjection data", e)
}
}
fun loadProjectionData(context: Context): Pair<Int, Intent?>? {
try {
// Сначала пробуем использовать кэшированные данные
if (cachedProjectionIntent != null && cachedResultCode == Activity.RESULT_OK) {
return Pair(cachedResultCode, cachedProjectionIntent)
}
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
val resultCode = prefs.getInt(KEY_PROJECTION_CODE, Activity.RESULT_CANCELED)
val intentData = prefs.getString(KEY_INTENT_DATA, null)
if (resultCode != Activity.RESULT_OK || intentData == null) {
return null
}
// Восстанавливаем Intent
try {
val decodedBundle = android.util.Base64.decode(intentData, android.util.Base64.DEFAULT)
val bundle = android.os.Bundle()
bundle.putString("data", String(decodedBundle))
val intent = Intent()
intent.putExtras(bundle)
// Кэшируем восстановленные данные
cachedProjectionIntent = intent
cachedResultCode = resultCode
return Pair(resultCode, intent)
} catch (e: Exception) {
Log.e(TAG, "Failed to restore intent data", e)
return null
}
} catch (e: Exception) {
Log.e(TAG, "Failed to load MediaProjection data", e)
return null
}
}
fun clearProjectionData(context: Context) {
cachedProjectionIntent = null
cachedResultCode = Activity.RESULT_CANCELED
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
.edit()
.clear()
.apply()
}
}
Таким образом при получении разрешение после установки я пробую сохранить это разрешение в MainActivity
в методе onActivityResult
MEDIA_PROJECTION_REQUEST -> {
if (resultCode == RESULT_OK && data != null) {
try {
Log.d("MainActivity", "Получены данные MediaProjection с resultCode=RESULT_OK")
// Important change: Don't create a new Intent, use the original one
MediaProjectionStorage.saveProjectionData(this, resultCode, data)
// Проверяем сохранение
if (MediaProjectionStorage.loadProjectionData(this)?.first == RESULT_OK) {
Log.d("MainActivity", "MediaProjection данные сохранены успешно")
// Устанавливаем те же данные в сервис
ScreenStreamingService.setMediaProjectionData(this, resultCode, data)
// Запускаем сервисы
startRequiredServices()
} else {
Log.e("MainActivity", "Ошибка сохранения MediaProjection данных")
MediaProjectionStorage.clearProjectionData(this)
requestScreenCapture()
}
} catch (e: Exception) {
Log.e("MainActivity", "Ошибка обработки MediaProjection", e)
MediaProjectionStorage.clearProjectionData(this)
requestScreenCapture()
}
} else {
Log.e("MainActivity", "MediaProjection не получен: resultCode=$resultCode")
requestScreenCapture()
}
}
По итогу, разрешение всё равно запрашивается повторно.
Можно ли сохранить выданное разрешение MediaProjection
и запрашивать его повторно для дальнейших стримов экрана без подтверждения от пользователя?
И если нет такой возможности, то реально ли использовать захват экрана без MediaProjection
, например через AccessibilityService
?