Skip to content

Commit

Permalink
Refactor SplitInstallManager: data types
Browse files Browse the repository at this point in the history
  • Loading branch information
fynngodau committed Sep 21, 2024
1 parent 7ab6cf1 commit c3398b9
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 47 deletions.
17 changes: 11 additions & 6 deletions vending-app/src/main/java/org/microg/vending/ui/VendingActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import com.android.vending.RequestItem
import com.android.vending.buildRequestHeaders
import com.android.volley.VolleyError
import com.google.android.finsky.GoogleApiResponse
import com.google.android.finsky.splitinstallservice.DownloadStatus
import com.google.android.finsky.splitinstallservice.PackageComponent
import com.google.android.finsky.splitinstallservice.SplitInstallManager
import com.google.android.finsky.splitinstallservice.uninstallPackage
import kotlinx.coroutines.runBlocking
Expand Down Expand Up @@ -94,16 +96,19 @@ class VendingActivity : ComponentActivity() {
)

Log.d(TAG, res.toString())
val triples = setOf(Triple("base", res.response!!.splitReqResult!!.pkgList!!.baseUrl!!, 0)) +
res.response!!.splitReqResult!!.pkgList!!.pkgDownLoadInfo!!.map {
Triple(it.splitPkgName!!, it.downloadUrl!!, 0)
}
val components = listOf(
PackageComponent(app.packageName, "base", res.response!!.splitReqResult!!.pkgList!!.baseUrl!!)
) + res.response.splitReqResult!!.pkgList!!.pkgDownLoadInfo.map {
PackageComponent(app.packageName, it.splitPkgName!!, it.downloadUrl!!)
}

if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
SplitInstallManager(this@VendingActivity).apply {
triples.forEach { updateSplitInstallRecord(app.packageName, it) }
components.forEach {
SplitInstallManager.splitInstallRecord[it] = DownloadStatus.PENDING
}
notify(this@VendingActivity)
installSplitPackage(this@VendingActivity, app.packageName, triples, isUpdate)
installSplitPackage(this@VendingActivity, app.packageName, components, isUpdate)
}
} else {
TODO("implement installation on Lollipop devices")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.google.android.finsky.splitinstallservice

enum class DownloadStatus {
PENDING,
FAILED,
COMPLETE
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.google.android.finsky.splitinstallservice

data class PackageComponent(
val packageName: String,
val componentName: String,
val url: String
)
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,6 @@ private const val KEY_ERROR_CODE = "error_code"
private const val KEY_SESSION_ID = "session_id"
private const val KEY_SESSION_STATE = "session_state"

private const val STATUS_UNKNOWN = -1
private const val STATUS_DOWNLOADING = 0
private const val STATUS_DOWNLOADED = 1

private const val ACTION_UPDATE_SERVICE = "com.google.android.play.core.splitinstall.receiver.SplitInstallUpdateIntentService"

private const val FILE_SAVE_PATH = "phonesky-download-service"
Expand Down Expand Up @@ -91,24 +87,24 @@ class SplitInstallManager(val context: Context) {
Log.d(TAG, "startInstall oauthToken: $oauthToken")
if (oauthToken.isNullOrEmpty()) return false
notify(context)
val triples = runCatching { requestDownloadUrls(callingPackage, oauthToken, needInstallSplitPack) }.getOrNull()
Log.w(TAG, "startInstall requestDownloadUrls triples: $triples")
if (triples.isNullOrEmpty()) {
val components = runCatching { requestDownloadUrls(callingPackage, oauthToken, needInstallSplitPack) }.getOrNull()
Log.w(TAG, "startInstall requestDownloadUrls triples: $components")
if (components.isNullOrEmpty()) {
NotificationManagerCompat.from(context).cancel(SPLIT_INSTALL_NOTIFY_ID)
return false
}
val intent = runCatching { installSplitPackage(context, callingPackage, triples) }.getOrNull()
val intent = runCatching { installSplitPackage(context, callingPackage, components) }.getOrNull()
NotificationManagerCompat.from(context).cancel(SPLIT_INSTALL_NOTIFY_ID)
if (intent == null) { return false }
sendCompleteBroad(context, callingPackage, intent)
return true
}

@RequiresApi(Build.VERSION_CODES.M)
internal suspend fun installSplitPackage(context: Context, callingPackage: String, downloadList: Set<Triple<String, String, Int>>, isUpdate: Boolean = false): Intent {
internal suspend fun installSplitPackage(context: Context, callingPackage: String, downloadList: List<PackageComponent>, isUpdate: Boolean = false): Intent {
Log.d(TAG, "installSplitPackage start ")
if (!context.splitSaveFile().exists()) context.splitSaveFile().mkdir()
val downloadSplitPackage = downloadSplitPackage(context, callingPackage, downloadList)
val downloadSplitPackage = downloadSplitPackage(context, downloadList)
if (!downloadSplitPackage) {
Log.w(TAG, "installSplitPackage download failed")
throw RuntimeException("installSplitPackage downloadSplitPackage has error")
Expand Down Expand Up @@ -142,8 +138,8 @@ class SplitInstallManager(val context: Context) {
sessionId = packageInstaller.createSession(params)
session = packageInstaller.openSession(sessionId)
downloadList.forEach { item ->
val pkgPath = File(context.splitSaveFile().toString(), item.first)
session.openWrite(item.first, 0, -1).use { outputStream ->
val pkgPath = File(context.splitSaveFile().toString(), item.componentName)
session.openWrite(item.componentName, 0, -1).use { outputStream ->
FileInputStream(pkgPath).use { inputStream -> inputStream.copyTo(outputStream) }
session.fsync(outputStream)
}
Expand All @@ -155,7 +151,8 @@ class SplitInstallManager(val context: Context) {
val intent = Intent(context, InstallResultReceiver::class.java).apply {
putExtra(KEY_BYTES_DOWNLOADED, totalDownloaded)
}
val pendingIntent = PendingIntent.getBroadcast(context, sessionId, intent, 0)
val pendingIntent = PendingIntent.getBroadcast(context, sessionId, intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
session.commit(pendingIntent.intentSender)
Log.d(TAG, "installSplitPackage session commit")
return deferred.await()
Expand All @@ -168,24 +165,28 @@ class SplitInstallManager(val context: Context) {
}

@RequiresApi(Build.VERSION_CODES.M)
private suspend fun downloadSplitPackage(context: Context, callingPackage: String, downloadList: Set<Triple<String, String, Int>>): Boolean =
private suspend fun downloadSplitPackage(
context: Context,
downloadList: List<PackageComponent>
): Boolean =
coroutineScope {
val results = downloadList.map { info ->
Log.d(TAG, "downloadSplitPackage: $info")
async {
val downloaded = runCatching {
httpClient.download(info.second, File(context.splitSaveFile().toString(), info.first), SPLIT_INSTALL_REQUEST_TAG)
runCatching {
httpClient.download(info.url, File(context.splitSaveFile().toString(), info.componentName), SPLIT_INSTALL_REQUEST_TAG)
}.onFailure {
Log.w(TAG, "downloadSplitPackage url:${info.second} save:${info.first}", it)
}.getOrNull() != null
downloaded.also { updateSplitInstallRecord(callingPackage, Triple(info.first, info.second, if (it) STATUS_DOWNLOADED else STATUS_UNKNOWN)) }
Log.w(TAG, "downloadSplitPackage url:${info.url} save:${info.componentName}", it)
}.isSuccess.also { downloadSuccessful ->
splitInstallRecord[info] = if (downloadSuccessful) DownloadStatus.COMPLETE else DownloadStatus.FAILED
}
}
}.awaitAll()
return@coroutineScope results.all { it }
}

@RequiresApi(Build.VERSION_CODES.M)
private suspend fun requestDownloadUrls(callingPackage: String, authToken: String, packs: MutableSet<String>): ArraySet<Triple<String, String, Int>> {
private suspend fun requestDownloadUrls(callingPackage: String, authToken: String, packs: MutableSet<String>): MutableList<PackageComponent> {
val versionCode = PackageInfoCompat.getLongVersionCode(context.packageManager.getPackageInfo(callingPackage, 0))
val requestUrl =
StringBuilder("$URL_DELIVERY?doc=$callingPackage&ot=1&vc=$versionCode&bvc=$versionCode&pf=1&pf=2&pf=3&pf=4&pf=5&pf=7&pf=8&pf=9&pf=10&da=4&bda=4&bf=4&fdcf=1&fdcf=2&ch=")
Expand All @@ -200,18 +201,23 @@ class SplitInstallManager(val context: Context) {
)
Log.d(TAG, "requestDownloadUrls end response -> $response")
val splitPkgInfoList = response.response?.splitReqResult?.pkgList?.pkgDownLoadInfo ?: throw RuntimeException("splitPkgInfoList is null")
val packSet = ArraySet<Triple<String, String, Int>>()
val components: MutableList<PackageComponent> = mutableListOf();
splitPkgInfoList.filter {
!it.splitPkgName.isNullOrEmpty() && !it.downloadUrl.isNullOrEmpty()
}.forEach { info ->
packs.filter {
it.contains(info.splitPkgName!!)
}.forEach {
packSet.add(Triple(first = it, second = info.downloadUrl!!, STATUS_DOWNLOADING))
components.add(PackageComponent(callingPackage, it, info.downloadUrl!!))
}
}
Log.d(TAG, "requestDownloadUrls end packSet -> $packSet")
return packSet.onEach { updateSplitInstallRecord(callingPackage, it) }

Log.d(TAG, "requestDownloadUrls end -> $components")
components.forEach {
splitInstallRecord[it] = DownloadStatus.PENDING
}

return components
}

private suspend fun getOauthToken(): String {
Expand All @@ -236,21 +242,10 @@ class SplitInstallManager(val context: Context) {

@RequiresApi(Build.VERSION_CODES.M)
private fun checkSplitInstalled(callingPackage: String, splitName: String): Boolean {
if (!splitInstallRecord.containsKey(callingPackage)) return false
return splitInstallRecord[callingPackage]?.find { it.first == splitName }?.third != STATUS_UNKNOWN
}

@RequiresApi(Build.VERSION_CODES.M)
internal fun updateSplitInstallRecord(callingPackage: String, triple: Triple<String, String, Int>) {
splitInstallRecord[callingPackage]?.let { triples ->
val find = triples.find { it.first == triple.first }
find?.let { triples.remove(it) }
triples.add(triple)
} ?: run {
val triples = ArraySet<Triple<String, String, Int>>()
triples.add(triple)
splitInstallRecord[callingPackage] = triples
}
return splitInstallRecord.keys.find { it.packageName == callingPackage && it.componentName == splitName }
?.let {
splitInstallRecord[it] != DownloadStatus.FAILED
} ?: false
}

internal fun notify(context: Context) {
Expand Down Expand Up @@ -338,8 +333,8 @@ class SplitInstallManager(val context: Context) {
}

companion object {
// Installation records, including subpackage name, download path, and installation status
private val splitInstallRecord = HashMap<String, ArraySet<Triple<String, String, Int>>>()
// Installation records, including (sub)package name, download path, and installation status
internal val splitInstallRecord: MutableMap<PackageComponent, DownloadStatus> = mutableMapOf()
private val deferredMap = mutableMapOf<Int, CompletableDeferred<Intent>>()
}
}

0 comments on commit c3398b9

Please sign in to comment.