Now it looks good

This commit is contained in:
Gregory
2023-12-24 18:26:11 +03:00
parent 375b9bce30
commit b291c800f1
5 changed files with 161 additions and 136 deletions

View File

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

View File

@ -282,6 +282,7 @@
"normal": "Normal",
"shizuku": "Shizuku",
"root": "Root",
"shizukuBinderNotFound": "Shizuku is not running",
"removeAppQuestion": {
"one": "Remove App?",
"other": "Remove Apps?"

View File

@ -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": "Удалить приложения?"

View File

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

View File

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