mirror of
https://github.com/screentinker/screentinker.git
synced 2026-06-18 20:22:42 -06:00
On Android 14+ (targetSdk 34) the app could fail to run at all on newer devices (Pixel 10, onn HD stick). Root cause: the always-on WebSocketService called the 2-arg startForeground(), which claims EVERY foreground-service type declared in the manifest - including mediaProjection. Android 14 rejects starting a mediaProjection-typed FGS without a MediaProjection consent token, so the core service threw on launch and the player never came up. Matches the reporter's "screen recording policy" hunch - via the FGS type, not the capture trigger. Fixes: - WebSocketService now claims ONLY mediaPlayback (explicit startForeground(..., FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK), API>=29 guarded; 2-arg on older). Manifest type narrowed to mediaPlayback. - New MediaProjectionService (manifest type mediaProjection), started only AFTER the user grants consent. It enters the foreground with the mediaProjection type BEFORE getMediaProjection() (required on 14+), then drives ScreenCaptureService. The consent Activity now hands the result to this service instead of calling getMediaProjection() directly (an Activity can't hold that FGS type). - ScreenCaptureService: register the MediaProjection.Callback BEFORE createVirtualDisplay() (Android 14 throws IllegalStateException otherwise). Verified: Kotlin compiles, manifest merges (WebSocketService=mediaPlayback, MediaProjectionService=mediaProjection), signed debug APK builds. NOT yet verified on-device - needs a Pixel 10 / onn-stick run + logcat to confirm the exact crash is resolved. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
68 lines
2.6 KiB
Kotlin
68 lines
2.6 KiB
Kotlin
package com.remotedisplay.player
|
|
|
|
import android.app.Activity
|
|
import android.content.Context
|
|
import android.content.Intent
|
|
import android.media.projection.MediaProjectionManager
|
|
import android.os.Bundle
|
|
import android.util.Log
|
|
import com.remotedisplay.player.service.MediaProjectionService
|
|
|
|
/**
|
|
* Transparent activity that requests MediaProjection permission.
|
|
* Shows a system dialog asking "Start recording?" - user taps "Start now" once.
|
|
*/
|
|
class ScreenCapturePermissionActivity : Activity() {
|
|
|
|
companion object {
|
|
private const val REQUEST_CODE = 1001
|
|
private const val TAG = "ScreenCapturePermission"
|
|
|
|
// Store the result intent so the service can use it
|
|
var resultCode: Int = RESULT_CANCELED
|
|
private set
|
|
var resultData: Intent? = null
|
|
private set
|
|
var hasPermission = false
|
|
private set
|
|
|
|
fun requestPermission(context: Context) {
|
|
val intent = Intent(context, ScreenCapturePermissionActivity::class.java).apply {
|
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
}
|
|
context.startActivity(intent)
|
|
}
|
|
}
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
super.onCreate(savedInstanceState)
|
|
val mediaProjectionManager = getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
|
|
startActivityForResult(mediaProjectionManager.createScreenCaptureIntent(), REQUEST_CODE)
|
|
}
|
|
|
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
|
if (requestCode == REQUEST_CODE) {
|
|
if (resultCode == RESULT_OK && data != null) {
|
|
Log.i(TAG, "MediaProjection permission granted, starting via service")
|
|
|
|
// Store the result so the service can create the projection
|
|
Companion.resultCode = resultCode
|
|
Companion.resultData = data?.clone() as? Intent
|
|
Companion.hasPermission = true
|
|
|
|
// #5: hand the consent to the dedicated mediaProjection foreground
|
|
// service. It must enter the foreground with the mediaProjection FGS
|
|
// type BEFORE getMediaProjection() on Android 14+ - an Activity can't
|
|
// do that, so we can't call getMediaProjection() directly here.
|
|
MediaProjectionService.start(this, resultCode, data)
|
|
|
|
getSharedPreferences("remote_display", MODE_PRIVATE)
|
|
.edit().putBoolean("screen_capture_granted", true).apply()
|
|
} else {
|
|
Log.w(TAG, "MediaProjection permission denied")
|
|
}
|
|
}
|
|
finish()
|
|
}
|
|
}
|