Rename package to com.machaiv3lli.fdroid

This commit is contained in:
machiav3lli
2022-07-05 03:21:35 +02:00
parent b8deae87ea
commit c482580686
126 changed files with 689 additions and 684 deletions

View File

@@ -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
}
}
}

View File

@@ -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"
}
}

View File

@@ -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)
}
}
}

View File

@@ -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) }
}
}

View File

@@ -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)
}

View File

@@ -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
)
}
}

View File

@@ -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()
}
}
}