Skip to content

Commit

Permalink
Install packages directly to improve performance
Browse files Browse the repository at this point in the history
* reduces temporary storage usage
* speeds up installation by a little bit
  • Loading branch information
fynngodau committed Sep 25, 2024
1 parent ec18cdb commit 0f64199
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import org.microg.gms.utils.singleInstanceOf
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.OutputStream

private const val POST_TIMEOUT = 8000L

Expand All @@ -41,18 +42,24 @@ class HttpClient {
url: String,
downloadFile: File,
params: Map<String, String> = emptyMap()
): File {
): File = downloadFile.also { toFile ->
val parentDir = downloadFile.getParentFile()
if (parentDir != null && !parentDir.exists() && !parentDir.mkdirs()) {
throw IOException("Failed to create directories: ${parentDir.absolutePath}")
}

FileOutputStream(toFile).use { download(url, it, params) }
}

suspend fun download(
url: String,
downloadTo: OutputStream,
params: Map<String, String> = emptyMap()
) {
client.prepareGet(url.asUrl(params)).execute { response ->
val parentDir = downloadFile.getParentFile()
if (parentDir != null && !parentDir.exists() && !parentDir.mkdirs()) {
throw IOException("Failed to create directories: ${parentDir.absolutePath}")
}
val body: ByteReadChannel = response.body()
FileOutputStream(downloadFile).use { out ->
body.copyTo(out = out)
}
body.copyTo(out = downloadTo)
}
return downloadFile
}

suspend fun <O> get(
Expand Down

This file was deleted.

18 changes: 7 additions & 11 deletions vending-app/src/main/java/org/microg/vending/ui/VendingActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.android.vending.buildRequestHeaders
import com.android.vending.installer.installPackages
import com.android.vending.installer.installPackagesFromNetwork
import com.android.vending.installer.uninstallPackage
import kotlinx.coroutines.runBlocking
import org.microg.gms.common.DeviceConfiguration
Expand Down Expand Up @@ -104,22 +105,17 @@ class VendingActivity : ComponentActivity() {
return@runBlocking
}

val packageFiles = client.downloadPackageComponents(this@VendingActivity, downloadUrls.getOrThrow(), Unit)
if (packageFiles.values.any { it == null }) {
Log.w(TAG, "Cannot proceed to installation as not all files were downloaded")
apps[app] = previousState
return@runBlocking
}

runCatching {
installPackages(
app.packageName,
packageFiles.values.filterNotNull(),
isUpdate
installPackagesFromNetwork(
packageName = app.packageName,
components = downloadUrls.getOrThrow(),
httpClient = client,
isUpdate = isUpdate
)
}.onSuccess {
apps[app] = AppState.INSTALLED
}.onFailure {
Log.w(TAG, "Installation from network unsuccessful.")
apps[app] = previousState
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,65 @@ import android.content.pm.PackageInstaller.SessionParams
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import com.google.android.finsky.splitinstallservice.PackageComponent
import kotlinx.coroutines.CompletableDeferred
import org.microg.vending.billing.core.HttpClient
import java.io.File
import java.io.FileInputStream
import java.io.IOException
import java.io.OutputStream

@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
internal suspend fun Context.installPackages(
callingPackage: String,
packageName: String,
componentFiles: List<File>,
isUpdate: Boolean = false
) = installPackagesInternal(
packageName = packageName,
componentNames = componentFiles.map { it.name },
isUpdate = isUpdate
) { fileName, to ->
val component = componentFiles.find { it.name == fileName }!!
FileInputStream(component).use { it.copyTo(to) }
component.delete()
}

@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
internal suspend fun Context.installPackagesFromNetwork(
packageName: String,
components: List<PackageComponent>,
httpClient: HttpClient = HttpClient(),
isUpdate: Boolean = false
) = installPackagesInternal(
packageName = packageName,
componentNames = components.map { it.componentName },
isUpdate = isUpdate
) { fileName, to ->
val component = components.find { it.componentName == fileName }!!
Log.v(TAG, "installing $fileName for $packageName from network")
httpClient.download(component.url, to)
}

@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private suspend fun Context.installPackagesInternal(
packageName: String,
componentNames: List<String>,
isUpdate: Boolean = false,
writeComponent: suspend (componentName: String, to: OutputStream) -> Unit
) {
Log.v(TAG, "installPackages start")

val packageInstaller = packageManager.packageInstaller
val installed = packageManager.getInstalledPackages(0).any {
it.applicationInfo.packageName == callingPackage
it.applicationInfo.packageName == packageName
}
// Contrary to docs, MODE_INHERIT_EXISTING cannot be used if package is not yet installed.
val params = SessionParams(
if (!installed || isUpdate) SessionParams.MODE_FULL_INSTALL
else SessionParams.MODE_INHERIT_EXISTING
)
params.setAppPackageName(callingPackage)
params.setAppLabel(callingPackage + "Subcontracting")
params.setAppPackageName(packageName)
params.setAppLabel(packageName + "Subcontracting")
params.setInstallLocation(PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY)
try {
@SuppressLint("PrivateApi") val method = SessionParams::class.java.getDeclaredMethod(
Expand All @@ -45,17 +80,14 @@ internal suspend fun Context.installPackages(
}
val sessionId: Int
var session: PackageInstaller.Session? = null
var totalDownloaded = 0L
try {
sessionId = packageInstaller.createSession(params)
session = packageInstaller.openSession(sessionId)
componentFiles.forEach { file ->
session.openWrite(file.name, 0, -1).use { outputStream ->
FileInputStream(file).use { inputStream -> inputStream.copyTo(outputStream) }
componentNames.forEach { component ->
session.openWrite(component, 0, -1).use { outputStream ->
writeComponent(component, outputStream)
session.fsync(outputStream)
}
totalDownloaded += file.length()
file.delete()
}
val deferred = CompletableDeferred<Unit>()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import androidx.core.content.pm.PackageInfoCompat
import com.android.vending.R
import com.android.vending.installer.KEY_BYTES_DOWNLOADED
import com.android.vending.installer.installPackages
import com.android.vending.installer.installPackagesFromNetwork
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
Expand Down Expand Up @@ -92,29 +93,22 @@ class SplitInstallManager(val context: Context) {
splitInstallRecord[it] = DownloadStatus.PENDING
}

val packageFiles = httpClient.downloadPackageComponents(context, components, SPLIT_LANGUAGE_TAG)

packageFiles.forEach { (component, downloadFile) ->
splitInstallRecord[component] = if (downloadFile != null) DownloadStatus.COMPLETE else DownloadStatus.FAILED
}

val installFiles = packageFiles.map {
if (it.value == null) {
Log.w(TAG, "splitInstallFlow download failed, as ${it.key} was not downloaded")
throw RuntimeException("installSplitPackage downloadSplitPackage has error")
} else it.value!!
}
Log.v(TAG, "splitInstallFlow downloaded success, downloaded ${installFiles.size} files")

val success = runCatching {
context.installPackages(callingPackage, installFiles, false)
context.installPackagesFromNetwork(
packageName = callingPackage,
components = components,
httpClient = httpClient,
isUpdate = false
)
}.isSuccess

NotificationManagerCompat.from(context).cancel(SPLIT_INSTALL_NOTIFY_ID)
return if (success) {
sendCompleteBroad(context, callingPackage, components.sumOf { it.size.toLong() })
components.forEach { splitInstallRecord[it] = DownloadStatus.COMPLETE }
true
} else {
components.forEach { splitInstallRecord[it] = DownloadStatus.FAILED }
false
}
}
Expand Down

0 comments on commit 0f64199

Please sign in to comment.