forked from geode-sdk/android-launcher
perform launcher download during update
This commit is contained in:
3 files changed
+118
-103
No files matched your search
@@ -14,13 +14,10 @@ import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ElevatedCard
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
@@ -29,7 +26,6 @@ import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
@@ -51,10 +47,9 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextLinkStyles
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import com.geode.launcher.InstallReceiver
|
||||
import com.geode.launcher.R
|
||||
import com.geode.launcher.main.LauncherUpdater.downloadUpdate
|
||||
import com.geode.launcher.ui.theme.Typography
|
||||
import com.geode.launcher.updater.ReleaseManager
|
||||
import com.geode.launcher.utils.LaunchUtils
|
||||
@@ -63,9 +58,12 @@ import com.mikepenz.markdown.compose.LocalBulletListHandler
|
||||
import com.mikepenz.markdown.m3.Markdown
|
||||
import com.mikepenz.markdown.m3.markdownTypography
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancelAndJoin
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor
|
||||
import java.io.File
|
||||
@@ -101,6 +99,7 @@ fun clearDownloadedApks(context: Context) {
|
||||
object LauncherUpdater {
|
||||
enum class LauncherUpdateState {
|
||||
Began,
|
||||
Downloading,
|
||||
Active,
|
||||
Inactive,
|
||||
Finished;
|
||||
@@ -113,12 +112,16 @@ object LauncherUpdater {
|
||||
_uiState.emit(LauncherUpdateState.Inactive)
|
||||
}
|
||||
|
||||
suspend fun installPackage(context: Context, file: File) {
|
||||
if (!file.exists()) {
|
||||
_uiState.emit(LauncherUpdateState.Finished)
|
||||
return
|
||||
}
|
||||
suspend fun downloadUpdate(context: Context): File? {
|
||||
_uiState.emit(LauncherUpdateState.Downloading)
|
||||
|
||||
return withContext(Dispatchers.IO) {
|
||||
ReleaseManager.get(context)
|
||||
.downloadLatestLauncherUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun installPackage(context: Context, file: File) {
|
||||
_uiState.emit(LauncherUpdateState.Began)
|
||||
|
||||
val installer = context.packageManager.packageInstaller
|
||||
@@ -147,7 +150,7 @@ object LauncherUpdater {
|
||||
}
|
||||
})
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
withContext(Dispatchers.IO) { runInterruptible {
|
||||
file.inputStream().use { apkStream ->
|
||||
val length = file.length()
|
||||
|
||||
@@ -160,46 +163,39 @@ object LauncherUpdater {
|
||||
}
|
||||
|
||||
val sessionId = installer.createSession(params)
|
||||
val session = installer.openSession(sessionId)
|
||||
installer.openSession(sessionId).use { session ->
|
||||
session.openWrite("package", 0, length).use { sessionStream ->
|
||||
apkStream.copyTo(sessionStream)
|
||||
session.fsync(sessionStream)
|
||||
}
|
||||
|
||||
session.openWrite("package", 0, length).use { sessionStream ->
|
||||
apkStream.copyTo(sessionStream)
|
||||
session.fsync(sessionStream)
|
||||
val intent = Intent(context, InstallReceiver::class.java)
|
||||
val pi = PendingIntent.getBroadcast(
|
||||
context,
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
|
||||
)
|
||||
|
||||
session.commit(pi.intentSender)
|
||||
}
|
||||
|
||||
val intent = Intent(context, InstallReceiver::class.java)
|
||||
val pi = PendingIntent.getBroadcast(
|
||||
context,
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
|
||||
)
|
||||
|
||||
session.commit(pi.intentSender)
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
suspend fun installLauncherUpdate(context: Context) {
|
||||
val nextUpdate = ReleaseManager.get(context).availableLauncherUpdate.value
|
||||
val launcherDownload = nextUpdate?.getDownload()
|
||||
val launcherDownload = downloadUpdate(context)
|
||||
|
||||
if (launcherDownload != null) {
|
||||
val outputFile = launcherDownload.filename
|
||||
val baseDirectory = context.filesDir
|
||||
|
||||
val outputPathFile = File(baseDirectory, outputFile)
|
||||
if (!outputPathFile.exists()) {
|
||||
if (!launcherDownload.exists()) {
|
||||
return
|
||||
}
|
||||
|
||||
PreferenceUtils.get(context).setBoolean(PreferenceUtils.Key.CLEANUP_APKS, true)
|
||||
|
||||
try {
|
||||
LauncherUpdater.installPackage(context, outputPathFile)
|
||||
LauncherUpdater.installPackage(context, launcherDownload)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
val msg = context.getString(R.string.launcher_self_update_failed, e.message)
|
||||
@@ -213,7 +209,7 @@ suspend fun installLauncherUpdate(context: Context) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LauncherUpdateDialog(onDismiss: () -> Unit) {
|
||||
fun LauncherUpdateDialog(isCancelling: Boolean, onDismiss: () -> Unit) {
|
||||
val updateState by LauncherUpdater.uiState.collectAsState()
|
||||
|
||||
val cancellable = updateState != LauncherUpdater.LauncherUpdateState.Active
|
||||
@@ -224,36 +220,32 @@ fun LauncherUpdateDialog(onDismiss: () -> Unit) {
|
||||
}
|
||||
}
|
||||
|
||||
if (cancellable) {
|
||||
return
|
||||
}
|
||||
|
||||
Dialog(
|
||||
onDismissRequest = {
|
||||
if (cancellable) onDismiss()
|
||||
AlertDialog(
|
||||
icon = {
|
||||
Icon(
|
||||
painterResource(R.drawable.icon_update),
|
||||
contentDescription = null
|
||||
)
|
||||
},
|
||||
properties = DialogProperties(usePlatformDefaultWidth = false)
|
||||
) {
|
||||
Card(
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh
|
||||
),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(18.dp),
|
||||
modifier = Modifier.padding(24.dp)
|
||||
) {
|
||||
CircularProgressIndicator()
|
||||
|
||||
Text(
|
||||
stringResource(R.string.launcher_self_update_progress),
|
||||
style = MaterialTheme.typography.bodyLarge
|
||||
)
|
||||
title = {
|
||||
if (isCancelling) {
|
||||
Text(stringResource(R.string.release_fetch_cancelling))
|
||||
} else if (updateState == LauncherUpdater.LauncherUpdateState.Downloading) {
|
||||
Text(stringResource(R.string.launcher_downloading_update))
|
||||
} else {
|
||||
Text(stringResource(R.string.launcher_self_update_progress))
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
text = {
|
||||
LinearProgressIndicator()
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = onDismiss, enabled = cancellable && !isCancelling) {
|
||||
Text(stringResource(R.string.message_box_cancel))
|
||||
}
|
||||
},
|
||||
onDismissRequest = {}
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@@ -265,10 +257,19 @@ fun LauncherUpdateInformation(onDismiss: () -> Unit) {
|
||||
val sheetState = rememberModalBottomSheetState()
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
var updateJob by remember { mutableStateOf<Job?>(null) }
|
||||
var cancellingUpdate by remember { mutableStateOf(false) }
|
||||
|
||||
var beginUpdate by remember { mutableStateOf(false) }
|
||||
if (beginUpdate) {
|
||||
LauncherUpdateDialog(onDismiss = {
|
||||
beginUpdate = false
|
||||
LauncherUpdateDialog(cancellingUpdate, onDismiss = {
|
||||
cancellingUpdate = true
|
||||
|
||||
coroutineScope.launch {
|
||||
updateJob?.cancelAndJoin()
|
||||
|
||||
beginUpdate = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -295,9 +296,11 @@ fun LauncherUpdateInformation(onDismiss: () -> Unit) {
|
||||
Row(modifier = Modifier.padding(vertical = 12.dp)) {
|
||||
Button(
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
installLauncherUpdate(context)
|
||||
updateJob = coroutineScope.launch {
|
||||
cancellingUpdate = false
|
||||
beginUpdate = true
|
||||
|
||||
installLauncherUpdate(context)
|
||||
}
|
||||
},
|
||||
) {
|
||||
@@ -362,10 +365,19 @@ fun LauncherUpdateIndicator(modifier: Modifier = Modifier) {
|
||||
}
|
||||
}
|
||||
|
||||
var updateJob by remember { mutableStateOf<Job?>(null) }
|
||||
var cancellingUpdate by remember { mutableStateOf(false) }
|
||||
|
||||
var beginUpdate by remember { mutableStateOf(false) }
|
||||
if (beginUpdate) {
|
||||
LauncherUpdateDialog {
|
||||
beginUpdate = false
|
||||
LauncherUpdateDialog(cancellingUpdate) {
|
||||
cancellingUpdate = true
|
||||
coroutineScope.launch {
|
||||
updateJob?.cancelAndJoin()
|
||||
updateJob = null
|
||||
|
||||
beginUpdate = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,9 +406,11 @@ fun LauncherUpdateIndicator(modifier: Modifier = Modifier) {
|
||||
|
||||
TextButton(
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
installLauncherUpdate(context)
|
||||
updateJob = coroutineScope.launch {
|
||||
cancellingUpdate = false
|
||||
beginUpdate = true
|
||||
|
||||
installLauncherUpdate(context)
|
||||
}
|
||||
},
|
||||
) {
|
||||
|
||||
@@ -133,16 +133,18 @@ class ReleaseManager private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun downloadLauncherUpdate(release: Downloadable) {
|
||||
val download = release.getDownload() ?: return
|
||||
private suspend fun downloadLauncherUpdate(release: Downloadable): File? {
|
||||
val download = release.getDownload() ?: return null
|
||||
|
||||
val outputDirectory = applicationContext.filesDir
|
||||
val outputFile = File(outputDirectory, download.filename)
|
||||
|
||||
/*
|
||||
if (outputFile.exists()) {
|
||||
// only download the apk once
|
||||
return
|
||||
}
|
||||
*/
|
||||
|
||||
_uiState.value = ReleaseManagerState.InDownload(0, download.size)
|
||||
|
||||
@@ -159,8 +161,10 @@ class ReleaseManager private constructor(
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
sendError(e)
|
||||
return
|
||||
return null
|
||||
}
|
||||
|
||||
return outputFile
|
||||
}
|
||||
|
||||
private suspend fun performUpdate(release: Downloadable) {
|
||||
@@ -213,8 +217,6 @@ class ReleaseManager private constructor(
|
||||
} catch (_: Exception) { }
|
||||
}
|
||||
|
||||
downloadLauncherUpdateIfNecessary()
|
||||
|
||||
// extraction performed
|
||||
updatePreferences(release)
|
||||
_uiState.value = ReleaseManagerState.Finished(true)
|
||||
@@ -237,15 +239,29 @@ class ReleaseManager private constructor(
|
||||
return originalFileHash != currentFileHash
|
||||
}
|
||||
|
||||
private fun checkLauncherUpdate(launcherUpdate: DownloadableLauncherRelease) {
|
||||
if (launcherUpdate.release.tagName != BuildConfig.VERSION_NAME) {
|
||||
_availableLauncherUpdate.value = launcherUpdate
|
||||
private suspend fun getLatestLauncherUpdate(): DownloadableLauncherRelease? {
|
||||
return try {
|
||||
releaseRepository.getLatestLauncherRelease()
|
||||
} catch (e: Exception) {
|
||||
sendError(e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun downloadLauncherUpdateIfNecessary() {
|
||||
val update = _availableLauncherUpdate.value ?: return
|
||||
downloadLauncherUpdate(update)
|
||||
private suspend fun checkLauncherUpdate() {
|
||||
val launcherRelease = getLatestLauncherUpdate() ?: return
|
||||
|
||||
if (launcherRelease.release.tagName != BuildConfig.VERSION_NAME) {
|
||||
_availableLauncherUpdate.value = launcherRelease
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun downloadLatestLauncherUpdate(): File? {
|
||||
val update = _availableLauncherUpdate.value
|
||||
?: getLatestLauncherUpdate()
|
||||
?: return null
|
||||
|
||||
return downloadLauncherUpdate(update)
|
||||
}
|
||||
|
||||
private suspend fun checkForNewRelease(isManual: Boolean = false) {
|
||||
@@ -266,23 +282,12 @@ class ReleaseManager private constructor(
|
||||
return
|
||||
}
|
||||
|
||||
val launcherRelease = try {
|
||||
releaseRepository.getLatestLauncherRelease()
|
||||
} catch (e: Exception) {
|
||||
sendError(e)
|
||||
return
|
||||
}
|
||||
|
||||
if (launcherRelease != null) {
|
||||
checkLauncherUpdate(launcherRelease)
|
||||
}
|
||||
checkLauncherUpdate()
|
||||
|
||||
val currentTime = Clock.System.now().toEpochMilliseconds()
|
||||
sharedPreferences.setLong(PreferenceUtils.Key.LAST_UPDATE_CHECK_TIME, currentTime)
|
||||
|
||||
if (release == null) {
|
||||
downloadLauncherUpdateIfNecessary()
|
||||
|
||||
_uiState.value = ReleaseManagerState.Finished()
|
||||
return
|
||||
}
|
||||
@@ -292,8 +297,6 @@ class ReleaseManager private constructor(
|
||||
|
||||
// check if an update is needed
|
||||
if (latestVersion == currentVersion && !fileWasExternallyModified()) {
|
||||
downloadLauncherUpdateIfNecessary()
|
||||
|
||||
_uiState.value = ReleaseManagerState.Finished()
|
||||
return
|
||||
}
|
||||
@@ -302,8 +305,6 @@ class ReleaseManager private constructor(
|
||||
|
||||
// check if the file was externally modified
|
||||
if (!allowOverwriting && fileWasExternallyModified(true)) {
|
||||
downloadLauncherUpdateIfNecessary()
|
||||
|
||||
sendError(UpdateException(UpdateException.Reason.EXTERNAL_FILE_IN_USE))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
<string name="launcher_self_update_failed_title">Update failed</string>
|
||||
|
||||
<string name="launcher_self_update_failed">Failed to install launcher update: %1$s.</string>
|
||||
<string name="launcher_self_update_progress">Updating Geode Launcher…</string>
|
||||
<string name="launcher_self_update_progress">Installing update…</string>
|
||||
|
||||
<string name="launcher_starting_game">Starting Geometry Dash…</string>
|
||||
<string name="launcher_downloading_update">Downloading update…</string>
|
||||
|
||||
Reference in new issue
Block a user