mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-07-13 13:26:43 +02:00
Now it looks good
This commit is contained in:
@ -1,10 +1,5 @@
|
||||
package dev.imranr.obtainium
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import io.flutter.plugin.common.MethodChannel.Result
|
||||
import androidx.annotation.NonNull
|
||||
import android.content.Intent
|
||||
import android.content.IntentSender
|
||||
import android.content.pm.IPackageInstaller
|
||||
@ -15,119 +10,95 @@ import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Process
|
||||
import rikka.shizuku.Shizuku
|
||||
import rikka.shizuku.Shizuku.OnBinderDeadListener
|
||||
import rikka.shizuku.Shizuku.OnBinderReceivedListener
|
||||
import rikka.shizuku.Shizuku.OnRequestPermissionResultListener
|
||||
import rikka.shizuku.ShizukuBinderWrapper
|
||||
import androidx.annotation.NonNull
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import dev.imranr.obtainium.util.IIntentSenderAdaptor
|
||||
import dev.imranr.obtainium.util.IntentSenderUtils
|
||||
import dev.imranr.obtainium.util.PackageInstallerUtils
|
||||
import dev.imranr.obtainium.util.ShizukuSystemServerApi
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import io.flutter.plugin.common.MethodChannel.Result
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import rikka.shizuku.Shizuku
|
||||
import rikka.shizuku.Shizuku.OnRequestPermissionResultListener
|
||||
import rikka.shizuku.ShizukuBinderWrapper
|
||||
|
||||
class MainActivity: FlutterActivity() {
|
||||
private val installersChannel = "installers"
|
||||
private val SHIZUKU_PERMISSION_REQUEST_CODE = 839 // random num
|
||||
private var shizukuBinderAlive = false
|
||||
private var shizukuPermissionGranted = false
|
||||
private var installersChannel: MethodChannel? = null
|
||||
private val SHIZUKU_PERMISSION_REQUEST_CODE = (10..200).random()
|
||||
|
||||
private val shizukuBinderReceivedListener = OnBinderReceivedListener {
|
||||
if(!Shizuku.isPreV11()) { // pre 11 unsupported
|
||||
shizukuBinderAlive = true
|
||||
private fun shizukuCheckPermission() {
|
||||
try {
|
||||
if (Shizuku.isPreV11()) { // Unsupported
|
||||
installersChannel!!.invokeMethod("resPermShizuku", mapOf("res" to -1))
|
||||
} else if (Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED) {
|
||||
installersChannel!!.invokeMethod("resPermShizuku", mapOf("res" to 1))
|
||||
} else if (Shizuku.shouldShowRequestPermissionRationale()) { // Deny and don't ask again
|
||||
installersChannel!!.invokeMethod("resPermShizuku", mapOf("res" to 0))
|
||||
} else {
|
||||
Shizuku.requestPermission(SHIZUKU_PERMISSION_REQUEST_CODE)
|
||||
}
|
||||
} catch (_: Exception) { // If shizuku not running
|
||||
installersChannel!!.invokeMethod("resPermShizuku", mapOf("res" to -1))
|
||||
}
|
||||
}
|
||||
|
||||
private val shizukuBinderDeadListener = OnBinderDeadListener { shizukuBinderAlive = false }
|
||||
|
||||
private val shizukuRequestPermissionResultListener = OnRequestPermissionResultListener {
|
||||
requestCode: Int, grantResult: Int ->
|
||||
if (requestCode == SHIZUKU_PERMISSION_REQUEST_CODE) {
|
||||
shizukuPermissionGranted = grantResult == PackageManager.PERMISSION_GRANTED
|
||||
val res = if (grantResult == PackageManager.PERMISSION_GRANTED) 1 else 0
|
||||
installersChannel!!.invokeMethod("resPermShizuku", mapOf("res" to res))
|
||||
}
|
||||
}
|
||||
|
||||
private fun shizukuCheckPermission() {
|
||||
if(Shizuku.isPreV11()) {
|
||||
shizukuPermissionGranted = false
|
||||
} else if (Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED) {
|
||||
shizukuPermissionGranted = true
|
||||
} else if (Shizuku.shouldShowRequestPermissionRationale()) { // Deny and don't ask again
|
||||
shizukuPermissionGranted = false
|
||||
} else {
|
||||
Shizuku.requestPermission(SHIZUKU_PERMISSION_REQUEST_CODE)
|
||||
}
|
||||
}
|
||||
|
||||
private fun shizukuInstallApk(uri: Uri) {
|
||||
val packageInstaller: PackageInstaller
|
||||
private fun shizukuInstallApk(apkFileUri: String, result: Result) {
|
||||
val uri = Uri.parse(apkFileUri)
|
||||
var res = false
|
||||
var session: PackageInstaller.Session? = null
|
||||
val cr = contentResolver
|
||||
val res = StringBuilder()
|
||||
val installerPackageName: String
|
||||
var installerAttributionTag: String? = null
|
||||
val userId: Int
|
||||
val isRoot: Boolean
|
||||
try {
|
||||
val _packageInstaller: IPackageInstaller =
|
||||
val iPackageInstaller: IPackageInstaller =
|
||||
ShizukuSystemServerApi.PackageManager_getPackageInstaller()
|
||||
isRoot = Shizuku.getUid() == 0
|
||||
|
||||
// the reason for use "com.android.shell" as installer package under adb is that getMySessions will check installer package's owner
|
||||
installerPackageName = if (isRoot) packageName else "com.android.shell"
|
||||
val isRoot = Shizuku.getUid() == 0
|
||||
// The reason for use "com.android.shell" as installer package under adb
|
||||
// is that getMySessions will check installer package's owner
|
||||
val installerPackageName = if (isRoot) packageName else "com.android.shell"
|
||||
var installerAttributionTag: String? = null
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
installerAttributionTag = attributionTag
|
||||
}
|
||||
userId = if (isRoot) Process.myUserHandle().hashCode() else 0
|
||||
packageInstaller = PackageInstallerUtils.createPackageInstaller(
|
||||
_packageInstaller,
|
||||
installerPackageName,
|
||||
installerAttributionTag,
|
||||
userId
|
||||
)
|
||||
val sessionId: Int
|
||||
res.append("createSession: ")
|
||||
val userId = if (isRoot) Process.myUserHandle().hashCode() else 0
|
||||
val packageInstaller = PackageInstallerUtils.createPackageInstaller(
|
||||
iPackageInstaller, installerPackageName, installerAttributionTag, userId)
|
||||
val params =
|
||||
PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
|
||||
var installFlags: Int = PackageInstallerUtils.getInstallFlags(params)
|
||||
installFlags =
|
||||
installFlags or (0x00000004 /*PackageManager.INSTALL_ALLOW_TEST*/ or 0x00000002) /*PackageManager.INSTALL_REPLACE_EXISTING*/
|
||||
installFlags = installFlags or 0x00000004 // PackageManager.INSTALL_ALLOW_TEST
|
||||
PackageInstallerUtils.setInstallFlags(params, installFlags)
|
||||
sessionId = packageInstaller.createSession(params)
|
||||
res.append(sessionId).append('\n')
|
||||
res.append('\n').append("write: ")
|
||||
val _session = IPackageInstallerSession.Stub.asInterface(
|
||||
ShizukuBinderWrapper(
|
||||
_packageInstaller.openSession(sessionId).asBinder()
|
||||
)
|
||||
)
|
||||
session = PackageInstallerUtils.createSession(_session)
|
||||
val name = "apk.apk"
|
||||
val `is` = cr.openInputStream(uri)
|
||||
val os = session.openWrite(name, 0, -1)
|
||||
val buf = ByteArray(8192)
|
||||
var len: Int
|
||||
val sessionId = packageInstaller.createSession(params)
|
||||
val iSession = IPackageInstallerSession.Stub.asInterface(
|
||||
ShizukuBinderWrapper(iPackageInstaller.openSession(sessionId).asBinder()))
|
||||
session = PackageInstallerUtils.createSession(iSession)
|
||||
val inputStream = contentResolver.openInputStream(uri)
|
||||
val openedSession = session.openWrite("apk.apk", 0, -1)
|
||||
val buffer = ByteArray(8192)
|
||||
var length: Int
|
||||
try {
|
||||
while (`is`!!.read(buf).also { len = it } > 0) {
|
||||
os.write(buf, 0, len)
|
||||
os.flush()
|
||||
session.fsync(os)
|
||||
while (inputStream!!.read(buffer).also { length = it } > 0) {
|
||||
openedSession.write(buffer, 0, length)
|
||||
openedSession.flush()
|
||||
session.fsync(openedSession)
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
`is`!!.close()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
try {
|
||||
os.close()
|
||||
inputStream!!.close()
|
||||
openedSession.close()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
res.append('\n').append("commit: ")
|
||||
val results = arrayOf<Intent?>(null)
|
||||
val countDownLatch = CountDownLatch(1)
|
||||
val intentSender: IntentSender =
|
||||
@ -139,66 +110,62 @@ class MainActivity: FlutterActivity() {
|
||||
})
|
||||
session.commit(intentSender)
|
||||
countDownLatch.await()
|
||||
val result = results[0]
|
||||
val status =
|
||||
result!!.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE)
|
||||
val message = result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)
|
||||
res.append('\n').append("status: ").append(status).append(" (").append(message)
|
||||
.append(")")
|
||||
} catch (tr: Throwable) {
|
||||
tr.printStackTrace()
|
||||
res.append(tr)
|
||||
res = results[0]!!.getIntExtra(
|
||||
PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE) == 0
|
||||
} catch (_: Exception) {
|
||||
res = false
|
||||
} finally {
|
||||
if (session != null) {
|
||||
try {
|
||||
session.close()
|
||||
} catch (tr: Throwable) {
|
||||
res.append(tr)
|
||||
} catch (_: Exception) {
|
||||
res = false
|
||||
}
|
||||
}
|
||||
}
|
||||
result.success(res)
|
||||
}
|
||||
|
||||
private fun installWithShizuku(apkFilePath: String, result: Result) {
|
||||
shizukuCheckPermission()
|
||||
shizukuInstallApk(Uri.parse("file://$apkFilePath"))
|
||||
result.success(0)
|
||||
private fun rootCheckPermission(result: Result) {
|
||||
Shell.getShell(Shell.GetShellCallback(
|
||||
fun(shell: Shell) {
|
||||
result.success(shell.isRoot)
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
private fun installWithRoot(apkFilePath: String, result: Result) {
|
||||
Shell.sh("pm install -r -t " + apkFilePath).submit { out ->
|
||||
private fun rootInstallApk(apkFilePath: String, result: Result) {
|
||||
Shell.sh("pm install -R -t " + apkFilePath).submit { out ->
|
||||
val builder = StringBuilder()
|
||||
for (data in out.getOut()) {
|
||||
builder.append(data)
|
||||
}
|
||||
result.success(if (builder.toString().endsWith("Success")) 0 else 1)
|
||||
for (data in out.getOut()) { builder.append(data) }
|
||||
result.success(builder.toString().endsWith("Success"))
|
||||
}
|
||||
}
|
||||
|
||||
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, installersChannel).setMethodCallHandler {
|
||||
call, result ->
|
||||
var apkFilePath: String? = call.argument("apkFilePath")
|
||||
if (call.method == "installWithShizuku") {
|
||||
installWithShizuku(apkFilePath.toString(), result)
|
||||
} else if (call.method == "installWithRoot") {
|
||||
installWithRoot(apkFilePath.toString(), result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Shizuku.addBinderReceivedListener(shizukuBinderReceivedListener)
|
||||
Shizuku.addBinderDeadListener(shizukuBinderDeadListener)
|
||||
Shizuku.addRequestPermissionResultListener(shizukuRequestPermissionResultListener)
|
||||
installersChannel = MethodChannel(
|
||||
flutterEngine.dartExecutor.binaryMessenger, "installers")
|
||||
installersChannel!!.setMethodCallHandler {
|
||||
call, result ->
|
||||
if (call.method == "checkPermissionShizuku") {
|
||||
shizukuCheckPermission()
|
||||
result.success(0)
|
||||
} else if (call.method == "checkPermissionRoot") {
|
||||
rootCheckPermission(result)
|
||||
} else if (call.method == "installWithShizuku") {
|
||||
val apkFileUri: String? = call.argument("apkFileUri")
|
||||
shizukuInstallApk(apkFileUri!!, result)
|
||||
} else if (call.method == "installWithRoot") {
|
||||
val apkFilePath: String? = call.argument("apkFilePath")
|
||||
rootInstallApk(apkFilePath!!, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
Shizuku.removeBinderReceivedListener(shizukuBinderReceivedListener)
|
||||
Shizuku.removeBinderDeadListener(shizukuBinderDeadListener)
|
||||
Shizuku.removeRequestPermissionResultListener(shizukuRequestPermissionResultListener)
|
||||
}
|
||||
}
|
||||
|
@ -282,6 +282,7 @@
|
||||
"normal": "Normal",
|
||||
"shizuku": "Shizuku",
|
||||
"root": "Root",
|
||||
"shizukuBinderNotFound": "Shizuku is not running",
|
||||
"removeAppQuestion": {
|
||||
"one": "Remove App?",
|
||||
"other": "Remove Apps?"
|
||||
|
@ -275,13 +275,14 @@
|
||||
"downloadingXNotifChannel": "Загрузка {}",
|
||||
"completeAppInstallationNotifChannel": "Завершение установки приложения",
|
||||
"checkingForUpdatesNotifChannel": "Проверка обновлений",
|
||||
"onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
|
||||
"supportFixedAPKURL": "Support fixed APK URLs",
|
||||
"selectX": "Select {}",
|
||||
"onlyCheckInstalledOrTrackOnlyApps": "Проверять обновления только у установленных или отслеживаемых приложений",
|
||||
"supportFixedAPKURL": "Поддержка фиксированных URL-адресов APK",
|
||||
"selectX": "Выбрать {}",
|
||||
"installMethod": "Метод установки",
|
||||
"normal": "Нормальный",
|
||||
"shizuku": "Shizuku",
|
||||
"root": "Суперпользователь",
|
||||
"shizukuBinderNotFound": "Shizuku не запущен",
|
||||
"removeAppQuestion": {
|
||||
"one": "Удалить приложение?",
|
||||
"other": "Удалить приложения?"
|
||||
|
@ -505,7 +505,8 @@ class AppsProvider with ChangeNotifier {
|
||||
!(await canDowngradeApps())) {
|
||||
throw DowngradeError();
|
||||
}
|
||||
if (needsBGWorkaround) {
|
||||
if (needsBGWorkaround &&
|
||||
settingsProvider.installMethod == InstallMethodSettings.normal) {
|
||||
// The below 'await' will never return if we are in a background process
|
||||
// To work around this, we should assume the install will be successful
|
||||
// So we update the app's installed version first as we will never get to the later code
|
||||
@ -517,12 +518,13 @@ class AppsProvider with ChangeNotifier {
|
||||
attemptToCorrectInstallStatus: false);
|
||||
}
|
||||
int? code;
|
||||
if (settingsProvider.installMethod == InstallMethodSettings.normal) {
|
||||
switch (settingsProvider.installMethod) {
|
||||
case InstallMethodSettings.normal:
|
||||
code = await AndroidPackageInstaller.installApk(apkFilePath: file.file.path);
|
||||
} else if (settingsProvider.installMethod == InstallMethodSettings.shizuku) {
|
||||
code = await Installers.installWithShizuku(apkFilePath: file.file.path);
|
||||
} else if (settingsProvider.installMethod == InstallMethodSettings.root) {
|
||||
code = await Installers.installWithRoot(apkFilePath: file.file.path);
|
||||
case InstallMethodSettings.shizuku:
|
||||
code = (await Installers.installWithShizuku(apkFileUri: file.file.uri.toString())) ? 0 : 1;
|
||||
case InstallMethodSettings.root:
|
||||
code = (await Installers.installWithRoot(apkFilePath: file.file.path)) ? 0 : 1;
|
||||
}
|
||||
bool installed = false;
|
||||
if (code != null && code != 0 && code != 3) {
|
||||
@ -679,9 +681,23 @@ class AppsProvider with ChangeNotifier {
|
||||
}
|
||||
var appId = downloadedFile?.appId ?? downloadedDir!.appId;
|
||||
bool willBeSilent = await canInstallSilently(apps[appId]!.app);
|
||||
switch (settingsProvider.installMethod) {
|
||||
case InstallMethodSettings.normal:
|
||||
if (!(await settingsProvider.getInstallPermission(enforce: false))) {
|
||||
throw ObtainiumError(tr('cancelled'));
|
||||
}
|
||||
case InstallMethodSettings.shizuku:
|
||||
int code = await Installers.checkPermissionShizuku();
|
||||
if (code == -1) {
|
||||
throw ObtainiumError(tr('shizukuBinderNotFound'));
|
||||
} else if (code == 0) {
|
||||
throw ObtainiumError(tr('cancelled'));
|
||||
}
|
||||
case InstallMethodSettings.root:
|
||||
if (!(await Installers.checkPermissionRoot())) {
|
||||
throw ObtainiumError(tr('cancelled'));
|
||||
}
|
||||
}
|
||||
if (!willBeSilent && context != null) {
|
||||
// ignore: use_build_context_synchronously
|
||||
await waitForUserToReturnToForeground(context);
|
||||
|
@ -3,12 +3,52 @@ import 'package:flutter/services.dart';
|
||||
|
||||
class Installers {
|
||||
static const MethodChannel _channel = MethodChannel('installers');
|
||||
static bool _callbacksApplied = false;
|
||||
static int _resPermShizuku = -2; // not set
|
||||
|
||||
static Future<int?> installWithShizuku({required String apkFilePath}) async {
|
||||
return await _channel.invokeMethod('installWithShizuku', {'apkFilePath': apkFilePath});
|
||||
static Future waitWhile(bool Function() test,
|
||||
[Duration pollInterval = const Duration(milliseconds: 100)]) {
|
||||
var completer = Completer();
|
||||
check() {
|
||||
if (test()) {
|
||||
Timer(pollInterval, check);
|
||||
} else {
|
||||
completer.complete();
|
||||
}
|
||||
}
|
||||
check();
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
static Future<int?> installWithRoot({required String apkFilePath}) async {
|
||||
return await _channel.invokeMethod('installWithRoot', {'apkFilePath': apkFilePath});
|
||||
static Future handleCalls(MethodCall call) async {
|
||||
if (call.method == 'resPermShizuku') {
|
||||
_resPermShizuku = call.arguments['res'];
|
||||
}
|
||||
}
|
||||
|
||||
static Future<int> checkPermissionShizuku() async {
|
||||
if (!_callbacksApplied) {
|
||||
_channel.setMethodCallHandler(handleCalls);
|
||||
_callbacksApplied = true;
|
||||
}
|
||||
await _channel.invokeMethod('checkPermissionShizuku');
|
||||
await waitWhile(() => _resPermShizuku == -2);
|
||||
int res = _resPermShizuku;
|
||||
_resPermShizuku = -2;
|
||||
return res;
|
||||
}
|
||||
|
||||
static Future<bool> checkPermissionRoot() async {
|
||||
return await _channel.invokeMethod('checkPermissionRoot');
|
||||
}
|
||||
|
||||
static Future<bool> installWithShizuku({required String apkFileUri}) async {
|
||||
return await _channel.invokeMethod(
|
||||
'installWithShizuku', {'apkFileUri': apkFileUri});
|
||||
}
|
||||
|
||||
static Future<bool> installWithRoot({required String apkFilePath}) async {
|
||||
return await _channel.invokeMethod(
|
||||
'installWithRoot', {'apkFilePath': apkFilePath});
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user