Skip to content

Commit

Permalink
very basic self updater
Browse files Browse the repository at this point in the history
  • Loading branch information
qimiko committed Jun 21, 2024
1 parent 375f771 commit 759ee4a
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 31 deletions.
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>

<uses-feature
android:name="android.hardware.wifi"
Expand Down
7 changes: 3 additions & 4 deletions app/src/main/java/com/geode/launcher/AltMainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ class AltMainActivity : ComponentActivity() {
super.onCreate(savedInstanceState)

enableEdgeToEdge()
clearDownloadedApks(this)

val gdInstalled = GamePackageUtils.isGameInstalled(packageManager)
val geodeInstalled = LaunchUtils.isGeodeInstalled(this)
Expand Down Expand Up @@ -483,11 +484,9 @@ fun AltMainScreen(
)

val nextLauncherUpdate by launchViewModel.nextLauncherUpdate.collectAsState()
val launcherDownloadUrl =
nextLauncherUpdate?.getLauncherDownload()?.browserDownloadUrl

// only show launcher update in a case where a user won't see it ingame
if (launcherDownloadUrl != null && launchUIState is LaunchViewModel.LaunchUIState.Cancelled) {
if (nextLauncherUpdate != null && launchUIState is LaunchViewModel.LaunchUIState.Cancelled) {
StatusIndicator(
icon = {
Icon(
Expand All @@ -496,7 +495,7 @@ fun AltMainScreen(
)
},
onClick = {
downloadUrl(context, launcherDownloadUrl)
installLauncherUpdate(context)
},
text = {
Text(
Expand Down
1 change: 1 addition & 0 deletions app/src/main/java/com/geode/launcher/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import com.geode.launcher.utils.GamePackageUtils
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
clearDownloadedApks(this)

super.onCreate(savedInstanceState)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ fun NotificationCardFromType(type: LaunchNotificationType) {
AnimatedNotificationCard(
displayLength = 5000L,
onClick = {
downloadLauncherUpdate(context)
installLauncherUpdate(context)
}
) {
LauncherUpdateContent()
Expand Down Expand Up @@ -110,7 +110,6 @@ fun LaunchNotification() {
GeodeLauncherTheme(theme = theme, blackBackground = backgroundOption) {
// surface is not in use, so this is unfortunately not provided
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
// using manual placements as column layouts seem to mess up the animation
Column(
modifier = Modifier.displayCutoutPadding()
) {
Expand All @@ -124,7 +123,7 @@ fun LaunchNotification() {
}

@Composable
fun AnimatedNotificationCard(visibilityDelay: Long = 0L, displayLength: Long = 3000L, onClick: (() -> Unit)? = null, modifier: Modifier = Modifier, contents: @Composable () -> Unit) {
fun AnimatedNotificationCard(modifier: Modifier = Modifier, visibilityDelay: Long = 0L, displayLength: Long = 3000L, onClick: (() -> Unit)? = null, contents: @Composable () -> Unit) {
val state = remember {
MutableTransitionState(false).apply {
targetState = visibilityDelay <= 0
Expand Down
76 changes: 54 additions & 22 deletions app/src/main/java/com/geode/launcher/main/UpdateComponents.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.provider.DocumentsContract
import android.text.format.Formatter
import android.widget.Toast
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
Expand All @@ -29,39 +29,76 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.geode.launcher.R
import com.geode.launcher.UserDirectoryProvider
import com.geode.launcher.updater.ReleaseManager
import com.geode.launcher.updater.ReleaseViewModel
import com.geode.launcher.utils.LaunchUtils
import com.geode.launcher.utils.PreferenceUtils
import java.io.File
import java.net.ConnectException
import java.net.UnknownHostException

fun clearDownloadedApks(context: Context) {
// technically we should be using the activity results but it was too inconsistent for my liking
val performCleanup = PreferenceUtils.get(context).getBoolean(PreferenceUtils.Key.CLEANUP_APKS)
if (!performCleanup) {
return
}

val baseDirectory = LaunchUtils.getBaseDirectory(context)

fun downloadUrl(context: Context, url: String) {
try {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
context.startActivity(intent)
} catch (e: ActivityNotFoundException) {
Toast.makeText(context, context.getString(R.string.no_activity_found), Toast.LENGTH_SHORT).show()
baseDirectory.listFiles {
// only select apk files
_, name -> name.lowercase().endsWith(".apk")
}?.forEach {
it.delete()
}
}

fun downloadLauncherUpdate(context: Context) {
fun generateInstallIntent(uri: Uri): Intent {
// maybe one day i'll rewrite this to use packageinstaller. not today
return Intent(Intent.ACTION_INSTALL_PACKAGE).apply {
setDataAndType(uri, "application/vnd.android.package-archive")
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
}

fun installLauncherUpdate(context: Context) {
val nextUpdate = ReleaseManager.get(context).availableLauncherUpdate.value
val launcherUrl = nextUpdate?.getLauncherDownload()?.browserDownloadUrl
val launcherDownload = nextUpdate?.getLauncherDownload()

if (launcherDownload != null) {
val outputFile = launcherDownload.name
val baseDirectory = LaunchUtils.getBaseDirectory(context)

if (launcherUrl != null) {
downloadUrl(context, launcherUrl)
val outputPathFile = File(baseDirectory, outputFile)
if (!outputPathFile.exists()) {
return
}

val outputPath = "${UserDirectoryProvider.ROOT}${outputFile}"

val uri = DocumentsContract.buildDocumentUri("${context.packageName}.user", outputPath)

PreferenceUtils.get(context).setBoolean(PreferenceUtils.Key.CLEANUP_APKS, true)

try {
val intent = generateInstallIntent(uri)
context.startActivity(intent)
} catch (e: ActivityNotFoundException) {
Toast.makeText(context, context.getString(R.string.no_activity_found), Toast.LENGTH_SHORT).show()
}
} else {
Toast.makeText(context, context.getString(R.string.release_fetch_no_releases), Toast.LENGTH_SHORT).show()
}
}

@Composable
fun LauncherUpdateIndicator(modifier: Modifier = Modifier, openTo: String) {
fun LauncherUpdateIndicator(modifier: Modifier = Modifier) {
val context = LocalContext.current

ElevatedCard(modifier) {
Expand All @@ -81,10 +118,10 @@ fun LauncherUpdateIndicator(modifier: Modifier = Modifier, openTo: String) {
)

TextButton(
onClick = { downloadUrl(context, openTo) },
onClick = { installLauncherUpdate(context) },
modifier = Modifier.align(Alignment.End)
) {
Text(stringResource(R.string.launcher_download))
Text(stringResource(R.string.launcher_install))
}
}
}
Expand Down Expand Up @@ -248,15 +285,10 @@ fun UpdateCard(releaseViewModel: ReleaseViewModel, modifier: Modifier = Modifier
}

val nextUpdate by releaseViewModel.nextLauncherUpdate.collectAsState()
val nextUpdateValue = nextUpdate

if (!releaseViewModel.isInUpdate && nextUpdateValue != null) {
val updateUrl = nextUpdateValue.getLauncherDownload()?.browserDownloadUrl
?: nextUpdateValue.htmlUrl

if (!releaseViewModel.isInUpdate && nextUpdate != null) {
LauncherUpdateIndicator(
modifier = modifier,
openTo = updateUrl
modifier = modifier
)
}
}
41 changes: 41 additions & 0 deletions app/src/main/java/com/geode/launcher/updater/ReleaseManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,34 @@ class ReleaseManager private constructor(
}
}

private suspend fun downloadLauncherUpdate(release: Release) {
val download = release.getLauncherDownload() ?: return

val outputDirectory = LaunchUtils.getBaseDirectory(applicationContext)
val outputFile = File(outputDirectory, download.name)

if (outputFile.exists()) {
// only download the apk once
return
}

_uiState.value = ReleaseManagerState.InDownload(0, download.size.toLong())

try {
val fileStream = DownloadUtils.downloadStream(
httpClient,
download.browserDownloadUrl
) { progress, outOf ->
_uiState.value = ReleaseManagerState.InDownload(progress, outOf)
}

fileStream.copyTo(outputFile.outputStream())
} catch (e: Exception) {
sendError(e)
return
}
}

private suspend fun performUpdate(release: Release) {
val releaseAsset = release.getGeodeDownload()
if (releaseAsset == null) {
Expand Down Expand Up @@ -170,6 +198,8 @@ class ReleaseManager private constructor(
return
}

downloadLauncherUpdateIfNecessary()

// extraction performed
updatePreferences(release)
_uiState.value = ReleaseManagerState.Finished(true)
Expand Down Expand Up @@ -200,6 +230,11 @@ class ReleaseManager private constructor(
}
}

private suspend fun downloadLauncherUpdateIfNecessary() {
val update = _availableLauncherUpdate.value ?: return
downloadLauncherUpdate(update)
}

private suspend fun checkForNewRelease(allowOverwriting: Boolean = false) {
val release = try {
getLatestRelease()
Expand All @@ -220,6 +255,8 @@ class ReleaseManager private constructor(
}

if (release == null) {
downloadLauncherUpdateIfNecessary()

_uiState.value = ReleaseManagerState.Finished()
return
}
Expand All @@ -234,12 +271,16 @@ class ReleaseManager private constructor(

// check if an update is needed
if (latestVersion == currentVersion && geodeFile.exists()) {
downloadLauncherUpdateIfNecessary()

_uiState.value = ReleaseManagerState.Finished()
return
}

// check if the file was externally modified
if (!allowOverwriting && fileWasExternallyModified()) {
downloadLauncherUpdateIfNecessary()

sendError(UpdateException(UpdateException.Reason.EXTERNAL_FILE_IN_USE))
return
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,8 @@ class PreferenceUtils(private val sharedPreferences: SharedPreferences) {
FORCE_HRR,
ENABLE_REDESIGN,
RELEASE_CHANNEL_TAG,
DEVELOPER_MODE
DEVELOPER_MODE,
CLEANUP_APKS
}

private fun defaultValueForBooleanKey(key: Key): Boolean {
Expand Down Expand Up @@ -175,6 +176,7 @@ class PreferenceUtils(private val sharedPreferences: SharedPreferences) {
Key.ENABLE_REDESIGN -> "PreferenceEnableRedesign"
Key.RELEASE_CHANNEL_TAG -> "PreferenceReleaseChannelTag"
Key.DEVELOPER_MODE -> "PreferenceDeveloperMode"
Key.CLEANUP_APKS -> "PreferenceCleanupPackages"
}
}

Expand Down
3 changes: 2 additions & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<string name="launcher_launch">Launch</string>
<string name="launcher_settings">Settings</string>
<string name="launcher_download">Download</string>
<string name="launcher_install">Install</string>
<string name="launcher_title">Geode</string>
<string name="launcher_logo_alt">geode logo</string>
<string name="launcher_launch_icon_alt">play icon</string>
Expand Down Expand Up @@ -50,7 +51,7 @@
<string name="launcher_notification_settings">Launcher Settings</string>
<string name="launcher_notification_update_failed">Failed to check for updates.</string>
<string name="launcher_notification_update_success">Geode updated!</string>
<string name="launcher_notification_update_cta">Tap to download.</string>
<string name="launcher_notification_update_cta">Tap to install.</string>

<string name="launcher_starting_game">Starting Geometry Dash…</string>
<string name="launcher_downloading_update">Downloading update…</string>
Expand Down

0 comments on commit 759ee4a

Please sign in to comment.