mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-07-17 07:06:43 +02:00
Working shizuku installer, need refactor
This commit is contained in:
@ -5,12 +5,27 @@ import io.flutter.embedding.engine.FlutterEngine
|
|||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
import io.flutter.plugin.common.MethodChannel.Result
|
import io.flutter.plugin.common.MethodChannel.Result
|
||||||
import androidx.annotation.NonNull
|
import androidx.annotation.NonNull
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentSender
|
||||||
|
import android.content.pm.IPackageInstaller
|
||||||
|
import android.content.pm.IPackageInstallerSession
|
||||||
|
import android.content.pm.PackageInstaller
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.Process
|
||||||
import rikka.shizuku.Shizuku
|
import rikka.shizuku.Shizuku
|
||||||
import rikka.shizuku.Shizuku.OnBinderDeadListener
|
import rikka.shizuku.Shizuku.OnBinderDeadListener
|
||||||
import rikka.shizuku.Shizuku.OnBinderReceivedListener
|
import rikka.shizuku.Shizuku.OnBinderReceivedListener
|
||||||
import rikka.shizuku.Shizuku.OnRequestPermissionResultListener
|
import rikka.shizuku.Shizuku.OnRequestPermissionResultListener
|
||||||
|
import rikka.shizuku.ShizukuBinderWrapper
|
||||||
|
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 java.io.IOException
|
||||||
|
import java.util.concurrent.CountDownLatch
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
|
|
||||||
class MainActivity: FlutterActivity() {
|
class MainActivity: FlutterActivity() {
|
||||||
@ -46,8 +61,107 @@ class MainActivity: FlutterActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun shizukuInstallApk(uri: Uri) {
|
||||||
|
val packageInstaller: PackageInstaller
|
||||||
|
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 =
|
||||||
|
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"
|
||||||
|
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 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*/
|
||||||
|
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
|
||||||
|
try {
|
||||||
|
while (`is`!!.read(buf).also { len = it } > 0) {
|
||||||
|
os.write(buf, 0, len)
|
||||||
|
os.flush()
|
||||||
|
session.fsync(os)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
`is`!!.close()
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
os.close()
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.append('\n').append("commit: ")
|
||||||
|
val results = arrayOf<Intent?>(null)
|
||||||
|
val countDownLatch = CountDownLatch(1)
|
||||||
|
val intentSender: IntentSender =
|
||||||
|
IntentSenderUtils.newInstance(object : IIntentSenderAdaptor() {
|
||||||
|
override fun send(intent: Intent?) {
|
||||||
|
results[0] = intent
|
||||||
|
countDownLatch.countDown()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
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)
|
||||||
|
} finally {
|
||||||
|
if (session != null) {
|
||||||
|
try {
|
||||||
|
session.close()
|
||||||
|
} catch (tr: Throwable) {
|
||||||
|
res.append(tr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun installWithShizuku(apkFilePath: String, result: Result) {
|
private fun installWithShizuku(apkFilePath: String, result: Result) {
|
||||||
shizukuCheckPermission()
|
shizukuCheckPermission()
|
||||||
|
shizukuInstallApk(Uri.parse("file://$apkFilePath"))
|
||||||
result.success(0)
|
result.success(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
package dev.imranr.obtainium.util;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.app.Application;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
public class ApplicationUtils {
|
||||||
|
|
||||||
|
private static Application application;
|
||||||
|
|
||||||
|
public static Application getApplication() {
|
||||||
|
return application;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setApplication(Application application) {
|
||||||
|
ApplicationUtils.application = application;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getProcessName() {
|
||||||
|
if (Build.VERSION.SDK_INT >= 28)
|
||||||
|
return Application.getProcessName();
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
@SuppressLint("PrivateApi")
|
||||||
|
Class<?> activityThread = Class.forName("android.app.ActivityThread");
|
||||||
|
@SuppressLint("DiscouragedPrivateApi")
|
||||||
|
Method method = activityThread.getDeclaredMethod("currentProcessName");
|
||||||
|
return (String) method.invoke(null);
|
||||||
|
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package dev.imranr.obtainium.util;
|
||||||
|
|
||||||
|
import android.content.IIntentReceiver;
|
||||||
|
import android.content.IIntentSender;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.IBinder;
|
||||||
|
|
||||||
|
public abstract class IIntentSenderAdaptor extends IIntentSender.Stub {
|
||||||
|
|
||||||
|
public abstract void send(Intent intent);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int send(int code, Intent intent, String resolvedType, IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
|
||||||
|
send(intent);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken, IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
|
||||||
|
send(intent);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package dev.imranr.obtainium.util;
|
||||||
|
|
||||||
|
import android.content.IIntentSender;
|
||||||
|
import android.content.IntentSender;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
|
||||||
|
public class IntentSenderUtils {
|
||||||
|
|
||||||
|
public static IntentSender newInstance(IIntentSender binder) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
|
||||||
|
//noinspection JavaReflectionMemberAccess
|
||||||
|
return IntentSender.class.getConstructor(IIntentSender.class).newInstance(binder);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
package dev.imranr.obtainium.util;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.IPackageInstaller;
|
||||||
|
import android.content.pm.IPackageInstallerSession;
|
||||||
|
import android.content.pm.PackageInstaller;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
|
||||||
|
@SuppressWarnings({"JavaReflectionMemberAccess"})
|
||||||
|
public class PackageInstallerUtils {
|
||||||
|
|
||||||
|
public static PackageInstaller createPackageInstaller(IPackageInstaller installer, String installerPackageName, String installerAttributionTag, int userId) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
return PackageInstaller.class.getConstructor(IPackageInstaller.class, String.class, String.class, int.class)
|
||||||
|
.newInstance(installer, installerPackageName, installerAttributionTag, userId);
|
||||||
|
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
return PackageInstaller.class.getConstructor(IPackageInstaller.class, String.class, int.class)
|
||||||
|
.newInstance(installer, installerPackageName, userId);
|
||||||
|
} else {
|
||||||
|
return PackageInstaller.class.getConstructor(Context.class, PackageManager.class, IPackageInstaller.class, String.class, int.class)
|
||||||
|
.newInstance(ApplicationUtils.getApplication(), ApplicationUtils.getApplication().getPackageManager(), installer, installerPackageName, userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PackageInstaller.Session createSession(IPackageInstallerSession session) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
|
||||||
|
return PackageInstaller.Session.class.getConstructor(IPackageInstallerSession.class)
|
||||||
|
.newInstance(session);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getInstallFlags(PackageInstaller.SessionParams params) throws NoSuchFieldException, IllegalAccessException {
|
||||||
|
return (int) PackageInstaller.SessionParams.class.getDeclaredField("installFlags").get(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setInstallFlags(PackageInstaller.SessionParams params, int newValue) throws NoSuchFieldException, IllegalAccessException {
|
||||||
|
PackageInstaller.SessionParams.class.getDeclaredField("installFlags").set(params, newValue);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
package dev.imranr.obtainium.util;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.IPackageInstaller;
|
||||||
|
import android.content.pm.IPackageManager;
|
||||||
|
import android.content.pm.UserInfo;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.IUserManager;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import rikka.shizuku.ShizukuBinderWrapper;
|
||||||
|
import rikka.shizuku.SystemServiceHelper;
|
||||||
|
|
||||||
|
public class ShizukuSystemServerApi {
|
||||||
|
|
||||||
|
private static final Singleton<IPackageManager> PACKAGE_MANAGER = new Singleton<IPackageManager>() {
|
||||||
|
@Override
|
||||||
|
protected IPackageManager create() {
|
||||||
|
return IPackageManager.Stub.asInterface(new ShizukuBinderWrapper(SystemServiceHelper.getSystemService("package")));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final Singleton<IUserManager> USER_MANAGER = new Singleton<IUserManager>() {
|
||||||
|
@Override
|
||||||
|
protected IUserManager create() {
|
||||||
|
return IUserManager.Stub.asInterface(new ShizukuBinderWrapper(SystemServiceHelper.getSystemService(Context.USER_SERVICE)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static IPackageInstaller PackageManager_getPackageInstaller() throws RemoteException {
|
||||||
|
IPackageInstaller packageInstaller = PACKAGE_MANAGER.get().getPackageInstaller();
|
||||||
|
return IPackageInstaller.Stub.asInterface(new ShizukuBinderWrapper(packageInstaller.asBinder()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<UserInfo> UserManager_getUsers(boolean excludePartial, boolean excludeDying, boolean excludePreCreated) throws RemoteException {
|
||||||
|
if (Build.VERSION.SDK_INT >= 30) {
|
||||||
|
return USER_MANAGER.get().getUsers(excludePartial, excludeDying, excludePreCreated);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
return USER_MANAGER.get().getUsers(excludeDying);
|
||||||
|
} catch (NoSuchFieldError e) {
|
||||||
|
return USER_MANAGER.get().getUsers(excludePartial, excludeDying, excludePreCreated);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// method 2: use transactRemote directly
|
||||||
|
/*public static List<UserInfo> UserManager_getUsers(boolean excludeDying) {
|
||||||
|
Parcel data = SystemServiceHelper.obtainParcel(Context.USER_SERVICE, "android.os.IUserManager", "getUsers");
|
||||||
|
Parcel reply = Parcel.obtain();
|
||||||
|
data.writeInt(excludeDying ? 1 : 0);
|
||||||
|
|
||||||
|
List<UserInfo> res = null;
|
||||||
|
try {
|
||||||
|
ShizukuService.transactRemote(data, reply, 0);
|
||||||
|
reply.readException();
|
||||||
|
res = reply.createTypedArrayList(UserInfo.CREATOR);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.e("ShizukuSample", "UserManager#getUsers", e);
|
||||||
|
} finally {
|
||||||
|
data.recycle();
|
||||||
|
reply.recycle();
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}*/
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package dev.imranr.obtainium.util;
|
||||||
|
|
||||||
|
public abstract class Singleton<T> {
|
||||||
|
|
||||||
|
private T mInstance;
|
||||||
|
|
||||||
|
protected abstract T create();
|
||||||
|
|
||||||
|
public final T get() {
|
||||||
|
synchronized (this) {
|
||||||
|
if (mInstance == null) {
|
||||||
|
mInstance = create();
|
||||||
|
}
|
||||||
|
return mInstance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user