mirror of
https://github.com/screentinker/screentinker.git
synced 2026-06-20 21:22:37 -06:00
Merge pull request #67 from screentinker/fix/android-ota-install-completion
fix(android): OTA install completion + kiosk auto-confirm (1.7.10)
This commit is contained in:
commit
acd93377e7
|
|
@ -11,8 +11,8 @@ android {
|
||||||
applicationId = "com.remotedisplay.player"
|
applicationId = "com.remotedisplay.player"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 12
|
versionCode = 13
|
||||||
versionName = "1.7.9"
|
versionName = "1.7.10"
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import android.util.DisplayMetrics
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import android.view.accessibility.AccessibilityEvent
|
import android.view.accessibility.AccessibilityEvent
|
||||||
|
import android.view.accessibility.AccessibilityNodeInfo
|
||||||
|
|
||||||
class PowerAccessibilityService : AccessibilityService() {
|
class PowerAccessibilityService : AccessibilityService() {
|
||||||
|
|
||||||
|
|
@ -22,7 +23,53 @@ class PowerAccessibilityService : AccessibilityService() {
|
||||||
Log.i(TAG, "Service connected")
|
Log.i(TAG, "Service connected")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAccessibilityEvent(event: AccessibilityEvent?) {}
|
private var lastConfirm = 0L
|
||||||
|
|
||||||
|
override fun onAccessibilityEvent(event: AccessibilityEvent?) {
|
||||||
|
val pkg = event?.packageName?.toString() ?: return
|
||||||
|
// Auto-confirm the system app-update dialog so OTA updates apply unattended
|
||||||
|
// on kiosk screens (no one is there to tap "Update"). Scoped to the package
|
||||||
|
// installer only, so this never touches anything else.
|
||||||
|
if (!pkg.contains("packageinstaller", ignoreCase = true)) return
|
||||||
|
if (event.eventType != AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED &&
|
||||||
|
event.eventType != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) return
|
||||||
|
autoConfirmInstall()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun autoConfirmInstall() {
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
if (now - lastConfirm < 1500) return // debounce repeated content events
|
||||||
|
val root = rootInActiveWindow ?: return
|
||||||
|
// Positive button by resource id first (locale-independent), then by label.
|
||||||
|
val ids = listOf(
|
||||||
|
"com.google.android.packageinstaller:id/ok_button",
|
||||||
|
"com.android.packageinstaller:id/ok_button",
|
||||||
|
"android:id/button1"
|
||||||
|
)
|
||||||
|
for (id in ids) {
|
||||||
|
for (n in root.findAccessibilityNodeInfosByViewId(id)) {
|
||||||
|
if (clickButton(n)) { lastConfirm = now; Log.i(TAG, "Auto-confirmed install via $id"); return }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (label in listOf("Update", "Install", "Reinstall", "Continue")) {
|
||||||
|
for (n in root.findAccessibilityNodeInfosByText(label)) {
|
||||||
|
if (clickButton(n)) { lastConfirm = now; Log.i(TAG, "Auto-confirmed install via '$label'"); return }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Click the node or its nearest clickable+enabled ancestor (the button).
|
||||||
|
private fun clickButton(node: AccessibilityNodeInfo?): Boolean {
|
||||||
|
var cur = node
|
||||||
|
var depth = 0
|
||||||
|
while (cur != null && depth < 4) {
|
||||||
|
if (cur.isClickable && cur.isEnabled) return cur.performAction(AccessibilityNodeInfo.ACTION_CLICK)
|
||||||
|
cur = cur.parent
|
||||||
|
depth++
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
override fun onInterrupt() {}
|
override fun onInterrupt() {}
|
||||||
|
|
||||||
// Global actions
|
// Global actions
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,45 @@ class UpdateChecker(private val context: Context) {
|
||||||
// Check every 30 minutes
|
// Check every 30 minutes
|
||||||
private val CHECK_INTERVAL = 30 * 60 * 1000L
|
private val CHECK_INTERVAL = 30 * 60 * 1000L
|
||||||
|
|
||||||
|
private var installReceiverRegistered = false
|
||||||
|
|
||||||
|
// The PackageInstaller session reports its status (incl. STATUS_PENDING_USER_ACTION,
|
||||||
|
// which Android 13+ returns for non-device-owner installers) via this broadcast.
|
||||||
|
// Without handling it the committed session just stalls and the update never
|
||||||
|
// installs. On the action prompt we launch the confirm dialog; the accessibility
|
||||||
|
// service auto-confirms it on kiosks.
|
||||||
|
private fun ensureInstallReceiver() {
|
||||||
|
if (installReceiverRegistered) return
|
||||||
|
val receiver = object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(ctx: Context, intent: Intent) {
|
||||||
|
when (intent.getIntExtra(android.content.pm.PackageInstaller.EXTRA_STATUS, -999)) {
|
||||||
|
android.content.pm.PackageInstaller.STATUS_PENDING_USER_ACTION -> {
|
||||||
|
val confirm = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
|
||||||
|
intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
|
||||||
|
else @Suppress("DEPRECATION") intent.getParcelableExtra(Intent.EXTRA_INTENT)
|
||||||
|
if (confirm != null) {
|
||||||
|
confirm.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
try { context.startActivity(confirm); Log.i(TAG, "Launched install confirmation") }
|
||||||
|
catch (e: Exception) { Log.e(TAG, "Confirm launch failed: ${e.message}") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
android.content.pm.PackageInstaller.STATUS_SUCCESS -> Log.i(TAG, "Update installed successfully")
|
||||||
|
else -> Log.w(TAG, "Install status: ${intent.getStringExtra(android.content.pm.PackageInstaller.EXTRA_STATUS_MESSAGE)}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val filter = IntentFilter("com.remotedisplay.player.INSTALL_COMPLETE")
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
context.registerReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED)
|
||||||
|
} else {
|
||||||
|
@Suppress("UnspecifiedRegisterReceiverFlag") context.registerReceiver(receiver, filter)
|
||||||
|
}
|
||||||
|
installReceiverRegistered = true
|
||||||
|
}
|
||||||
|
|
||||||
fun startPeriodicCheck() {
|
fun startPeriodicCheck() {
|
||||||
stopPeriodicCheck()
|
stopPeriodicCheck()
|
||||||
|
ensureInstallReceiver()
|
||||||
checkTimer = object : Runnable {
|
checkTimer = object : Runnable {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
checkForUpdate()
|
checkForUpdate()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue