Feat: TV remote control via D-pad focus navigation using AccessibilityService
This commit is contained in:
@@ -21,6 +21,7 @@ import android.provider.Settings
|
||||
import android.util.DisplayMetrics
|
||||
import android.util.Log
|
||||
import android.view.WindowManager
|
||||
import android.widget.Toast
|
||||
|
||||
class CaptureService : Service() {
|
||||
|
||||
@@ -39,6 +40,14 @@ class CaptureService : Service() {
|
||||
// CS-01 fix: 通过 idrHandler.post 序列化到主线程,避免 TOCTOU 竞争
|
||||
@Volatile var instance: CaptureService? = null
|
||||
|
||||
@JvmStatic
|
||||
fun onNativeStatus(msg: String) {
|
||||
val svc = instance ?: return
|
||||
svc.idrHandler.post {
|
||||
Toast.makeText(svc, msg, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun requestIdr() {
|
||||
val svc = instance ?: return
|
||||
@@ -100,9 +109,16 @@ class CaptureService : Service() {
|
||||
super.onCreate()
|
||||
instance = this
|
||||
createNotificationChannel()
|
||||
// CS-06: start with DATA_SYNC type only; upgrade to MEDIA_PROJECTION in
|
||||
// onStartCommand when the token is actually available. On Android 14+
|
||||
// (targetSdk 34+) calling startForeground(MEDIA_PROJECTION) without
|
||||
// immediately pairing it with getMediaProjection() causes the system to
|
||||
// kill the service after a ~5-second grace period — the root cause of the
|
||||
// 3-9 s disconnect seen on Google TV when no screen-capture permission
|
||||
// was granted.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
startForeground(NOTIF_ID, buildNotification(),
|
||||
android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION)
|
||||
android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
|
||||
} else {
|
||||
startForeground(NOTIF_ID, buildNotification())
|
||||
}
|
||||
@@ -124,12 +140,23 @@ class CaptureService : Service() {
|
||||
Log.i(TAG, "CaptureService: server=$ip:$port device=$model res=$res")
|
||||
|
||||
// YB-01 fix: so 库加载失败是 Error,不被 catch(Exception) 捕获,需单独处理
|
||||
// ExceptionInInitializerError 包裹 UnsatisfiedLinkError(首次访问 object 时触发)
|
||||
val initRet: Int
|
||||
try {
|
||||
YamaBridge.nativeInit(ip, port, androidId, model, osVer, res, user, packageCodePath ?: "")
|
||||
initRet = YamaBridge.nativeInit(ip, port, androidId, model, osVer, res, user, packageCodePath ?: "")
|
||||
} catch (e: UnsatisfiedLinkError) {
|
||||
Log.e(TAG, "Native library load failed: $e")
|
||||
stopSelf()
|
||||
return START_NOT_STICKY
|
||||
} catch (e: ExceptionInInitializerError) {
|
||||
Log.e(TAG, "Native library init failed: ${e.cause}")
|
||||
stopSelf()
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
if (initRet != 0) {
|
||||
Log.e(TAG, "nativeInit failed: ret=$initRet")
|
||||
stopSelf()
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
val projResult = intent.getIntExtra(EXTRA_PROJECTION_RESULT, -1)
|
||||
@@ -139,12 +166,22 @@ class CaptureService : Service() {
|
||||
@Suppress("DEPRECATION") intent.getParcelableExtra(EXTRA_PROJECTION_DATA)
|
||||
|
||||
if (projResult == android.app.Activity.RESULT_OK && projData != null) {
|
||||
// Android 14+: upgrade foreground type to MEDIA_PROJECTION in the same
|
||||
// start command that calls getMediaProjection(), as required by API 34+.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
startForeground(NOTIF_ID, buildNotification(),
|
||||
android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION)
|
||||
}
|
||||
val pm = getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
|
||||
val mp = pm.getMediaProjection(projResult, projData)
|
||||
mediaProjection = mp
|
||||
// CS-05 fix: 分辨率为 0 时不启动采集,避免 MediaCodec 配置崩溃
|
||||
if (sw > 0 && sh > 0) {
|
||||
startCapture(mp, sw, sh)
|
||||
try {
|
||||
startCapture(mp, sw, sh)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Screen capture failed: $e — running without capture")
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "Invalid screen resolution ${sw}x${sh}, skipping capture")
|
||||
}
|
||||
@@ -223,9 +260,14 @@ class CaptureService : Service() {
|
||||
// Android 14+ requires callback registered before createVirtualDisplay()
|
||||
mp.registerCallback(object : MediaProjection.Callback() {
|
||||
override fun onStop() {
|
||||
Log.i(TAG, "MediaProjection stopped")
|
||||
Log.i(TAG, "MediaProjection stopped — switching to dataSync foreground type")
|
||||
stopCapture()
|
||||
stopSelf()
|
||||
// Android 14+: system auto-kills a foreground service whose mediaProjection
|
||||
// is revoked. Re-declare as dataSync-only to survive without screen capture.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
startForeground(NOTIF_ID, buildNotification(),
|
||||
android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
|
||||
}
|
||||
}
|
||||
}, null)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user