Skip to content

Commit

Permalink
Refactor SplitInstallManager: structural pt. 2
Browse files Browse the repository at this point in the history
  • Loading branch information
fynngodau committed Sep 21, 2024
1 parent 23d4459 commit e434c8c
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ 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 com.android.vending.installer.uninstallPackage
import kotlinx.coroutines.runBlocking
import org.microg.gms.common.DeviceConfiguration
import org.microg.gms.common.asProto
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.android.vending.installer

import android.content.Context
import android.content.Intent
import kotlinx.coroutines.CompletableDeferred
import java.io.File

private const val FILE_SAVE_PATH = "phonesky-download-service"
internal const val TAG = "GmsPackageInstaller"

const val KEY_BYTES_DOWNLOADED = "bytes_downloaded"


fun Context.packageDownloadLocation() = File(filesDir, FILE_SAVE_PATH).apply {
if (!exists()) mkdir()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.android.vending.installer

import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInfo
import android.content.pm.PackageInstaller
import android.content.pm.PackageInstaller.SessionParams
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import com.google.android.finsky.splitinstallservice.SplitInstallManager.InstallResultReceiver
import kotlinx.coroutines.CompletableDeferred
import java.io.File
import java.io.FileInputStream
import java.io.IOException

@RequiresApi(Build.VERSION_CODES.M)
public suspend fun installPackages(context: Context, callingPackage: String, componentFiles: List<File>, isUpdate: Boolean = false, deferredMap: MutableMap<Int, CompletableDeferred<Intent>> = mutableMapOf()): Intent {
Log.v(TAG, "installPackages start")

val packageInstaller = context.packageManager.packageInstaller
val installed = context.packageManager.getInstalledPackages(0).any {
it.applicationInfo.packageName == callingPackage
}
// 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.setInstallLocation(PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY)
try {
@SuppressLint("PrivateApi") val method = SessionParams::class.java.getDeclaredMethod(
"setDontKillApp", Boolean::class.javaPrimitiveType
)
method.invoke(params, true)
} catch (e: Exception) {
Log.w(TAG, "Error setting dontKillApp", e)
}
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) }
session.fsync(outputStream)
}
totalDownloaded += file.length()
file.delete()
}
val deferred = CompletableDeferred<Intent>()
deferredMap[sessionId] = deferred
val intent = Intent(context, InstallResultReceiver::class.java).apply {
putExtra(KEY_BYTES_DOWNLOADED, totalDownloaded)
}
val pendingIntent = PendingIntent.getBroadcast(context, sessionId, intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE)
session.commit(pendingIntent.intentSender)
Log.d(TAG, "installPackages session commit")
return deferred.await()
} catch (e: IOException) {
Log.w(TAG, "Error installing packages", e)
throw e
} finally {
session?.close()
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.google.android.finsky.splitinstallservice
package com.android.vending.installer

import android.app.PendingIntent
import android.content.Context
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@ import com.android.vending.AUTH_TOKEN_SCOPE
import com.android.vending.R
import com.android.vending.buildRequestHeaders
import com.android.vending.getAuthToken
import com.android.vending.installer.KEY_BYTES_DOWNLOADED
import com.android.vending.installer.installPackages
import com.android.vending.installer.packageDownloadLocation
import com.google.android.finsky.GoogleApiResponse
import com.google.android.finsky.splitinstallservice.SplitInstallManager.Companion.deferredMap
import com.google.android.finsky.splitinstallservice.SplitInstallManager.InstallResultReceiver
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
Expand All @@ -53,7 +58,6 @@ private const val NOTIFY_CHANNEL_NAME = "Split Install"
private const val KEY_LANGUAGE = "language"
private const val KEY_LANGUAGES = "languages"
private const val KEY_MODULE_NAME = "module_name"
private const val KEY_BYTES_DOWNLOADED = "bytes_downloaded"
private const val KEY_TOTAL_BYTES_TO_DOWNLOAD = "total_bytes_to_download"
private const val KEY_STATUS = "status"
private const val KEY_ERROR_CODE = "error_code"
Expand All @@ -62,7 +66,6 @@ private const val KEY_SESSION_STATE = "session_state"

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

private const val FILE_SAVE_PATH = "phonesky-download-service"
private const val TAG = "SplitInstallManager"

class SplitInstallManager(val context: Context) {
Expand Down Expand Up @@ -107,7 +110,6 @@ class SplitInstallManager(val context: Context) {
}

internal suspend fun downloadAndInstall(forPackage: String, downloadList: List<PackageComponent>, isUpdate: Boolean = false): Intent? {
if (!context.splitSaveFile().exists()) context.splitSaveFile().mkdir()
val packageFiles = downloadPackageComponents(context, downloadList)

Check failure on line 113 in vending-app/src/main/kotlin/com/google/android/finsky/splitinstallservice/SplitInstallManager.kt

View workflow job for this annotation

GitHub Actions / Gradle build

Call requires API level 23 (current min is 19): downloadPackageComponents [NewApi]

Check failure on line 113 in vending-app/src/main/kotlin/com/google/android/finsky/splitinstallservice/SplitInstallManager.kt

View workflow job for this annotation

GitHub Actions / Gradle build

Call requires API level 23 (current min is 19): downloadPackageComponents [NewApi]
val installFiles = packageFiles.map {
if (it.value == null) {
Expand All @@ -118,7 +120,7 @@ class SplitInstallManager(val context: Context) {
Log.v(TAG, "splitInstallFlow downloaded success, downloaded ${installFiles.size} files")

return runCatching {
installPackages(context, forPackage, installFiles, isUpdate)
installPackages(context, forPackage, installFiles, isUpdate, deferredMap)
}.getOrNull()

}
Expand Down Expand Up @@ -172,7 +174,7 @@ class SplitInstallManager(val context: Context) {
Log.d(TAG, "downloadSplitPackage: $info")
async {
info to runCatching {
val file = File(context.splitSaveFile().toString(), info.componentName)
val file = File(context.packageDownloadLocation().toString(), info.componentName)
httpClient.download(
url = info.url,
downloadFile = file,
Expand All @@ -188,64 +190,6 @@ class SplitInstallManager(val context: Context) {
}.awaitAll().associate { it }
}

@RequiresApi(Build.VERSION_CODES.M)
internal suspend fun installPackages(context: Context, callingPackage: String, componentFiles: List<File>, isUpdate: Boolean = false): Intent {
Log.v(TAG, "installPackages start")

val packageInstaller = context.packageManager.packageInstaller
val installed = context.packageManager.getInstalledPackages(0).any {
it.applicationInfo.packageName == callingPackage
}
// 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.setInstallLocation(PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY)
params.setRequireUserAction(SessionParams.USER_ACTION_NOT_REQUIRED)
try {
@SuppressLint("PrivateApi") val method = SessionParams::class.java.getDeclaredMethod(
"setDontKillApp", Boolean::class.javaPrimitiveType
)
method.invoke(params, true)
} catch (e: Exception) {
Log.w(TAG, "Error setting dontKillApp", e)
}
val sessionId: Int
var session: PackageInstaller.Session? = null
var totalDownloaded = 0L
try {
sessionId = packageInstaller.createSession(params)
session = packageInstaller.openSession(sessionId)
componentFiles.forEach { file ->
val pkgPath = File(context.splitSaveFile(), file.name)
session.openWrite(file.name, 0, -1).use { outputStream ->
FileInputStream(pkgPath).use { inputStream -> inputStream.copyTo(outputStream) }
session.fsync(outputStream)
}
totalDownloaded += pkgPath.length()
pkgPath.delete()
}
val deferred = CompletableDeferred<Intent>()
deferredMap[sessionId] = deferred
val intent = Intent(context, InstallResultReceiver::class.java).apply {
putExtra(KEY_BYTES_DOWNLOADED, totalDownloaded)
}
val pendingIntent = PendingIntent.getBroadcast(context, sessionId, intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE)
session.commit(pendingIntent.intentSender)
Log.d(TAG, "installPackages session commit")
return deferred.await()
} catch (e: IOException) {
Log.w(TAG, "Error installing packages", e)
throw e
} finally {
session?.close()
}
}

// TODO: use existing code
private suspend fun getOauthToken(): String {
val accounts = AccountManager.get(context).getAccountsByType(DEFAULT_ACCOUNT_TYPE)
Expand Down Expand Up @@ -308,7 +252,6 @@ class SplitInstallManager(val context: Context) {
}
}

private fun Context.splitSaveFile() = File(filesDir, FILE_SAVE_PATH)

private fun sendCompleteBroad(context: Context, packageName: String, intent: Intent) {
Log.d(TAG, "sendCompleteBroadcast: intent:$intent")
Expand Down

0 comments on commit e434c8c

Please sign in to comment.