mirror of
https://github.com/Aviortheking/Neo-Store.git
synced 2025-08-16 09:08:49 +00:00
Rename package to com.machaiv3lli.fdroid
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
package com.machiav3lli.fdroid.installer
|
||||
|
||||
import android.content.Context
|
||||
import com.machiav3lli.fdroid.utility.Utils.rootInstallerEnabled
|
||||
|
||||
abstract class AppInstaller {
|
||||
abstract val defaultInstaller: BaseInstaller?
|
||||
|
||||
companion object {
|
||||
@Volatile
|
||||
private var INSTANCE: AppInstaller? = null
|
||||
fun getInstance(context: Context?): AppInstaller? {
|
||||
if (INSTANCE == null) {
|
||||
synchronized(AppInstaller::class.java) {
|
||||
context?.let {
|
||||
INSTANCE = object : AppInstaller() {
|
||||
override val defaultInstaller: BaseInstaller
|
||||
get() {
|
||||
return when (rootInstallerEnabled) {
|
||||
false -> DefaultInstaller(it)
|
||||
true -> RootInstaller(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return INSTANCE
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
package com.machiav3lli.fdroid.installer
|
||||
|
||||
import android.content.Context
|
||||
|
||||
abstract class BaseInstaller(val context: Context) : InstallationEvents {
|
||||
companion object {
|
||||
const val ROOT_INSTALL_PACKAGE = "cat %s | pm install -i %s --user %s -t -r -S %s"
|
||||
const val ROOT_INSTALL_PACKAGE_SESSION_CREATE = "pm install-create -i %s --user %s -r -S %s"
|
||||
const val ROOT_INSTALL_PACKAGE_SESSION_WRITE = "cat %s | pm install-write -S %s %s %s"
|
||||
const val ROOT_INSTALL_PACKAGE_SESSION_COMMIT = "pm install-commit %s"
|
||||
const val ROOT_UNINSTALL_PACKAGE = "pm uninstall --user %s %s"
|
||||
const val DELETE_PACKAGE = "%s rm %s"
|
||||
}
|
||||
}
|
@@ -0,0 +1,127 @@
|
||||
package com.machiav3lli.fdroid.installer
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageInstaller.SessionParams
|
||||
import android.util.Log
|
||||
import com.machiav3lli.fdroid.content.Cache
|
||||
import com.machiav3lli.fdroid.utility.extension.android.Android
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
|
||||
class DefaultInstaller(context: Context) : BaseInstaller(context) {
|
||||
|
||||
private val packageManager = context.packageManager
|
||||
private val sessionInstaller = packageManager.packageInstaller
|
||||
private val intent = Intent(context, InstallerService::class.java)
|
||||
|
||||
companion object {
|
||||
val flags = if (Android.sdk(31)) PendingIntent.FLAG_MUTABLE else 0
|
||||
val sessionParams = SessionParams(SessionParams.MODE_FULL_INSTALL).apply {
|
||||
if (Android.sdk(31)) {
|
||||
setRequireUserAction(SessionParams.USER_ACTION_NOT_REQUIRED)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun install(cacheFileName: String) {
|
||||
val cacheFile = Cache.getReleaseFile(context, cacheFileName)
|
||||
mDefaultInstaller(cacheFile)
|
||||
}
|
||||
|
||||
override suspend fun install(packageName: String, cacheFileName: String) {
|
||||
val cacheFile = Cache.getReleaseFile(context, cacheFileName)
|
||||
// using packageName to store the app's name for the notification later down the line
|
||||
intent.putExtra(InstallerService.KEY_APP_NAME, packageName)
|
||||
mDefaultInstaller(cacheFile)
|
||||
}
|
||||
|
||||
override suspend fun install(packageName: String, cacheFile: File) {
|
||||
intent.putExtra(InstallerService.KEY_APP_NAME, packageName)
|
||||
mDefaultInstaller(cacheFile)
|
||||
}
|
||||
|
||||
override suspend fun uninstall(packageName: String) = mDefaultUninstaller(packageName)
|
||||
|
||||
private fun mDefaultInstaller(cacheFile: File) {
|
||||
// clean up inactive sessions
|
||||
sessionInstaller.mySessions
|
||||
.filter { session -> !session.isActive }
|
||||
.forEach { session ->
|
||||
try {
|
||||
sessionInstaller.abandonSession(session.sessionId)
|
||||
} catch (_: SecurityException) {
|
||||
Log.w(
|
||||
"DefaultInstaller",
|
||||
"Attempted to abandon a session we do not own."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// start new session
|
||||
val id = sessionInstaller.createSession(sessionParams)
|
||||
val session = sessionInstaller.openSession(id)
|
||||
|
||||
// get package name
|
||||
val packageInfo = packageManager.getPackageArchiveInfo(cacheFile.absolutePath, 0)
|
||||
val packageName = packageInfo?.packageName ?: "unknown-package"
|
||||
|
||||
// error flags
|
||||
var hasErrors = false
|
||||
|
||||
session.use { activeSession ->
|
||||
try {
|
||||
activeSession.openWrite(packageName, 0, cacheFile.length()).use { packageStream ->
|
||||
try {
|
||||
cacheFile.inputStream().use { fileStream ->
|
||||
fileStream.copyTo(packageStream)
|
||||
}
|
||||
} catch (_: FileNotFoundException) {
|
||||
Log.w(
|
||||
"DefaultInstaller",
|
||||
"Cache file does not seem to exist."
|
||||
)
|
||||
hasErrors = true
|
||||
} catch (_: IOException) {
|
||||
Log.w(
|
||||
"DefaultInstaller",
|
||||
"Failed to perform cache to package copy due to a bad pipe."
|
||||
)
|
||||
hasErrors = true
|
||||
}
|
||||
}
|
||||
} catch (_: SecurityException) {
|
||||
Log.w(
|
||||
"DefaultInstaller",
|
||||
"Attempted to use a destroyed or sealed session when installing."
|
||||
)
|
||||
hasErrors = true
|
||||
} catch (_: IOException) {
|
||||
Log.w(
|
||||
"DefaultInstaller",
|
||||
"Couldn't open up active session file for copying install data."
|
||||
)
|
||||
hasErrors = true
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasErrors) {
|
||||
session.commit(PendingIntent.getService(context, id, intent, flags).intentSender)
|
||||
cacheFile.delete()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun mDefaultUninstaller(packageName: String) {
|
||||
intent.putExtra(InstallerService.KEY_ACTION, InstallerService.ACTION_UNINSTALL)
|
||||
|
||||
val pendingIntent = PendingIntent.getService(context, -1, intent, flags)
|
||||
|
||||
withContext(Dispatchers.Default) {
|
||||
sessionInstaller.uninstall(packageName, pendingIntent.intentSender)
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,65 @@
|
||||
package com.machiav3lli.fdroid.installer
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.core.net.toUri
|
||||
import com.machiav3lli.fdroid.content.Cache
|
||||
import com.machiav3lli.fdroid.utility.extension.android.Android
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
|
||||
// TODO: Use this for MIUI device instead of guiding new users
|
||||
class DefaultInstallerOld(context: Context) : BaseInstaller(context) {
|
||||
override suspend fun install(cacheFileName: String) {
|
||||
val cacheFile = Cache.getReleaseFile(context, cacheFileName)
|
||||
mOldDefaultInstaller(cacheFile)
|
||||
}
|
||||
|
||||
override suspend fun install(packageName: String, cacheFileName: String) {
|
||||
val cacheFile = Cache.getReleaseFile(context, cacheFileName)
|
||||
mOldDefaultInstaller(cacheFile)
|
||||
}
|
||||
|
||||
override suspend fun install(packageName: String, cacheFile: File) =
|
||||
mOldDefaultInstaller(cacheFile)
|
||||
|
||||
override suspend fun uninstall(packageName: String) = mOldDefaultUninstaller(packageName)
|
||||
|
||||
private suspend fun mOldDefaultInstaller(cacheFile: File) {
|
||||
val (uri, flags) = if (Android.sdk(24)) {
|
||||
Pair(
|
||||
Cache.getReleaseFile(context, cacheFile.name).toUri(),
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
)
|
||||
} else {
|
||||
Pair(Uri.fromFile(cacheFile), 0)
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
withContext(Dispatchers.IO) {
|
||||
context.startActivity(
|
||||
Intent(Intent.ACTION_INSTALL_PACKAGE)
|
||||
.setDataAndType(uri, "application/vnd.android.package-archive")
|
||||
.setFlags(flags)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun mOldDefaultUninstaller(packageName: String) {
|
||||
val uri = Uri.fromParts("package", packageName, null)
|
||||
val intent = Intent()
|
||||
intent.data = uri
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
if (Android.sdk(28)) {
|
||||
intent.action = Intent.ACTION_DELETE
|
||||
} else {
|
||||
intent.action = Intent.ACTION_UNINSTALL_PACKAGE
|
||||
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true)
|
||||
}
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
withContext(Dispatchers.IO) { context.startActivity(intent) }
|
||||
}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
package com.machiav3lli.fdroid.installer
|
||||
|
||||
import java.io.File
|
||||
|
||||
interface InstallationEvents {
|
||||
suspend fun install(cacheFileName: String)
|
||||
|
||||
suspend fun install(packageName: String, cacheFileName: String)
|
||||
|
||||
suspend fun install(packageName: String, cacheFile: File)
|
||||
|
||||
suspend fun uninstall(packageName: String)
|
||||
}
|
@@ -0,0 +1,100 @@
|
||||
package com.machiav3lli.fdroid.installer
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageInstaller
|
||||
import android.net.Uri
|
||||
import android.os.IBinder
|
||||
import androidx.lifecycle.LifecycleService
|
||||
import com.machiav3lli.fdroid.NOTIFICATION_CHANNEL_INSTALLER
|
||||
import com.machiav3lli.fdroid.R
|
||||
import com.machiav3lli.fdroid.ui.activities.MainActivityX
|
||||
import com.machiav3lli.fdroid.utility.Utils
|
||||
import com.machiav3lli.fdroid.utility.extension.android.Android
|
||||
import com.machiav3lli.fdroid.utility.extension.android.notificationManager
|
||||
import com.machiav3lli.fdroid.utility.notifyStatus
|
||||
|
||||
/**
|
||||
* Runs during or after a PackageInstaller session in order to handle completion, failure, or
|
||||
* interruptions requiring user intervention, such as the package installer prompt.
|
||||
*/
|
||||
class InstallerService : LifecycleService() {
|
||||
companion object {
|
||||
const val KEY_ACTION = "installerAction"
|
||||
const val KEY_APP_NAME = "appName"
|
||||
const val ACTION_UNINSTALL = "uninstall"
|
||||
const val INSTALLED_NOTIFICATION_TIMEOUT: Long = 5000
|
||||
const val NOTIFICATION_TAG_PREFIX = "install-"
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
if (Android.sdk(26)) {
|
||||
NotificationChannel(
|
||||
NOTIFICATION_CHANNEL_INSTALLER,
|
||||
getString(R.string.syncing), NotificationManager.IMPORTANCE_LOW
|
||||
)
|
||||
.let(notificationManager::createNotificationChannel)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
super.onStartCommand(intent, flags, startId)
|
||||
val status = intent?.getIntExtra(PackageInstaller.EXTRA_STATUS, -1)
|
||||
|
||||
// only trigger a prompt if in foreground, otherwise make notification
|
||||
if (Utils.inForeground() && status == PackageInstaller.STATUS_PENDING_USER_ACTION) {
|
||||
// Triggers the installer prompt and "unknown apps" prompt if needed
|
||||
val promptIntent: Intent? = intent.getParcelableExtra(Intent.EXTRA_INTENT)
|
||||
|
||||
promptIntent?.let {
|
||||
it.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
|
||||
it.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, "com.android.vending")
|
||||
it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
|
||||
startActivity(it)
|
||||
}
|
||||
} else {
|
||||
notifyStatus(intent)
|
||||
}
|
||||
|
||||
stopSelf()
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
super.onBind(intent)
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an intent that provides the specified activity information necessary to trigger
|
||||
* the package manager's prompt, thus completing a staged installation requiring user
|
||||
* intervention.
|
||||
*
|
||||
* @param intent the intent provided by PackageInstaller to the callback target passed to
|
||||
* PackageInstaller.Session.commit().
|
||||
* @return a pending intent that can be attached to a background-accessible entry point such as
|
||||
* a notification
|
||||
*/
|
||||
fun installIntent(intent: Intent): PendingIntent {
|
||||
// prepare prompt intent
|
||||
val promptIntent: Intent? = intent.getParcelableExtra(Intent.EXTRA_INTENT)
|
||||
val name = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)
|
||||
|
||||
return PendingIntent.getActivity(
|
||||
this,
|
||||
0,
|
||||
Intent(this, MainActivityX::class.java)
|
||||
.setAction(MainActivityX.ACTION_INSTALL)
|
||||
.setData(Uri.parse("package:$name"))
|
||||
.putExtra(Intent.EXTRA_INTENT, promptIntent)
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
|
||||
if (Android.sdk(23)) PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
else PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
}
|
||||
}
|
@@ -0,0 +1,129 @@
|
||||
package com.machiav3lli.fdroid.installer
|
||||
|
||||
import android.content.Context
|
||||
import com.machiav3lli.fdroid.BuildConfig
|
||||
import com.machiav3lli.fdroid.content.Cache
|
||||
import com.machiav3lli.fdroid.content.Preferences
|
||||
import com.machiav3lli.fdroid.utility.extension.android.Android
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.util.regex.Pattern
|
||||
|
||||
class RootInstaller(context: Context) : BaseInstaller(context) {
|
||||
|
||||
companion object {
|
||||
private val getCurrentUserState: String =
|
||||
if (Android.sdk(25)) Shell.su("am get-current-user").exec().out[0]
|
||||
else Shell.su("dumpsys activity | grep -E \"mUserLru\"")
|
||||
.exec().out[0].trim()
|
||||
.removePrefix("mUserLru: [").removeSuffix("]")
|
||||
|
||||
private val String.quote
|
||||
get() = "\"${this.replace(Regex("""[\\$"`]""")) { c -> "\\${c.value}" }}\""
|
||||
|
||||
private val getUtilBoxPath: String
|
||||
get() {
|
||||
listOf("toybox", "busybox").forEach {
|
||||
val shellResult = Shell.su("which $it").exec()
|
||||
if (shellResult.out.isNotEmpty()) {
|
||||
val utilBoxPath = shellResult.out.joinToString("")
|
||||
if (utilBoxPath.isNotEmpty()) return utilBoxPath.quote
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
val File.install
|
||||
get() = String.format(
|
||||
ROOT_INSTALL_PACKAGE,
|
||||
absolutePath,
|
||||
BuildConfig.APPLICATION_ID,
|
||||
getCurrentUserState,
|
||||
length()
|
||||
)
|
||||
|
||||
val File.session_install_create
|
||||
get() = String.format(
|
||||
ROOT_INSTALL_PACKAGE_SESSION_CREATE,
|
||||
BuildConfig.APPLICATION_ID,
|
||||
getCurrentUserState,
|
||||
length()
|
||||
)
|
||||
|
||||
fun File.sessionInstallWrite(session_id: Int) = String.format(
|
||||
ROOT_INSTALL_PACKAGE_SESSION_WRITE,
|
||||
absolutePath,
|
||||
length(),
|
||||
session_id,
|
||||
name
|
||||
)
|
||||
|
||||
fun sessionInstallCommit(session_id: Int) = String.format(
|
||||
ROOT_INSTALL_PACKAGE_SESSION_COMMIT,
|
||||
session_id
|
||||
)
|
||||
|
||||
val String.uninstall
|
||||
get() = String.format(
|
||||
ROOT_UNINSTALL_PACKAGE,
|
||||
getCurrentUserState,
|
||||
this
|
||||
)
|
||||
|
||||
val File.deletePackage
|
||||
get() = String.format(
|
||||
DELETE_PACKAGE,
|
||||
getUtilBoxPath,
|
||||
absolutePath.quote
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun install(cacheFileName: String) {
|
||||
val cacheFile = Cache.getReleaseFile(context, cacheFileName)
|
||||
mRootInstaller(cacheFile)
|
||||
}
|
||||
|
||||
override suspend fun install(packageName: String, cacheFileName: String) {
|
||||
val cacheFile = Cache.getReleaseFile(context, cacheFileName)
|
||||
mRootInstaller(cacheFile)
|
||||
}
|
||||
|
||||
override suspend fun install(packageName: String, cacheFile: File) =
|
||||
mRootInstaller(cacheFile)
|
||||
|
||||
override suspend fun uninstall(packageName: String) = mRootUninstaller(packageName)
|
||||
|
||||
private suspend fun mRootInstaller(cacheFile: File) {
|
||||
withContext(Dispatchers.Default) {
|
||||
if (Preferences[Preferences.Key.RootSessionInstaller]) {
|
||||
Shell.su(cacheFile.session_install_create)
|
||||
.submit {
|
||||
val sessionIdPattern = Pattern.compile("(\\d+)")
|
||||
val sessionIdMatcher = sessionIdPattern.matcher(it.out[0])
|
||||
val found = sessionIdMatcher.find()
|
||||
|
||||
if (found) {
|
||||
val sessionId = sessionIdMatcher?.group(1)?.toInt() ?: -1
|
||||
Shell.su(cacheFile.sessionInstallWrite(sessionId))
|
||||
.submit {
|
||||
Shell.su(sessionInstallCommit(sessionId)).exec()
|
||||
if (it.isSuccess) Shell.su(cacheFile.deletePackage).submit()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
Shell.su(cacheFile.install)
|
||||
.submit { if (it.isSuccess) Shell.su(cacheFile.deletePackage).submit() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun mRootUninstaller(packageName: String) {
|
||||
withContext(Dispatchers.Default) {
|
||||
Shell.su(packageName.uninstall).submit()
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user