mirror of
				https://github.com/ImranR98/Obtainium.git
				synced 2025-10-31 05:23:28 +01: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