IInstaller: Support installing shared libraries via supported installers
Session, Shizuku and Root Installer are supported for now. Native and Aurora Services have been deprecated and won't be supported. App Manager might support it or it will be deprecated as well (if it doesn't). Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
@@ -34,7 +34,7 @@ class InstallActivity : AppCompatActivity() {
|
||||
override fun onProgressChanged(sessionId: Int, progress: Float) {}
|
||||
|
||||
override fun onFinished(sessionId: Int, success: Boolean) {
|
||||
if (sessionInstaller.sessionId == sessionId) {
|
||||
if (sessionInstaller.parentSessionId == sessionId) {
|
||||
Log.i(TAG, "Install finished with status code: $success")
|
||||
finish()
|
||||
}
|
||||
|
||||
@@ -24,9 +24,9 @@ import com.aurora.store.R
|
||||
import com.aurora.store.data.event.InstallerEvent
|
||||
import com.aurora.store.data.room.download.Download
|
||||
import com.aurora.store.util.Log
|
||||
import com.aurora.store.util.PackageUtil.isSharedLibraryInstalled
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import java.io.File
|
||||
import java.util.regex.Pattern
|
||||
|
||||
class RootInstaller(context: Context) : InstallerBase(context) {
|
||||
@@ -36,7 +36,13 @@ class RootInstaller(context: Context) : InstallerBase(context) {
|
||||
Log.i("${download.packageName} already queued")
|
||||
} else {
|
||||
if (Shell.getShell().isRoot) {
|
||||
xInstall(download.packageName, getFiles(download.packageName, download.versionCode))
|
||||
download.sharedLibs.forEach {
|
||||
// Shared library packages cannot be updated
|
||||
if (!isSharedLibraryInstalled(context, it.packageName, it.versionCode)) {
|
||||
xInstall(download.packageName, download.versionCode, it.packageName)
|
||||
}
|
||||
}
|
||||
xInstall(download.packageName, download.versionCode)
|
||||
} else {
|
||||
postError(
|
||||
download.packageName,
|
||||
@@ -48,10 +54,10 @@ class RootInstaller(context: Context) : InstallerBase(context) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun xInstall(packageName: String, files: List<File>) {
|
||||
private fun xInstall(packageName: String, versionCode: Int, sharedLibPkgName: String = "") {
|
||||
var totalSize = 0
|
||||
|
||||
for (file in files)
|
||||
for (file in getFiles(packageName, versionCode, sharedLibPkgName))
|
||||
totalSize += file.length().toInt()
|
||||
|
||||
val result: Shell.Result =
|
||||
@@ -67,7 +73,7 @@ class RootInstaller(context: Context) : InstallerBase(context) {
|
||||
if (found) {
|
||||
val sessionId = sessionIdMatcher.group(1)?.toInt()
|
||||
if (Shell.getShell().isRoot && sessionId != null) {
|
||||
for (file in files) {
|
||||
for (file in getFiles(packageName, versionCode, sharedLibPkgName)) {
|
||||
Shell.cmd("cat \"${file.absoluteFile}\" | pm install-write -S ${file.length()} $sessionId \"${file.name}\"")
|
||||
.exec()
|
||||
}
|
||||
|
||||
@@ -20,23 +20,31 @@
|
||||
|
||||
package com.aurora.store.data.installer
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageInstaller
|
||||
import android.content.pm.PackageInstaller.PACKAGE_SOURCE_STORE
|
||||
import android.content.pm.PackageInstaller.SessionParams
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Process
|
||||
import com.aurora.extensions.isNAndAbove
|
||||
import com.aurora.extensions.isOAndAbove
|
||||
import com.aurora.extensions.isSAndAbove
|
||||
import com.aurora.extensions.isTAndAbove
|
||||
import com.aurora.extensions.isUAndAbove
|
||||
import com.aurora.store.data.receiver.InstallerStatusReceiver
|
||||
import com.aurora.store.data.room.download.Download
|
||||
import com.aurora.store.util.Log
|
||||
import com.aurora.store.util.PackageUtil.isSharedLibraryInstalled
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
class SessionInstaller(context: Context) : InstallerBase(context) {
|
||||
|
||||
class SessionInstaller(context: Context) : SessionInstallerBase(context) {
|
||||
var parentSessionId by Delegates.notNull<Int>()
|
||||
|
||||
var sessionId by Delegates.notNull<Int>()
|
||||
private val packageInstaller = context.packageManager.packageInstaller
|
||||
private val sessionIdMap = mutableMapOf<Int, String>()
|
||||
|
||||
override fun install(download: Download) {
|
||||
if (isAlreadyQueued(download.packageName)) {
|
||||
@@ -44,35 +52,109 @@ class SessionInstaller(context: Context) : SessionInstallerBase(context) {
|
||||
} else {
|
||||
Log.i("Received session install request for ${download.packageName}")
|
||||
|
||||
val packageInstaller = context.packageManager.packageInstaller
|
||||
val sessionParams = SessionParams(SessionParams.MODE_FULL_INSTALL).apply {
|
||||
setAppPackageName(download.packageName)
|
||||
if (isOAndAbove()) {
|
||||
setInstallReason(PackageManager.INSTALL_REASON_USER)
|
||||
}
|
||||
if (isNAndAbove()) {
|
||||
setOriginatingUid(android.os.Process.myUid())
|
||||
}
|
||||
if (isSAndAbove()) {
|
||||
setRequireUserAction(SessionParams.USER_ACTION_NOT_REQUIRED)
|
||||
}
|
||||
if (isTAndAbove()) {
|
||||
setPackageSource(PACKAGE_SOURCE_STORE)
|
||||
}
|
||||
if (isUAndAbove()) {
|
||||
setInstallerPackageName(context.packageName)
|
||||
val callback = object : PackageInstaller.SessionCallback() {
|
||||
override fun onCreated(sessionId: Int) {}
|
||||
|
||||
override fun onBadgingChanged(sessionId: Int) {}
|
||||
|
||||
override fun onActiveChanged(sessionId: Int, active: Boolean) {}
|
||||
|
||||
override fun onProgressChanged(sessionId: Int, progress: Float) {}
|
||||
|
||||
override fun onFinished(sessionId: Int, success: Boolean) {
|
||||
if (sessionId in sessionIdMap.keys && success) {
|
||||
sessionIdMap.remove(sessionId)
|
||||
if (sessionIdMap.isNotEmpty()) {
|
||||
val nextSession = sessionIdMap.keys.first()
|
||||
commitInstall(sessionIdMap.getValue(nextSession), nextSession)
|
||||
} else {
|
||||
packageInstaller.unregisterSessionCallback(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sessionId = packageInstaller.createSession(sessionParams)
|
||||
val session = packageInstaller.openSession(sessionId)
|
||||
download.sharedLibs.forEach {
|
||||
// Shared library packages cannot be updated
|
||||
if (!isSharedLibraryInstalled(context, it.packageName, it.versionCode)) {
|
||||
stageInstall(download.packageName, download.versionCode, it.packageName)
|
||||
}
|
||||
}
|
||||
stageInstall(download.packageName, download.versionCode)
|
||||
|
||||
xInstall(
|
||||
sessionId,
|
||||
session,
|
||||
download.packageName,
|
||||
getFiles(download.packageName, download.versionCode)
|
||||
if (sessionIdMap.size > 1) packageInstaller.registerSessionCallback(callback)
|
||||
commitInstall(
|
||||
sessionIdMap.getValue(sessionIdMap.keys.first()),
|
||||
sessionIdMap.keys.first()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun stageInstall(packageName: String, versionCode: Int, sharedLibPkgName: String = "") {
|
||||
val packageInstaller = context.packageManager.packageInstaller
|
||||
|
||||
val sessionParams = SessionParams(SessionParams.MODE_FULL_INSTALL).apply {
|
||||
setAppPackageName(sharedLibPkgName.ifBlank { packageName })
|
||||
if (isOAndAbove()) {
|
||||
setInstallReason(PackageManager.INSTALL_REASON_USER)
|
||||
}
|
||||
if (isNAndAbove()) {
|
||||
setOriginatingUid(Process.myUid())
|
||||
}
|
||||
if (isSAndAbove()) {
|
||||
setRequireUserAction(SessionParams.USER_ACTION_NOT_REQUIRED)
|
||||
}
|
||||
if (isTAndAbove()) {
|
||||
setPackageSource(PACKAGE_SOURCE_STORE)
|
||||
}
|
||||
if (isUAndAbove()) {
|
||||
setInstallerPackageName(context.packageName)
|
||||
}
|
||||
}
|
||||
val sessionId = packageInstaller.createSession(sessionParams)
|
||||
val session = packageInstaller.openSession(sessionId)
|
||||
|
||||
try {
|
||||
Log.i("Writing splits to session for $packageName")
|
||||
getFiles(packageName, versionCode, sharedLibPkgName).forEach {
|
||||
it.inputStream().use { input ->
|
||||
session.openWrite("${sharedLibPkgName.ifBlank { packageName }}_${System.currentTimeMillis()}", 0, -1).use { output ->
|
||||
input.copyTo(output)
|
||||
session.fsync(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add session to list of staged sessions
|
||||
sessionIdMap[sessionId] = packageName
|
||||
if (sharedLibPkgName.isBlank()) parentSessionId = sessionId
|
||||
} catch (exception: Exception) {
|
||||
session.abandon()
|
||||
removeFromInstallQueue(packageName)
|
||||
postError(packageName, exception.localizedMessage, exception.stackTraceToString())
|
||||
}
|
||||
}
|
||||
|
||||
private fun commitInstall(packageName: String, sessionId: Int) {
|
||||
Log.i("Starting install session for $packageName")
|
||||
val session = packageInstaller.openSession(sessionId)
|
||||
session.commit(getCallBackIntent(packageName).intentSender)
|
||||
session.close()
|
||||
}
|
||||
|
||||
private fun getCallBackIntent(packageName: String): PendingIntent {
|
||||
val callBackIntent = Intent(context, InstallerStatusReceiver::class.java).apply {
|
||||
action = InstallerStatusReceiver.ACTION_INSTALL_STATUS
|
||||
setPackage(context.packageName)
|
||||
putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName)
|
||||
addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
|
||||
}
|
||||
val flags = if (isSAndAbove()) {
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
|
||||
} else {
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
}
|
||||
|
||||
return PendingIntent.getBroadcast(context, parentSessionId, callBackIntent, flags)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
* Copyright (C) 2023, grrfe <grrfe@420blaze.it>
|
||||
*
|
||||
* Aurora Store is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Aurora Store is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.data.installer
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageInstaller
|
||||
import com.aurora.extensions.isSAndAbove
|
||||
import com.aurora.store.data.receiver.InstallerStatusReceiver
|
||||
import com.aurora.store.util.Log
|
||||
import java.io.File
|
||||
|
||||
abstract class SessionInstallerBase(context: Context) : InstallerBase(context) {
|
||||
|
||||
protected fun xInstall(
|
||||
sessionId: Int,
|
||||
session: PackageInstaller.Session,
|
||||
packageName: String,
|
||||
files: List<File>
|
||||
) {
|
||||
|
||||
try {
|
||||
Log.i("Writing splits to session for $packageName")
|
||||
|
||||
files.forEach {
|
||||
it.inputStream().use { input ->
|
||||
session.openWrite("${packageName}_${System.currentTimeMillis()}", 0, -1).use { output ->
|
||||
input.copyTo(output)
|
||||
session.fsync(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val callBackIntent = Intent(context, InstallerStatusReceiver::class.java).apply {
|
||||
action = InstallerStatusReceiver.ACTION_INSTALL_STATUS
|
||||
setPackage(context.packageName)
|
||||
putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName)
|
||||
addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
|
||||
}
|
||||
val flags = if (isSAndAbove())
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE else
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
|
||||
val pendingIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
sessionId,
|
||||
callBackIntent,
|
||||
flags
|
||||
)
|
||||
|
||||
Log.i("Starting install session for $packageName")
|
||||
session.commit(pendingIntent.intentSender)
|
||||
session.close()
|
||||
} catch (e: Exception) {
|
||||
session.abandon()
|
||||
removeFromInstallQueue(packageName)
|
||||
|
||||
postError(
|
||||
packageName,
|
||||
e.localizedMessage,
|
||||
e.stackTraceToString()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,9 @@
|
||||
|
||||
package com.aurora.store.data.installer
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.*
|
||||
import android.content.pm.PackageInstaller.SessionParams
|
||||
import android.os.Build
|
||||
@@ -29,13 +31,16 @@ import androidx.annotation.RequiresApi
|
||||
import com.aurora.extensions.isOAndAbove
|
||||
import com.aurora.extensions.isSAndAbove
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.receiver.InstallerStatusReceiver
|
||||
import com.aurora.store.data.room.download.Download
|
||||
import com.aurora.store.util.Log
|
||||
import com.aurora.store.util.PackageUtil.isSharedLibraryInstalled
|
||||
import dev.rikka.tools.refine.Refine
|
||||
import rikka.shizuku.ShizukuBinderWrapper
|
||||
import rikka.shizuku.SystemServiceHelper
|
||||
|
||||
class ShizukuInstaller(context: Context) : SessionInstallerBase(context) {
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
class ShizukuInstaller(context: Context) : InstallerBase(context) {
|
||||
|
||||
companion object {
|
||||
const val SHIZUKU_PACKAGE_NAME = "moe.shizuku.privileged.api"
|
||||
@@ -65,48 +70,89 @@ class ShizukuInstaller(context: Context) : SessionInstallerBase(context) {
|
||||
} else null
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
override fun install(download: Download) {
|
||||
if (isAlreadyQueued(download.packageName)) {
|
||||
Log.i("${download.packageName} already queued")
|
||||
} else {
|
||||
Log.i("Received session install request for ${download.packageName}")
|
||||
download.sharedLibs.forEach {
|
||||
// Shared library packages cannot be updated
|
||||
if (!isSharedLibraryInstalled(context, it.packageName, it.versionCode)) {
|
||||
install(download.packageName, download.versionCode, it.packageName)
|
||||
}
|
||||
}
|
||||
install(download.packageName, download.versionCode)
|
||||
}
|
||||
}
|
||||
|
||||
val (sessionId, session) = kotlin.runCatching {
|
||||
val params = SessionParams(SessionParams.MODE_FULL_INSTALL)
|
||||
private fun install(packageName: String, versionCode: Int, sharedLibPkgName: String = "") {
|
||||
Log.i("Received session install request for ${sharedLibPkgName.ifBlank { packageName }}")
|
||||
|
||||
// Replace existing app (Updates)
|
||||
var flags = Refine
|
||||
.unsafeCast<PackageInstallerHidden.SessionParamsHidden>(params).installFlags
|
||||
flags = flags or PackageManagerHidden.INSTALL_REPLACE_EXISTING
|
||||
Refine.unsafeCast<PackageInstallerHidden.SessionParamsHidden>(params).installFlags =
|
||||
flags
|
||||
val (sessionId, session) = kotlin.runCatching {
|
||||
val params = SessionParams(SessionParams.MODE_FULL_INSTALL)
|
||||
|
||||
val sessionId = packageInstaller!!.createSession(params)
|
||||
val iSession = IPackageInstallerSession.Stub.asInterface(
|
||||
iPackageInstaller.openSession(sessionId).asShizukuBinder()
|
||||
)
|
||||
val session = Refine.unsafeCast<PackageInstaller.Session>(
|
||||
PackageInstallerHidden.SessionHidden(iSession)
|
||||
)
|
||||
// Replace existing app (Updates)
|
||||
var flags = Refine
|
||||
.unsafeCast<PackageInstallerHidden.SessionParamsHidden>(params).installFlags
|
||||
flags = flags or PackageManagerHidden.INSTALL_REPLACE_EXISTING
|
||||
Refine.unsafeCast<PackageInstallerHidden.SessionParamsHidden>(params).installFlags =
|
||||
flags
|
||||
|
||||
sessionId to session
|
||||
}.getOrElse { ex ->
|
||||
ex.printStackTrace()
|
||||
postError(
|
||||
download.packageName,
|
||||
context.getString(R.string.installer_status_failure),
|
||||
context.getString(R.string.installer_shizuku_unavailable)
|
||||
)
|
||||
return
|
||||
val sessionId = packageInstaller!!.createSession(params)
|
||||
val iSession = IPackageInstallerSession.Stub.asInterface(
|
||||
iPackageInstaller.openSession(sessionId).asShizukuBinder()
|
||||
)
|
||||
val session = Refine.unsafeCast<PackageInstaller.Session>(
|
||||
PackageInstallerHidden.SessionHidden(iSession)
|
||||
)
|
||||
|
||||
sessionId to session
|
||||
}.getOrElse { ex ->
|
||||
ex.printStackTrace()
|
||||
postError(
|
||||
packageName,
|
||||
context.getString(R.string.installer_status_failure),
|
||||
context.getString(R.string.installer_shizuku_unavailable)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
Log.i("Writing splits to session for ${sharedLibPkgName.ifBlank { packageName }}")
|
||||
getFiles(packageName, versionCode, sharedLibPkgName).forEach {
|
||||
it.inputStream().use { input ->
|
||||
session.openWrite("${sharedLibPkgName.ifBlank { packageName }}_${System.currentTimeMillis()}", 0, -1).use { output ->
|
||||
input.copyTo(output)
|
||||
session.fsync(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xInstall(
|
||||
val callBackIntent = Intent(context, InstallerStatusReceiver::class.java).apply {
|
||||
action = InstallerStatusReceiver.ACTION_INSTALL_STATUS
|
||||
setPackage(context.packageName)
|
||||
addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
|
||||
putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, sharedLibPkgName.ifBlank { packageName })
|
||||
}
|
||||
val flags = if (isSAndAbove()) {
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
|
||||
} else {
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
}
|
||||
|
||||
val pendingIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
sessionId,
|
||||
session,
|
||||
download.packageName,
|
||||
getFiles(download.packageName, download.versionCode)
|
||||
callBackIntent,
|
||||
flags
|
||||
)
|
||||
|
||||
Log.i("Starting install session for $packageName")
|
||||
session.commit(pendingIntent.intentSender)
|
||||
session.close()
|
||||
} catch (exception: Exception) {
|
||||
session.abandon()
|
||||
removeFromInstallQueue(packageName)
|
||||
postError(packageName, exception.localizedMessage, exception.stackTraceToString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,11 +32,14 @@ import com.aurora.store.data.model.DownloadStatus
|
||||
import com.aurora.store.data.room.download.Download
|
||||
import com.aurora.store.util.DownloadWorkerUtil
|
||||
import com.aurora.store.util.NotificationUtil
|
||||
import com.aurora.store.util.PackageUtil.isSharedLibrary
|
||||
import com.aurora.store.util.PathUtil
|
||||
import com.aurora.store.util.Preferences
|
||||
import com.aurora.store.util.Preferences.PREFERENCE_AUTO_DELETE
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
@@ -56,6 +59,9 @@ open class PackageManagerReceiver : BroadcastReceiver() {
|
||||
if (intent.action != null && intent.data != null) {
|
||||
val packageName = intent.data!!.encodedSchemeSpecificPart
|
||||
|
||||
// We don't care about shared libraries, bail out
|
||||
if (isSharedLibrary(context, packageName)) return@goAsync
|
||||
|
||||
when (intent.action) {
|
||||
Intent.ACTION_PACKAGE_ADDED -> {
|
||||
Log.i(TAG, "Installed $packageName")
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.aurora.extensions.isSAndAbove
|
||||
import com.aurora.extensions.requiresObbDir
|
||||
import com.aurora.gplayapi.helpers.PurchaseHelper
|
||||
import com.aurora.store.data.installer.AppInstaller
|
||||
import com.aurora.store.data.installer.SessionInstaller
|
||||
import com.aurora.store.data.model.DownloadInfo
|
||||
import com.aurora.store.data.model.DownloadStatus
|
||||
import com.aurora.store.data.model.Request
|
||||
@@ -42,6 +43,7 @@ import java.io.File
|
||||
import java.net.URL
|
||||
import java.security.DigestInputStream
|
||||
import java.security.MessageDigest
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.NonCancellable
|
||||
import kotlin.properties.Delegates
|
||||
import com.aurora.gplayapi.data.models.File as GPlayFile
|
||||
@@ -159,9 +161,11 @@ class DownloadWorker @AssistedInject constructor(
|
||||
}
|
||||
|
||||
private suspend fun onSuccess() {
|
||||
withContext(NonCancellable) {
|
||||
val installer = appInstaller.getPreferredInstaller()
|
||||
val dispatcher = if (installer is SessionInstaller) Dispatchers.Main else NonCancellable
|
||||
withContext(dispatcher) {
|
||||
try {
|
||||
appInstaller.getPreferredInstaller().install(download)
|
||||
installer.install(download)
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Failed to install ${download.packageName}", exception)
|
||||
}
|
||||
|
||||
@@ -53,14 +53,22 @@ object PackageUtil {
|
||||
}
|
||||
}
|
||||
|
||||
fun isSharedLibrary(context: Context, packageName: String): Boolean {
|
||||
return if (isOAndAbove()) {
|
||||
getAllSharedLibraries(context).any { it.name == packageName }
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun isSharedLibraryInstalled(context: Context, packageName: String, versionCode: Int): Boolean {
|
||||
if (isOAndAbove()) {
|
||||
return if (isOAndAbove()) {
|
||||
val sharedLibraries = getAllSharedLibraries(context)
|
||||
return sharedLibraries.any {
|
||||
sharedLibraries.any {
|
||||
it.name == packageName && it.version == versionCode
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user