mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-13 03:51:10 +00:00
feat: auto drivers update on app open and roll back if a new pair never starts a session (#972)
This commit is contained in:
1 parent
4b2537a36b
commit
602a6fd734
8 files changed
+228
-23
No files matched your search
@@ -11,6 +11,7 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import com.droiddeck.launcher.gpu.AutoDriverTrial
|
||||
import com.droiddeck.launcher.gpu.DriverBundle
|
||||
import com.droiddeck.launcher.gpu.DriverPairs
|
||||
import com.droiddeck.launcher.gpu.GpuInfo
|
||||
@@ -47,7 +48,7 @@ internal class DriverMenus(private val activity: Activity, private val ui: Handl
|
||||
var pairPercent by mutableIntStateOf(-1)
|
||||
var autoStatus by mutableStateOf("")
|
||||
var activeBundle by mutableStateOf<String?>(null)
|
||||
private var autoCheckedThisProcess = false
|
||||
private var lastAutoCheck = 0L
|
||||
private var pairModeOpen by mutableStateOf(false)
|
||||
var restartPending by mutableStateOf(false)
|
||||
private set
|
||||
@@ -211,11 +212,32 @@ internal class DriverMenus(private val activity: Activity, private val ui: Handl
|
||||
if (auto) ensureAuto(force = false)
|
||||
}
|
||||
|
||||
fun checkOnOpen() {
|
||||
if (SessionPrefs.gpuDriverMode(activity) == SessionPrefs.GPU_DRIVERS_AUTO) ensureAuto(force = false)
|
||||
else if (!releaseChecking && releasesStale() && com.droiddeck.launcher.frontend.AutoComponents.isOnline(activity)) checkLatestTurnip()
|
||||
}
|
||||
|
||||
private fun releasesStale(): Boolean {
|
||||
val cached = TurnipReleases.cached(activity)
|
||||
return cached == null || System.currentTimeMillis() - cached.checkedAt > RELEASE_CHECK_INTERVAL_MS ||
|
||||
cached.assets.none { it.pair.isNotEmpty() }
|
||||
}
|
||||
|
||||
fun ensureAuto(force: Boolean) {
|
||||
mode = SessionPrefs.gpuDriverMode(activity)
|
||||
if (mode != SessionPrefs.GPU_DRIVERS_AUTO || pairBusy != null || releaseChecking) return
|
||||
if (!force && autoCheckedThisProcess) return
|
||||
autoCheckedThisProcess = true
|
||||
if (SessionState.running) {
|
||||
if (force) autoStatus = activity.getString(R.string.drivers_auto_waits_for_session)
|
||||
return
|
||||
}
|
||||
AutoDriverTrial.rollbackDue(activity)?.let { trial ->
|
||||
AutoDriverTrial.rollBack(activity, trial)
|
||||
autoStatus = activity.getString(R.string.drivers_rolled_back, trial.name)
|
||||
refreshDrivers()
|
||||
return
|
||||
}
|
||||
if (!force && System.currentTimeMillis() - lastAutoCheck < RELEASE_CHECK_INTERVAL_MS) return
|
||||
lastAutoCheck = System.currentTimeMillis()
|
||||
val displayChanged = SessionPrefs.androidDriver(activity) != QuestExperimental.defaultAndroidDriver()
|
||||
if (QuestExperimental.applyAutoDrivers(activity)) {
|
||||
autoStatus = activity.getString(
|
||||
@@ -231,10 +253,8 @@ internal class DriverMenus(private val activity: Activity, private val ui: Handl
|
||||
releaseChecking = true
|
||||
autoStatus = activity.getString(R.string.drivers_checking_latest)
|
||||
Thread({
|
||||
val cached = TurnipReleases.cached(activity)
|
||||
val stale = cached == null || System.currentTimeMillis() - cached.checkedAt > 24 * 3_600_000L ||
|
||||
cached.assets.none { it.pair.isNotEmpty() }
|
||||
val problem = if (force || stale) runCatching { TurnipReleases.refresh(activity) }.exceptionOrNull()?.message else null
|
||||
val problem = if (force || releasesStale()) runCatching { TurnipReleases.refresh(activity) }.exceptionOrNull()?.message else null
|
||||
val holdBack = !force && com.droiddeck.launcher.frontend.AutoComponents.constrained(activity)
|
||||
ui.post {
|
||||
releaseChecking = false
|
||||
refreshReleaseRows()
|
||||
@@ -248,6 +268,10 @@ internal class DriverMenus(private val activity: Activity, private val ui: Handl
|
||||
?: activity.getString(R.string.upd_current_status)
|
||||
pair == null || !pair.complete ->
|
||||
autoStatus = problem?.let { activity.getString(R.string.upd_couldnt_check, it) } ?: activity.getString(R.string.drivers_recommended_unpublished)
|
||||
AutoDriverTrial.release(pair) in AutoDriverTrial.bad(activity) ->
|
||||
autoStatus = activity.getString(R.string.drivers_skipping_bad, pair.name, pair.version)
|
||||
SessionState.running -> autoStatus = activity.getString(R.string.drivers_auto_waits_for_session)
|
||||
holdBack && row?.installed != true -> autoStatus = activity.getString(R.string.drivers_auto_waits_for_network, pair.name, pair.version)
|
||||
else -> installPair(pair, auto = true)
|
||||
}
|
||||
}
|
||||
@@ -268,8 +292,6 @@ internal class DriverMenus(private val activity: Activity, private val ui: Handl
|
||||
pairPercent = 0
|
||||
if (auto) autoStatus = activity.getString(R.string.drivers_downloading_pair, pair.name, pair.version)
|
||||
Thread({
|
||||
val lm = LinuxVulkanDriverManager(activity)
|
||||
val td = TurnipDriver(activity)
|
||||
var downloaded = false
|
||||
val result = runCatching {
|
||||
val zips = pair.assets
|
||||
@@ -283,22 +305,27 @@ internal class DriverMenus(private val activity: Activity, private val ui: Handl
|
||||
pairBusy = null
|
||||
pairPercent = -1
|
||||
result.onSuccess { (displayId, linuxId) ->
|
||||
val displayChanged = SessionPrefs.androidDriver(activity) != displayId
|
||||
if (auto && SessionState.running) {
|
||||
lastAutoCheck = 0L
|
||||
autoStatus = activity.getString(R.string.drivers_auto_waits_for_session)
|
||||
return@onSuccess
|
||||
}
|
||||
val previousDisplay = SessionPrefs.androidDriver(activity)
|
||||
val previousLinux = SessionPrefs.linuxDriver(activity)
|
||||
val displayChanged = previousDisplay != displayId
|
||||
SessionPrefs.setAndroidDriver(activity, displayId)
|
||||
SessionPrefs.setLinuxDriver(activity, linuxId)
|
||||
val restart = displayChanged && CompositorHost.isStarted
|
||||
if (auto) {
|
||||
val previous = SessionPrefs.gpuAutoInstalled(activity)
|
||||
for (id in previous - setOf(displayId, linuxId)) {
|
||||
val bundle = DriverBundle.containing(activity, id, lm.isInstalled(id))
|
||||
when {
|
||||
bundle != null -> { DriverBundle.remove(activity, bundle); TurnipReleases.forget(activity, bundle.id) }
|
||||
lm.isInstalled(id) -> lm.removeDriver(id)
|
||||
else -> td.remove(id)
|
||||
}
|
||||
TurnipReleases.forget(activity, id)
|
||||
}
|
||||
SessionPrefs.setGpuAutoInstalled(activity, setOf(displayId, linuxId))
|
||||
val trial = AutoDriverTrial.current(activity)
|
||||
val lastGood = trial?.let { setOf(it.previousDisplay, it.previousLinux) } ?: setOf(previousDisplay, previousLinux)
|
||||
val changed = previousDisplay != displayId || previousLinux != linuxId
|
||||
SessionPrefs.setGpuAutoInstalled(activity, SessionPrefs.gpuAutoInstalled(activity) + setOf(displayId, linuxId))
|
||||
if (changed) AutoDriverTrial.begin(
|
||||
activity, AutoDriverTrial.release(pair), "${pair.name} ${pair.version}", displayId, linuxId,
|
||||
trial?.previousDisplay ?: previousDisplay, trial?.previousLinux ?: previousLinux,
|
||||
)
|
||||
if (!SessionState.running) AutoDriverTrial.prune(activity, lastGood + setOf(displayId, linuxId))
|
||||
autoStatus = activity.getString(
|
||||
if (downloaded) { if (restart) R.string.drivers_updated_to_restart else R.string.drivers_updated_to }
|
||||
else { if (restart) R.string.drivers_switched_to_restart else R.string.drivers_switched_to },
|
||||
@@ -506,6 +533,7 @@ internal class DriverMenus(private val activity: Activity, private val ui: Handl
|
||||
|
||||
private companion object {
|
||||
const val TAG = "MainActivity"
|
||||
const val RELEASE_CHECK_INTERVAL_MS = 3_600_000L
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1051,7 +1051,7 @@ class MainActivity : ComponentActivity() {
|
||||
refreshPhantomStatus()
|
||||
syncLossless()
|
||||
updates.onResume()
|
||||
drivers.ensureAuto(force = false)
|
||||
drivers.checkOnOpen()
|
||||
protons.checkUpdates()
|
||||
if (!SessionState.running) Thread({
|
||||
val applied = runCatching { ComponentsManager.applyQueued(this) }.getOrDefault(emptyList())
|
||||
|
||||
@@ -332,7 +332,7 @@ object AutoComponents {
|
||||
caps.hasCapability(android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED)
|
||||
}
|
||||
|
||||
private fun constrained(context: Context): Boolean {
|
||||
fun constrained(context: Context): Boolean {
|
||||
val power = context.getSystemService(PowerManager::class.java)
|
||||
if (power?.isPowerSaveMode == true) return true
|
||||
if (Build.VERSION.SDK_INT >= 29 && (power?.currentThermalStatus ?: 0) >= PowerManager.THERMAL_STATUS_MODERATE) return true
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.droiddeck.launcher.gpu
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import com.droiddeck.launcher.session.SessionPrefs
|
||||
|
||||
object AutoDriverTrial {
|
||||
private const val TAG = "AutoDriverTrial"
|
||||
private const val PREFS = "gpu_auto_trial"
|
||||
const val UNREADY_STARTS_BEFORE_ROLLBACK = 2
|
||||
|
||||
class Trial(
|
||||
val release: String, val name: String,
|
||||
val display: String, val linux: String,
|
||||
val previousDisplay: String, val previousLinux: String,
|
||||
val starts: Int,
|
||||
)
|
||||
|
||||
@Volatile private var app: Context? = null
|
||||
@Volatile private var watching: Trial? = null
|
||||
|
||||
fun release(pair: DriverPairs.DriverPair) = "${pair.key}@${pair.version}"
|
||||
|
||||
fun current(context: Context): Trial? {
|
||||
val p = prefs(context)
|
||||
val release = p.getString("release", null) ?: return null
|
||||
return Trial(
|
||||
release, p.getString("name", "").orEmpty(),
|
||||
p.getString("display", "").orEmpty(), p.getString("linux", "").orEmpty(),
|
||||
p.getString("previousDisplay", "").orEmpty(), p.getString("previousLinux", "").orEmpty(),
|
||||
p.getInt("starts", 0),
|
||||
)
|
||||
}
|
||||
|
||||
fun begin(context: Context, release: String, name: String, display: String, linux: String, previousDisplay: String, previousLinux: String) {
|
||||
prefs(context).edit()
|
||||
.putString("release", release).putString("name", name)
|
||||
.putString("display", display).putString("linux", linux)
|
||||
.putString("previousDisplay", previousDisplay).putString("previousLinux", previousLinux)
|
||||
.putInt("starts", 0)
|
||||
.commit()
|
||||
}
|
||||
|
||||
fun clear(context: Context) {
|
||||
val bad = bad(context)
|
||||
prefs(context).edit().clear().putStringSet("bad", bad).commit()
|
||||
}
|
||||
|
||||
fun bad(context: Context): Set<String> = prefs(context).getStringSet("bad", emptySet()).orEmpty()
|
||||
|
||||
fun sessionStarting(context: Context, display: String, linux: String) {
|
||||
val trial = current(context)
|
||||
watching = null
|
||||
if (trial == null || trial.display != display || trial.linux != linux) return
|
||||
app = context.applicationContext
|
||||
watching = trial
|
||||
prefs(context).edit().putInt("starts", trial.starts + 1).commit()
|
||||
}
|
||||
|
||||
fun sessionReady() {
|
||||
val trial = watching ?: return
|
||||
val context = app ?: return
|
||||
watching = null
|
||||
if (current(context)?.release != trial.release) return
|
||||
Log.i(TAG, "${trial.release} reached a ready session; keeping it")
|
||||
clear(context)
|
||||
prune(context, setOf(trial.display, trial.linux))
|
||||
}
|
||||
|
||||
fun rollbackDue(context: Context): Trial? = current(context)?.takeIf { it.starts >= UNREADY_STARTS_BEFORE_ROLLBACK }
|
||||
|
||||
fun rollBack(context: Context, trial: Trial) {
|
||||
Log.w(TAG, "${trial.release} never reached a ready session in ${trial.starts} starts; restoring the previous drivers")
|
||||
if (SessionPrefs.androidDriver(context) == trial.display) SessionPrefs.setAndroidDriver(context, trial.previousDisplay)
|
||||
if (SessionPrefs.linuxDriver(context) == trial.linux) SessionPrefs.setLinuxDriver(context, trial.previousLinux)
|
||||
val bad = bad(context) + trial.release
|
||||
prefs(context).edit().clear().putStringSet("bad", bad).commit()
|
||||
prune(context, setOf(trial.previousDisplay, trial.previousLinux, SessionPrefs.androidDriver(context), SessionPrefs.linuxDriver(context)))
|
||||
}
|
||||
|
||||
fun prune(context: Context, keep: Set<String>) {
|
||||
val lm = LinuxVulkanDriverManager(context)
|
||||
val td = TurnipDriver(context)
|
||||
val owned = SessionPrefs.gpuAutoInstalled(context)
|
||||
for (id in owned - keep) {
|
||||
val bundle = DriverBundle.containing(context, id, lm.isInstalled(id))
|
||||
when {
|
||||
bundle != null -> {
|
||||
if (bundle.androidId in keep || bundle.linuxId in keep) continue
|
||||
DriverBundle.remove(context, bundle); TurnipReleases.forget(context, bundle.id)
|
||||
}
|
||||
lm.isInstalled(id) -> lm.removeDriver(id)
|
||||
else -> td.remove(id)
|
||||
}
|
||||
TurnipReleases.forget(context, id)
|
||||
}
|
||||
SessionPrefs.setGpuAutoInstalled(context, owned.intersect(keep))
|
||||
}
|
||||
|
||||
private fun prefs(context: Context) = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
||||
}
|
||||
@@ -103,6 +103,7 @@ object SessionEvents {
|
||||
if (!SessionState.running || SessionState.guestPid <= 1 || !SessionState.firstFrameSeen || SessionState.suspended) return
|
||||
if (SessionState.phase != SessionPhase.READY) {
|
||||
transition(SessionPhase.READY, "session.ready")
|
||||
Thread({ com.droiddeck.launcher.gpu.AutoDriverTrial.sessionReady() }, "gpu-driver-trial").start()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -539,6 +539,12 @@ class SessionService : Service() {
|
||||
LinuxRuntime.vulkanIcd(this)?.let { guest.add("VK_ICD_FILENAMES=" + it.path) }
|
||||
val linuxDriverId = SessionPrefs.linuxDriver(this)
|
||||
SessionState.runtimeDriver = linuxDriverId
|
||||
com.droiddeck.launcher.gpu.AutoDriverTrial.sessionStarting(
|
||||
this,
|
||||
com.droiddeck.launcher.wayland.CompositorHost.displayChoice.takeIf { com.droiddeck.launcher.wayland.CompositorHost.isStarted }
|
||||
?: SessionPrefs.androidDriver(this),
|
||||
linuxDriverId,
|
||||
)
|
||||
LinuxVulkanDriver.resolveIcdPath(this, linuxDriverId)
|
||||
?.let { guest.add(LinuxVulkanDriver.ENV + "=" + it) }
|
||||
tuDebug(linuxDriverId)?.let { guest.add("TU_DEBUG=$it") }
|
||||
|
||||
@@ -1285,6 +1285,10 @@
|
||||
<string name="drivers_downloading_pair">Downloading %1$s %2$s…</string>
|
||||
<string name="drivers_updated_to">Updated to %1$s %2$s.</string>
|
||||
<string name="drivers_updated_to_restart">Updated to %1$s %2$s. The display driver applies after DroidDeck restarts.</string>
|
||||
<string name="drivers_auto_waits_for_session">Driver updates wait until the session ends.</string>
|
||||
<string name="drivers_auto_waits_for_network">%1$s %2$s will download once you\'re off mobile data and battery saver.</string>
|
||||
<string name="drivers_skipping_bad">%1$s %2$s didn\'t start a session here, so Auto is staying on the drivers that did.</string>
|
||||
<string name="drivers_rolled_back">%1$s didn\'t start a session twice, so Auto went back to the drivers that worked.</string>
|
||||
<string name="drivers_switched_to">Switched to %1$s %2$s.</string>
|
||||
<string name="drivers_switched_to_restart">Switched to %1$s %2$s. The display driver applies after DroidDeck restarts.</string>
|
||||
<string name="drivers_using">Using %1$s %2$s.</string>
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.droiddeck.launcher.gpu
|
||||
|
||||
import android.content.Context
|
||||
import com.droiddeck.launcher.session.SessionPrefs
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.RuntimeEnvironment
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(sdk = [28], manifest = Config.NONE)
|
||||
class AutoDriverTrialTest {
|
||||
private val context: Context get() = RuntimeEnvironment.getApplication()
|
||||
|
||||
@Before fun reset() {
|
||||
context.getSharedPreferences("session", Context.MODE_PRIVATE).edit().clear().commit()
|
||||
context.getSharedPreferences("gpu_auto_trial", Context.MODE_PRIVATE).edit().clear().commit()
|
||||
SessionPrefs.setAndroidDriver(context, "new-display")
|
||||
SessionPrefs.setLinuxDriver(context, "new-linux")
|
||||
SessionPrefs.setGpuAutoInstalled(context, setOf("old-display", "old-linux", "new-display", "new-linux"))
|
||||
AutoDriverTrial.begin(context, "banner@r5", "Banners r5", "new-display", "new-linux", "old-display", "old-linux")
|
||||
}
|
||||
|
||||
@Test fun aReadySessionKeepsTheNewDriversAndDropsTheOldOnes() {
|
||||
AutoDriverTrial.sessionStarting(context, "new-display", "new-linux")
|
||||
AutoDriverTrial.sessionReady()
|
||||
assertNull(AutoDriverTrial.current(context))
|
||||
assertEquals(setOf("new-display", "new-linux"), SessionPrefs.gpuAutoInstalled(context))
|
||||
assertEquals("new-linux", SessionPrefs.linuxDriver(context))
|
||||
}
|
||||
|
||||
@Test fun startsThatNeverGetReadyRollBackAndMarkTheReleaseBad() {
|
||||
AutoDriverTrial.sessionStarting(context, "new-display", "new-linux")
|
||||
assertNull(AutoDriverTrial.rollbackDue(context))
|
||||
AutoDriverTrial.sessionStarting(context, "new-display", "new-linux")
|
||||
val trial = AutoDriverTrial.rollbackDue(context)!!
|
||||
AutoDriverTrial.rollBack(context, trial)
|
||||
assertEquals("old-display", SessionPrefs.androidDriver(context))
|
||||
assertEquals("old-linux", SessionPrefs.linuxDriver(context))
|
||||
assertEquals(setOf("old-display", "old-linux"), SessionPrefs.gpuAutoInstalled(context))
|
||||
assertTrue("banner@r5" in AutoDriverTrial.bad(context))
|
||||
assertNull(AutoDriverTrial.current(context))
|
||||
}
|
||||
|
||||
@Test fun rollingBackKeepsADriverTheUserSelectedSince() {
|
||||
SessionPrefs.setGpuAutoInstalled(context, setOf("old-display", "old-linux", "new-display", "new-linux", "chosen-linux"))
|
||||
SessionPrefs.setLinuxDriver(context, "chosen-linux")
|
||||
AutoDriverTrial.rollBack(context, AutoDriverTrial.current(context)!!)
|
||||
assertEquals("chosen-linux", SessionPrefs.linuxDriver(context))
|
||||
assertTrue("chosen-linux" in SessionPrefs.gpuAutoInstalled(context))
|
||||
}
|
||||
|
||||
@Test fun sessionsOnOtherDriversDoNotCountTowardsTheTrial() {
|
||||
AutoDriverTrial.sessionStarting(context, "old-display", "new-linux")
|
||||
AutoDriverTrial.sessionStarting(context, "old-display", "new-linux")
|
||||
AutoDriverTrial.sessionReady()
|
||||
assertNull(AutoDriverTrial.rollbackDue(context))
|
||||
assertEquals("banner@r5", AutoDriverTrial.current(context)?.release)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user