2022-12-11 17:14:09 +00:00
|
|
|
package com.lagradost.cloudstream3.utils
|
|
|
|
|
|
|
|
import android.app.PendingIntent
|
|
|
|
import android.content.BroadcastReceiver
|
|
|
|
import android.content.Context
|
|
|
|
import android.content.Intent
|
|
|
|
import android.content.IntentFilter
|
2023-01-03 19:56:03 +00:00
|
|
|
import android.content.IntentSender
|
2022-12-11 17:14:09 +00:00
|
|
|
import android.content.pm.PackageInstaller
|
|
|
|
import android.os.Build
|
2023-01-03 19:56:03 +00:00
|
|
|
import android.widget.Toast
|
|
|
|
import com.lagradost.cloudstream3.R
|
2022-12-11 17:14:09 +00:00
|
|
|
import com.lagradost.cloudstream3.mvvm.logError
|
2023-01-03 19:56:03 +00:00
|
|
|
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
|
|
|
import com.lagradost.cloudstream3.utils.Coroutines.main
|
2022-12-11 17:14:09 +00:00
|
|
|
import java.io.InputStream
|
|
|
|
|
|
|
|
const val INSTALL_ACTION = "ApkInstaller.INSTALL_ACTION"
|
|
|
|
|
|
|
|
|
|
|
|
class ApkInstaller(private val service: PackageInstallerService) {
|
2023-01-03 19:56:03 +00:00
|
|
|
|
|
|
|
companion object {
|
|
|
|
/**
|
|
|
|
* Used for postponed installations
|
|
|
|
**/
|
|
|
|
var delayedInstaller: DelayedInstaller? = null
|
|
|
|
}
|
|
|
|
|
|
|
|
inner class DelayedInstaller(
|
|
|
|
private val session: PackageInstaller.Session,
|
|
|
|
private val intent: IntentSender
|
|
|
|
) {
|
|
|
|
fun startInstallation(): Boolean {
|
|
|
|
return try {
|
|
|
|
session.commit(intent)
|
|
|
|
true
|
|
|
|
} catch (e: Exception) {
|
|
|
|
false
|
|
|
|
}.also { delayedInstaller = null }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-11 17:14:09 +00:00
|
|
|
private val packageInstaller = service.packageManager.packageInstaller
|
|
|
|
|
|
|
|
enum class InstallProgressStatus {
|
|
|
|
Preparing,
|
|
|
|
Downloading,
|
|
|
|
Installing,
|
|
|
|
Failed,
|
|
|
|
}
|
|
|
|
|
|
|
|
private val installActionReceiver = object : BroadcastReceiver() {
|
|
|
|
override fun onReceive(context: Context, intent: Intent) {
|
|
|
|
when (intent.getIntExtra(
|
|
|
|
PackageInstaller.EXTRA_STATUS,
|
|
|
|
PackageInstaller.STATUS_FAILURE
|
|
|
|
)) {
|
|
|
|
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
|
|
|
|
val userAction = intent.getParcelableExtra<Intent>(Intent.EXTRA_INTENT)
|
|
|
|
userAction?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
|
|
context.startActivity(userAction)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fun installApk(
|
|
|
|
context: Context,
|
|
|
|
inputStream: InputStream,
|
|
|
|
size: Long,
|
|
|
|
installProgress: (bytesRead: Int) -> Unit,
|
|
|
|
installProgressStatus: (InstallProgressStatus) -> Unit
|
|
|
|
) {
|
|
|
|
installProgressStatus.invoke(InstallProgressStatus.Preparing)
|
|
|
|
var activeSession: Int? = null
|
|
|
|
|
|
|
|
try {
|
|
|
|
val installParams =
|
|
|
|
PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
|
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
|
|
installParams.setRequireUserAction(PackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED)
|
|
|
|
}
|
|
|
|
|
|
|
|
activeSession = packageInstaller.createSession(installParams)
|
|
|
|
installParams.setSize(size)
|
|
|
|
|
|
|
|
val session = packageInstaller.openSession(activeSession)
|
|
|
|
installProgressStatus.invoke(InstallProgressStatus.Downloading)
|
|
|
|
|
|
|
|
session.openWrite(context.packageName, 0, size)
|
|
|
|
.use { outputStream ->
|
2023-02-07 16:01:14 +00:00
|
|
|
val buffer = ByteArray(4 * 1024)
|
2022-12-11 17:14:09 +00:00
|
|
|
var bytesRead = inputStream.read(buffer)
|
|
|
|
|
|
|
|
while (bytesRead >= 0) {
|
|
|
|
outputStream.write(buffer, 0, bytesRead)
|
|
|
|
bytesRead = inputStream.read(buffer)
|
|
|
|
installProgress.invoke(bytesRead)
|
|
|
|
}
|
|
|
|
|
2023-02-07 16:01:14 +00:00
|
|
|
session.fsync(outputStream)
|
2022-12-11 17:14:09 +00:00
|
|
|
inputStream.close()
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
val intentSender = PendingIntent.getBroadcast(
|
|
|
|
service,
|
|
|
|
activeSession,
|
|
|
|
Intent(INSTALL_ACTION),
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) PendingIntent.FLAG_MUTABLE else 0,
|
|
|
|
).intentSender
|
|
|
|
|
2023-01-03 19:56:03 +00:00
|
|
|
// Use delayed installations on android 13 and only if "allow from unknown sources" is enabled
|
|
|
|
// if the app lacks installation permission it cannot ask for the permission when it's closed.
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
|
|
|
|
context.packageManager.canRequestPackageInstalls()
|
|
|
|
) {
|
|
|
|
// Save for later installation since it's more jarring to have the app exit abruptly
|
|
|
|
delayedInstaller = DelayedInstaller(session, intentSender)
|
|
|
|
main {
|
|
|
|
// Use real toast since it should show even if app is exited
|
|
|
|
Toast.makeText(context, R.string.delayed_update_notice, Toast.LENGTH_LONG)
|
|
|
|
.show()
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
installProgressStatus.invoke(InstallProgressStatus.Installing)
|
|
|
|
session.commit(intentSender)
|
|
|
|
}
|
2022-12-11 17:14:09 +00:00
|
|
|
} catch (e: Exception) {
|
|
|
|
logError(e)
|
|
|
|
|
|
|
|
service.unregisterReceiver(installActionReceiver)
|
|
|
|
installProgressStatus.invoke(InstallProgressStatus.Failed)
|
|
|
|
|
|
|
|
activeSession?.let { sessionId ->
|
|
|
|
packageInstaller.abandonSession(sessionId)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
init {
|
|
|
|
service.registerReceiver(installActionReceiver, IntentFilter(INSTALL_ACTION))
|
|
|
|
service.receivers.add(installActionReceiver)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|