-
Notifications
You must be signed in to change notification settings - Fork 218
/
SessionInstaller.kt
113 lines (91 loc) · 3.73 KB
/
SessionInstaller.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
package com.apkupdater.util
import android.app.PendingIntent
import android.app.PendingIntent.FLAG_MUTABLE
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInstaller
import android.net.Uri
import android.os.Build
import android.provider.Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES
import androidx.core.content.ContextCompat.startActivity
import com.apkupdater.BuildConfig
import com.apkupdater.ui.activity.MainActivity
import eu.chainfire.libsuperuser.Shell
import java.io.File
import java.io.InputStream
import java.util.concurrent.atomic.AtomicBoolean
import java.util.zip.ZipFile
class SessionInstaller(private val context: Context) {
companion object {
const val InstallAction = "installAction"
}
private val installMutex = AtomicBoolean(false)
suspend fun install(id: Int, packageName: String, stream: InputStream) =
install(id, packageName, listOf(stream))
private suspend fun install(id: Int, packageName: String, streams: List<InputStream>) {
val packageInstaller: PackageInstaller = context.packageManager.packageInstaller
val params = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
params.setAppPackageName(packageName)
if (Build.VERSION.SDK_INT > 24) {
params.setOriginatingUid(android.os.Process.myUid())
}
if (Build.VERSION.SDK_INT >= 31) {
params.setRequireUserAction(PackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED)
}
if (Build.VERSION.SDK_INT >= 33) {
params.setPackageSource(PackageInstaller.PACKAGE_SOURCE_STORE)
}
val sessionId = packageInstaller.createSession(params)
val session = packageInstaller.openSession(sessionId)
streams.forEach {
session.openWrite("$packageName.${randomUUID()}", 0, -1).use { output ->
it.copyTo(output)
it.close()
session.fsync(output)
}
}
val intent = Intent(context, MainActivity::class.java).apply {
action = "$InstallAction.$id"
}
installMutex.lock()
val pending = PendingIntent.getActivity(context, 0, intent, FLAG_MUTABLE)
session.commit(pending.intentSender)
session.close()
}
fun rootInstall(file: File): Boolean {
val res = Shell.Pool.SU.run("pm install -r ${file.absolutePath}") == 0
file.delete()
return res
}
fun finish() = installMutex.unlock()
fun checkPermission(): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if(!context.packageManager.canRequestPackageInstalls()) {
val uri = Uri.parse("package:${BuildConfig.APPLICATION_ID}")
val intent = Intent(ACTION_MANAGE_UNKNOWN_APP_SOURCES, uri)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(context, intent, null)
return false
}
}
return true
}
@Suppress("BlockingMethodInNonBlockingContext")
suspend fun installXapk(id: Int, packageName: String, stream: InputStream) {
// Copy file to disk.
// TODO: Find a way to do this without saving file
val file = File(context.cacheDir, randomUUID())
stream.copyTo(file.outputStream())
// Get entries
val zip = ZipFile(file)
val entries = zip.entries().toList()
// Install all the apks
// TODO: Try to install only needed apks
// TODO: Add root install support
val apks = entries.filter { it.name.contains(".apk") }.map { zip.getInputStream(it) }
install(id, packageName, apks)
// Cleanup
zip.close()
file.delete()
}
}