diff --git a/api b/api index 453e947..4a1f140 160000 --- a/api +++ b/api @@ -1 +1 @@ -Subproject commit 453e94721a902acfa406d0c46682a4b1954d1af1 +Subproject commit 4a1f140b2e84459e75135f79c695ae00c4500674 diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 21cc360..146e13d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -22,8 +22,8 @@ android { applicationId = "com.rosan.dhizuku" minSdk = 21 targetSdk = 33 - versionCode = 2 - versionName = "1.0.3" + versionCode = 3 + versionName = "2.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { @@ -128,8 +128,12 @@ dependencies { implementation(libs.accompanist.drawablepainter) implementation(libs.accompanist.systemuicontroller) + implementation(libs.lsposed.hiddenapibypass) + implementation(libs.xxpermissions) implementation(libs.rikka.shizuku.api) implementation(libs.rikka.shizuku.provider) + + implementation(libs.commons.cli) } \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index fac7f23..b1f61ff 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -69,7 +69,7 @@ -keep public class com.rosan.dhizuku.data.process.model.impl.** { public static void main(java.lang.String[]); } -#-keep public class com.rosan.installer.data.process.model.impl.** extends com.rosan.installer.data.process.repo.ProcessRepo { +#-keep public class com.rosan.installer.data.process.model.impl.** extends com.rosan.dhizuku.data.process.repo.ProcessRepo { #public static void main(java.lang.String[]); #} #-keep public class com.rosan.installer.** extends android.app.Service diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7a7c49a..a4b6b27 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -64,11 +64,12 @@ + android:directBootAware="true" + android:exported="false" + tools:ignore="UnusedAttribute"> diff --git a/app/src/main/aidl/android/content/IIntentReceiver.aidl b/app/src/main/aidl/android/content/IIntentReceiver.aidl deleted file mode 100644 index 5d9895e..0000000 --- a/app/src/main/aidl/android/content/IIntentReceiver.aidl +++ /dev/null @@ -1,9 +0,0 @@ -package android.content; - -import android.content.Intent; -import android.os.Bundle; - -oneway interface IIntentReceiver { - void performReceive(in Intent intent, int resultCode, String data, - in Bundle extras, boolean ordered, boolean sticky, int sendingUser); -} diff --git a/app/src/main/aidl/android/content/IIntentSender.aidl b/app/src/main/aidl/android/content/IIntentSender.aidl deleted file mode 100644 index 2736b61..0000000 --- a/app/src/main/aidl/android/content/IIntentSender.aidl +++ /dev/null @@ -1,10 +0,0 @@ -package android.content; - -import android.content.IIntentReceiver; -import android.content.Intent; -import android.os.Bundle; - -oneway interface IIntentSender { - void send(int code, in Intent intent, String resolvedType, in IBinder whitelistToken, - IIntentReceiver finishedReceiver, String requiredPermission, in Bundle options); -} \ No newline at end of file diff --git a/app/src/main/aidl/android/content/pm/IOnChecksumsReadyListener.aidl b/app/src/main/aidl/android/content/pm/IOnChecksumsReadyListener.aidl deleted file mode 100644 index 9963a87..0000000 --- a/app/src/main/aidl/android/content/pm/IOnChecksumsReadyListener.aidl +++ /dev/null @@ -1,6 +0,0 @@ -package android.content.pm; -import android.content.pm.ApkChecksum; - -oneway interface IOnChecksumsReadyListener { - void onChecksumsReady(in List checksums); -} \ No newline at end of file diff --git a/app/src/main/aidl/android/content/pm/IPackageDeleteObserver2.aidl b/app/src/main/aidl/android/content/pm/IPackageDeleteObserver2.aidl deleted file mode 100644 index cc2406b..0000000 --- a/app/src/main/aidl/android/content/pm/IPackageDeleteObserver2.aidl +++ /dev/null @@ -1,7 +0,0 @@ -package android.content.pm; -import android.content.Intent; - -oneway interface IPackageDeleteObserver2 { - void onUserActionRequired(in Intent intent); - void onPackageDeleted(String packageName, int returnCode, String msg); -} \ No newline at end of file diff --git a/app/src/main/aidl/android/content/pm/IPackageInstallObserver2.aidl b/app/src/main/aidl/android/content/pm/IPackageInstallObserver2.aidl deleted file mode 100644 index a1c9ec3..0000000 --- a/app/src/main/aidl/android/content/pm/IPackageInstallObserver2.aidl +++ /dev/null @@ -1,8 +0,0 @@ -package android.content.pm; -import android.content.Intent; -import android.os.Bundle; - -oneway interface IPackageInstallObserver2 { - void onUserActionRequired(in Intent intent); - void onPackageInstalled(String basePackageName, int returnCode, String msg, in Bundle extras); -} \ No newline at end of file diff --git a/app/src/main/aidl/android/content/pm/IPackageInstaller.aidl b/app/src/main/aidl/android/content/pm/IPackageInstaller.aidl deleted file mode 100644 index 14aab7c..0000000 --- a/app/src/main/aidl/android/content/pm/IPackageInstaller.aidl +++ /dev/null @@ -1,33 +0,0 @@ -package android.content.pm; - -import android.content.pm.IPackageDeleteObserver2; -import android.content.pm.IPackageInstallerCallback; -import android.content.pm.IPackageInstallerSession; -import android.content.pm.PackageInstaller; -import android.content.pm.VersionedPackage; -import android.content.IntentSender; -import android.graphics.Bitmap; - -interface IPackageInstaller { - int createSession(in PackageInstaller.SessionParams params, String installerPackageName, String installerAttributionTag, int userId); -// @RequiresApi(Build.VERSION_CODES.S) -// int createSession(in PackageInstaller.SessionParams params, String installerPackageName, int userId); - void updateSessionAppIcon(int sessionId, in Bitmap appIcon); - void updateSessionAppLabel(int sessionId, String appLabel); - void abandonSession(int sessionId); - IPackageInstallerSession openSession(int sessionId); - PackageInstaller.SessionInfo getSessionInfo(int sessionId); - void registerCallback(IPackageInstallerCallback callback, int userId); - void unregisterCallback(IPackageInstallerCallback callback); - void uninstall(in VersionedPackage versionedPackage, String callerPackageName, int flags, - in IntentSender statusReceiver, int userId); - void uninstallExistingPackage(in VersionedPackage versionedPackage, String callerPackageName, - in IntentSender statusReceiver, int userId); - void installExistingPackage(String packageName, int installFlags, int installReason, - in IntentSender statusReceiver, int userId, in List whiteListedPermissions); - void setPermissionsResult(int sessionId, boolean accepted); - void bypassNextStagedInstallerCheck(boolean value); - void bypassNextAllowedApexUpdateCheck(boolean value); - void setAllowUnlimitedSilentUpdates(String installerPackageName); - void setSilentUpdatesThrottleTime(long throttleTimeInSeconds); -} \ No newline at end of file diff --git a/app/src/main/aidl/android/content/pm/IPackageInstallerCallback.aidl b/app/src/main/aidl/android/content/pm/IPackageInstallerCallback.aidl deleted file mode 100644 index 42e11bc..0000000 --- a/app/src/main/aidl/android/content/pm/IPackageInstallerCallback.aidl +++ /dev/null @@ -1,9 +0,0 @@ -package android.content.pm; - -oneway interface IPackageInstallerCallback { - void onSessionCreated(int sessionId); - void onSessionBadgingChanged(int sessionId); - void onSessionActiveChanged(int sessionId, boolean active); - void onSessionProgressChanged(int sessionId, float progress); - void onSessionFinished(int sessionId, boolean success); -} \ No newline at end of file diff --git a/app/src/main/aidl/android/content/pm/IPackageInstallerSession.aidl b/app/src/main/aidl/android/content/pm/IPackageInstallerSession.aidl deleted file mode 100644 index 0204d42..0000000 --- a/app/src/main/aidl/android/content/pm/IPackageInstallerSession.aidl +++ /dev/null @@ -1,33 +0,0 @@ -package android.content.pm; - -import android.content.pm.Checksum; -import android.content.pm.IOnChecksumsReadyListener; -import android.content.pm.IPackageInstallObserver2; -import android.content.IntentSender; -import android.os.ParcelFileDescriptor; - -interface IPackageInstallerSession { - void setClientProgress(float progress); - void addClientProgress(float progress); - String[] getNames(); - ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes); - ParcelFileDescriptor openRead(String name); - void write(String name, long offsetBytes, long lengthBytes, in ParcelFileDescriptor fd); - void stageViaHardLink(String target); - void setChecksums(String name, in Checksum[] checksums, in byte[] signature); - void requestChecksums(in String name, int optional, int required, in List trustedInstallers, in IOnChecksumsReadyListener onChecksumsReadyListener); - void removeSplit(String splitName); - void close(); - void commit(in IntentSender statusReceiver, boolean forTransferred); - void transfer(in String packageName); - void abandon(); - void addFile(int location, String name, long lengthBytes, in byte[] metadata, in byte[] signature); - void removeFile(int location, String name); - boolean isMultiPackage(); - int[] getChildSessionIds(); - void addChildSessionId(in int sessionId); - void removeChildSessionId(in int sessionId); - int getParentSessionId(); - boolean isStaged(); - int getInstallFlags(); -} \ No newline at end of file diff --git a/app/src/main/aidl/android/content/pm/IPackageManager.aidl b/app/src/main/aidl/android/content/pm/IPackageManager.aidl deleted file mode 100644 index 86ce213..0000000 --- a/app/src/main/aidl/android/content/pm/IPackageManager.aidl +++ /dev/null @@ -1,231 +0,0 @@ -package android.content.pm; - -import android.content.ComponentName; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; -import android.content.pm.ChangedPackages; -import android.content.pm.InstantAppInfo; -import android.content.pm.FeatureInfo; -import android.content.pm.InstallSourceInfo; -import android.content.pm.IOnChecksumsReadyListener; -import android.content.pm.IPackageInstaller; -import android.content.pm.IntentFilterVerificationInfo; -import android.content.pm.InstrumentationInfo; -import android.content.pm.ModuleInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager.ComponentEnabledSetting; -import android.content.pm.ProviderInfo; -import android.content.pm.PermissionGroupInfo; -import android.content.pm.PermissionInfo; -import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; -import android.content.pm.UserInfo; -import android.content.pm.VersionedPackage; -import android.graphics.Bitmap; -import android.net.Uri; -import android.os.Bundle; -import android.os.ParcelFileDescriptor; -import android.os.PersistableBundle; -import android.content.IntentSender; - -interface IPackageManager { - void checkPackageStartable(String packageName, int userId); - boolean isPackageAvailable(String packageName, int userId); - PackageInfo getPackageInfo(String packageName, long flags, int userId); - PackageInfo getPackageInfoVersioned(in VersionedPackage versionedPackage, - long flags, int userId); - int getPackageUid(String packageName, long flags, int userId); - int[] getPackageGids(String packageName, long flags, int userId); - String[] currentToCanonicalPackageNames(in String[] names); - String[] canonicalToCurrentPackageNames(in String[] names); - ApplicationInfo getApplicationInfo(String packageName, long flags, int userId); - int getTargetSdkVersion(String packageName); - ActivityInfo getActivityInfo(in ComponentName className, long flags, int userId); - boolean activitySupportsIntent(in ComponentName className, in Intent intent, - String resolvedType); - ActivityInfo getReceiverInfo(in ComponentName className, long flags, int userId); - ServiceInfo getServiceInfo(in ComponentName className, long flags, int userId); - ProviderInfo getProviderInfo(in ComponentName className, long flags, int userId); - boolean isProtectedBroadcast(String actionName); - int checkSignatures(String pkg1, String pkg2); - int checkUidSignatures(int uid1, int uid2); - List getAllPackages(); - String[] getPackagesForUid(int uid); - String getNameForUid(int uid); - String[] getNamesForUids(in int[] uids); - int getUidForSharedUser(String sharedUserName); - int getFlagsForUid(int uid); - int getPrivateFlagsForUid(int uid); - boolean isUidPrivileged(int uid); - ResolveInfo resolveIntent(in Intent intent, String resolvedType, long flags, int userId); - ResolveInfo findPersistentPreferredActivity(in Intent intent, int userId); - boolean canForwardTo(in Intent intent, String resolvedType, int sourceUserId, int targetUserId); - ResolveInfo resolveService(in Intent intent, - String resolvedType, long flags, int userId); - ProviderInfo resolveContentProvider(String name, long flags, int userId); - void querySyncProviders(inout List outNames, - inout List outInfo); - InstrumentationInfo getInstrumentationInfo( - in ComponentName className, int flags); - void finishPackageInstall(int token, boolean didLaunch); - void setInstallerPackageName(in String targetPackage, in String installerPackageName); - void setApplicationCategoryHint(String packageName, int categoryHint, String callerPackageName); - String getInstallerPackageName(in String packageName); - InstallSourceInfo getInstallSourceInfo(in String packageName); - void resetApplicationPreferences(int userId); - ResolveInfo getLastChosenActivity(in Intent intent, - String resolvedType, int flags); - void setLastChosenActivity(in Intent intent, String resolvedType, int flags, - in IntentFilter filter, int match, in ComponentName activity); - void addPreferredActivity(in IntentFilter filter, int match, - in ComponentName[] set, in ComponentName activity, int userId, boolean removeExisting); - void replacePreferredActivity(in IntentFilter filter, int match, - in ComponentName[] set, in ComponentName activity, int userId); - void clearPackagePreferredActivities(String packageName); - int getPreferredActivities(out List outFilters, - out List outActivities, String packageName); - void addPersistentPreferredActivity(in IntentFilter filter, in ComponentName activity, int userId); - void clearPackagePersistentPreferredActivities(String packageName, int userId); - void addCrossProfileIntentFilter(in IntentFilter intentFilter, String ownerPackage, - int sourceUserId, int targetUserId, int flags); - void clearCrossProfileIntentFilters(int sourceUserId, String ownerPackage); - String[] setDistractingPackageRestrictionsAsUser(in String[] packageNames, int restrictionFlags, - int userId); - String[] getUnsuspendablePackagesForUser(in String[] packageNames, int userId); - boolean isPackageSuspendedForUser(String packageName, int userId); - Bundle getSuspendedPackageAppExtras(String packageName, int userId); - byte[] getPreferredActivityBackup(int userId); - void restorePreferredActivities(in byte[] backup, int userId); - byte[] getDefaultAppsBackup(int userId); - void restoreDefaultApps(in byte[] backup, int userId); - byte[] getDomainVerificationBackup(int userId); - void restoreDomainVerification(in byte[] backup, int userId); - - ComponentName getHomeActivities(out List outHomeCandidates); - void setHomeActivity(in ComponentName className, int userId); - void overrideLabelAndIcon(in ComponentName componentName, String nonLocalizedLabel, - int icon, int userId); - void restoreLabelAndIcon(in ComponentName componentName, int userId); - - void setComponentEnabledSetting(in ComponentName componentName, - in int newState, in int flags, int userId); - void setComponentEnabledSettings(in List settings, int userId); - - int getComponentEnabledSetting(in ComponentName componentName, int userId); - - void setApplicationEnabledSetting(in String packageName, in int newState, int flags, - int userId, String callingPackage); - - int getApplicationEnabledSetting(in String packageName, int userId); - void logAppProcessStartIfNeeded(String packageName, String processName, int uid, String seinfo, String apkFile, int pid); - void flushPackageRestrictionsAsUser(in int userId); - - void setPackageStoppedState(String packageName, boolean stopped, int userId); - void freeStorage(in String volumeUuid, in long freeStorageSize, - int storageFlags, in IntentSender pi); - void clearApplicationProfileData(in String packageName); - - String[] getSystemSharedLibraryNames(); - boolean hasSystemFeature(String name, int version); - void enterSafeMode(); - boolean isSafeMode(); - boolean hasSystemUidErrors(); - oneway void notifyPackageUse(String packageName, int reason); - oneway void notifyDexLoad(String loadingPackageName, - in Map classLoaderContextMap, String loaderIsa); - boolean performDexOptMode(String packageName, boolean checkProfiles, - String targetCompilerFilter, boolean force, boolean bootComplete, String splitName); - boolean performDexOptSecondary(String packageName, - String targetCompilerFilter, boolean force); - void dumpProfiles(String packageName, boolean dumpClassesAndMethods); - void forceDexOpt(String packageName); - void reconcileSecondaryDexFiles(String packageName); - int getMoveStatus(int moveId); - int movePackage(in String packageName, in String volumeUuid); - int movePrimaryStorage(in String volumeUuid); - boolean setInstallLocation(int loc); - int getInstallLocation(); - int installExistingPackageAsUser(String packageName, int userId, int installFlags, - int installReason, in List whiteListedPermissions); - void verifyPendingInstall(int id, int verificationCode); - void extendVerificationTimeout(int id, int verificationCodeAtTimeout, long millisecondsToDelay); - void verifyIntentFilter(int id, int verificationCode, in List failedDomains); - int getIntentVerificationStatus(String packageName, int userId); - boolean updateIntentVerificationStatus(String packageName, int status, int userId); - boolean isFirstBoot(); - boolean isOnlyCoreApps(); - boolean isDeviceUpgrading(); - - boolean isStorageLow(); - boolean setApplicationHiddenSettingAsUser(String packageName, boolean hidden, int userId); - boolean getApplicationHiddenSettingAsUser(String packageName, int userId); - void setSystemAppHiddenUntilInstalled(String packageName, boolean hidden); - boolean setSystemAppInstallState(String packageName, boolean installed, int userId); - IPackageInstaller getPackageInstaller(); - boolean setBlockUninstallForUser(String packageName, boolean blockUninstall, int userId); - boolean getBlockUninstallForUser(String packageName, int userId); - String getPermissionControllerPackageName(); - String getSdkSandboxPackageName(); - byte[] getInstantAppCookie(String packageName, int userId); - boolean setInstantAppCookie(String packageName, in byte[] cookie, int userId); - Bitmap getInstantAppIcon(String packageName, int userId); - boolean isInstantApp(String packageName, int userId); - boolean setRequiredForSystemUser(String packageName, boolean systemUserApp); - void setUpdateAvailable(String packageName, boolean updateAvaialble); - String getServicesSystemSharedLibraryPackageName(); - String getSharedSystemSharedLibraryPackageName(); - ChangedPackages getChangedPackages(int sequenceNumber, int userId); - boolean isPackageDeviceAdminOnAnyUser(String packageName); - int getInstallReason(String packageName, int userId); - boolean canRequestPackageInstalls(String packageName, int userId); - void deletePreloadsFileCache(); - ComponentName getInstantAppResolverComponent(); - ComponentName getInstantAppResolverSettingsComponent(); - ComponentName getInstantAppInstallerComponent(); - String getInstantAppAndroidId(String packageName, int userId); - void setHarmfulAppWarning(String packageName, CharSequence warning, int userId); - CharSequence getHarmfulAppWarning(String packageName, int userId); - boolean hasSigningCertificate(String packageName, in byte[] signingCertificate, int flags); - boolean hasUidSigningCertificate(int uid, in byte[] signingCertificate, int flags); - String getDefaultTextClassifierPackageName(); - String getSystemTextClassifierPackageName(); - String getAttentionServicePackageName(); - String getRotationResolverPackageName(); - String getWellbeingPackageName(); - String getAppPredictionServicePackageName(); - String getSystemCaptionsServicePackageName(); - String getSetupWizardPackageName(); - String getIncidentReportApproverPackageName(); - String getContentCaptureServicePackageName(); - boolean isPackageStateProtected(String packageName, int userId); - void sendDeviceCustomizationReadyBroadcast(); - List getInstalledModules(int flags); - ModuleInfo getModuleInfo(String packageName, int flags); - int getRuntimePermissionsVersion(int userId); - void setRuntimePermissionsVersion(int version, int userId); - void notifyPackagesReplacedReceived(in String[] packages); - void requestPackageChecksums(in String packageName, boolean includeSplits, int optional, int required, in List trustedInstallers, in IOnChecksumsReadyListener onChecksumsReadyListener, int userId); - IntentSender getLaunchIntentSenderForPackage(String packageName, String callingPackage, - String featureId, int userId); - String[] getAppOpPermissionPackages(String permissionName); - PermissionGroupInfo getPermissionGroupInfo(String name, int flags); - boolean addPermission(in PermissionInfo info); - boolean addPermissionAsync(in PermissionInfo info); - void removePermission(String name); - int checkPermission(String permName, String pkgName, int userId); - void grantRuntimePermission(String packageName, String permissionName, int userId); - int checkUidPermission(String permName, int uid); - void setMimeGroup(String packageName, String group, in List mimeTypes); - String getSplashScreenTheme(String packageName, int userId); - void setSplashScreenTheme(String packageName, String themeName, int userId); - List getMimeGroup(String packageName, String group); - boolean isAutoRevokeWhitelisted(String packageName); - void makeProviderVisible(int recipientAppId, String visibleAuthority); - void makeUidVisible(int recipientAppId, int visibleUid); - IBinder getHoldLockToken(); - void holdLock(in IBinder token, in int durationMs); - void setKeepUninstalledPackages(in List packageList); - boolean canPackageQuery(String sourcePackageName, String targetPackageName, int userId); -} \ No newline at end of file diff --git a/app/src/main/aidl/android/content/pm/PackageInstaller.aidl b/app/src/main/aidl/android/content/pm/PackageInstaller.aidl deleted file mode 100644 index dfca396..0000000 --- a/app/src/main/aidl/android/content/pm/PackageInstaller.aidl +++ /dev/null @@ -1,4 +0,0 @@ -package android.content.pm; - -parcelable PackageInstaller.SessionParams; -parcelable PackageInstaller.SessionInfo; \ No newline at end of file diff --git a/app/src/main/aidl/com/rosan/dhizuku/aidl/IDhizukuUserServiceManager.aidl b/app/src/main/aidl/com/rosan/dhizuku/aidl/IDhizukuUserServiceManager.aidl new file mode 100644 index 0000000..0360cd9 --- /dev/null +++ b/app/src/main/aidl/com/rosan/dhizuku/aidl/IDhizukuUserServiceManager.aidl @@ -0,0 +1,5 @@ +package com.rosan.dhizuku.aidl; + +interface IDhizukuUserServiceManager { + void destroy(); +} \ No newline at end of file diff --git a/app/src/main/aidl/com/rosan/dhizuku/aidl/ITest.aidl b/app/src/main/aidl/com/rosan/dhizuku/aidl/ITest.aidl new file mode 100644 index 0000000..87dd782 --- /dev/null +++ b/app/src/main/aidl/com/rosan/dhizuku/aidl/ITest.aidl @@ -0,0 +1,6 @@ +package com.rosan.dhizuku.aidl; + +interface ITest { + void onCreate() = 1; + void onDestroy() = 2; +} \ No newline at end of file diff --git a/app/src/main/java/com/rosan/dhizuku/data/process/model/impl/DhizukuUserServiceRepoImpl.kt b/app/src/main/java/com/rosan/dhizuku/data/process/model/impl/DhizukuUserServiceRepoImpl.kt new file mode 100644 index 0000000..c5f664b --- /dev/null +++ b/app/src/main/java/com/rosan/dhizuku/data/process/model/impl/DhizukuUserServiceRepoImpl.kt @@ -0,0 +1,117 @@ +package com.rosan.dhizuku.data.process.model.impl + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.os.Binder +import android.os.Bundle +import android.os.IBinder +import android.os.IInterface +import android.os.Looper +import android.os.Parcel +import com.rosan.dhizuku.aidl.IDhizukuUserServiceManager +import com.rosan.dhizuku.data.process.repo.ProcessRepo +import com.rosan.dhizuku.data.reflect.repo.ReflectRepo +import com.rosan.dhizuku.server.DhizukuProcessReceiver +import com.rosan.dhizuku.shared.DhizukuVariables +import org.apache.commons.cli.DefaultParser +import org.apache.commons.cli.Option +import org.apache.commons.cli.Options +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import java.lang.reflect.Constructor +import kotlin.system.exitProcess + +class DhizukuUserServiceRepoImpl { + companion object : ProcessRepo(), KoinComponent { + private val context by inject() + + private val reflect by inject() + + private val options = Options().addOption( + Option.builder("c").argName("component name (pName/cName)").required().hasArg() + .type(String::class.java).build() + ) + + @JvmStatic + override fun main(args: Array) = super.main(args) + + override fun onCreate(args: Array) { + super.onCreate(args) + val cmdLine = DefaultParser().parse(options, args) + val componentName = ComponentName.unflattenFromString(cmdLine.getOptionValue("c")) + ?: throw Exception("need component name") + val service = createService(componentName).asBinder() + transactService(service, 1) + val manager = createManager(service) + val bundle = Bundle() + bundle.putBinder(DhizukuProcessReceiver.PARAM_MANAGER, manager.asBinder()) + bundle.putBinder(DhizukuProcessReceiver.PARAM_USER_SERVICE, service) + bundle.putParcelable(DhizukuVariables.PARAM_COMPONENT, componentName) + val intent = Intent(DhizukuProcessReceiver.ACTION_USER_SERVICE).putExtras(bundle) + context.sendBroadcast(intent) + Looper.loop() + } + + private fun createManager(service: IBinder): IDhizukuUserServiceManager { + return object : IDhizukuUserServiceManager.Stub() { + override fun destroy() { + transactService(service, 2) + exitProcess(0) + } + } + } + + private fun createService(componentName: ComponentName): IInterface { +// // compat for multi-user +// val userHandle = UserHandleCompat.getUserHandleForUid(uid) +// val packageContext = reflect.getDeclaredMethod( +// Context::class.java, +// "createPackageContextAsUser", +// String::class.java, +// Int::class.java, +// UserHandle::class.java +// )?.let { +// it.isAccessible = true +// it.invoke( +// context, +// componentName.packageName, +// Context.CONTEXT_INCLUDE_CODE or Context.CONTEXT_IGNORE_SECURITY, +// userHandle +// ) +// } as Context + val packageContext = context.createPackageContext( + componentName.packageName, + Context.CONTEXT_INCLUDE_CODE or Context.CONTEXT_IGNORE_SECURITY + ) + val serviceClazz = packageContext.classLoader.loadClass(componentName.className) + var constructorWithContext: Constructor<*>? = null + try { + constructorWithContext = serviceClazz.getConstructor(Context::class.java) + } catch (_: NoSuchMethodException) { + } catch (_: SecurityException) { + } + return (if (constructorWithContext != null) constructorWithContext.newInstance(context) + else serviceClazz.newInstance()).apply { + } as IInterface + } + + private fun transactService(service: IBinder, code: Int) { + var dataParcel: Parcel? = null + var replyParcel: Parcel? = null + try { + val data = Parcel.obtain() + dataParcel = data + val reply = Parcel.obtain() + replyParcel = reply + data.writeInterfaceToken(service.interfaceDescriptor!!) + service.transact( + IBinder.FIRST_CALL_TRANSACTION + code, data, reply, Binder.FLAG_ONEWAY + ) + } finally { + dataParcel?.recycle() + replyParcel?.recycle() + } + } + } +} diff --git a/app/src/main/java/com/rosan/dhizuku/data/process/repo/ProcessRepo.kt b/app/src/main/java/com/rosan/dhizuku/data/process/repo/ProcessRepo.kt new file mode 100644 index 0000000..8630544 --- /dev/null +++ b/app/src/main/java/com/rosan/dhizuku/data/process/repo/ProcessRepo.kt @@ -0,0 +1,49 @@ +package com.rosan.dhizuku.data.process.repo + +import com.rosan.dhizuku.di.init.processModules +import org.koin.core.context.startKoin +import java.io.FileDescriptor +import java.io.FileOutputStream +import java.io.OutputStream +import java.io.PrintStream +import kotlin.system.exitProcess + +abstract class ProcessRepo { + /* + * use @JvmStatic + * */ + open fun main(args: Array) { + setIgnoreWarning(true) + val exception = kotlin.runCatching { + startKoin { + modules(processModules) + } + onCreate(args) + destroy() + }.exceptionOrNull() + setIgnoreWarning(false) + if (exception == null) return + exception.printStackTrace() + throw exception + } + + protected open fun onCreate(args: Array) { + } + + fun destroy(code: Int = 0) { + onDestroy() + exitProcess(code) + } + + protected open fun onDestroy() { + } + + private fun setIgnoreWarning(state: Boolean) { + System.setErr(PrintStream(if (state) NullOutputStream else FileOutputStream(FileDescriptor.err))) + } + + object NullOutputStream : OutputStream() { + override fun write(b: Int) { + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/rosan/dhizuku/data/process/util/ProcessUtil.kt b/app/src/main/java/com/rosan/dhizuku/data/process/util/ProcessUtil.kt new file mode 100644 index 0000000..4e47ece --- /dev/null +++ b/app/src/main/java/com/rosan/dhizuku/data/process/util/ProcessUtil.kt @@ -0,0 +1,48 @@ +package com.rosan.dhizuku.data.process.util + +import android.content.Context +import android.os.Build +import org.koin.core.component.KoinComponent +import org.koin.core.component.get +import java.io.PrintWriter +import java.util.concurrent.TimeUnit +import kotlin.reflect.KClass + +object ProcessUtil : KoinComponent { + fun start(clazz: KClass, vararg params: Any?): Process { + val process = Runtime.getRuntime().exec("sh") + val writer = PrintWriter(process.outputStream, true) + val cmd = ArrayList() + cmd.add("/system/bin/app_process") + cmd.add(String.format("-Djava.class.path='%s'", get().packageCodePath)) + cmd.add("/system/bin") + cmd.add(String.format("'%s'", clazz.qualifiedName)) + cmd.addAll(params.map { it.toString() }) + writer.println(cmd.joinToString(" ")) + writer.println("exit $?") + return process + } + + fun waitFor(process: Process, timeout: Long, unit: TimeUnit): Boolean { + return kotlin.runCatching { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) process.waitFor(timeout, unit) + else { + val startTime = System.nanoTime() + var rem = unit.toNanos(timeout) + + do { + try { + process.exitValue() + return@runCatching true + } catch (ex: IllegalThreadStateException) { + if (rem > 0) Thread.sleep( + (TimeUnit.NANOSECONDS.toMillis(rem) + 1).coerceAtMost(100) + ) + } + rem = unit.toNanos(timeout) - (System.nanoTime() - startTime) + } while (rem > 0) + return@runCatching false + } + }.getOrDefault(false) + } +} diff --git a/app/src/main/java/com/rosan/dhizuku/data/reflect/model/impl/ReflectRepoImpl.kt b/app/src/main/java/com/rosan/dhizuku/data/reflect/model/impl/ReflectRepoImpl.kt new file mode 100644 index 0000000..1c9bcd1 --- /dev/null +++ b/app/src/main/java/com/rosan/dhizuku/data/reflect/model/impl/ReflectRepoImpl.kt @@ -0,0 +1,93 @@ +package com.rosan.dhizuku.data.reflect.model.impl + +import com.rosan.dhizuku.data.reflect.repo.ReflectRepo +import java.lang.reflect.Constructor +import java.lang.reflect.Field +import java.lang.reflect.Method + +class ReflectRepoImpl : ReflectRepo { + override fun getConstructors(clazz: Class<*>): Array> = clazz.constructors + override fun getDeclaredConstructors(clazz: Class<*>): Array> = + clazz.declaredConstructors + + override fun getFields(clazz: Class<*>): Array = clazz.fields + + override fun getDeclaredFields(clazz: Class<*>): Array = clazz.declaredFields + + override fun getMethods(clazz: Class<*>): Array = clazz.methods + + override fun getDeclaredMethods(clazz: Class<*>): Array = clazz.declaredMethods + + override fun getConstructor(clazz: Class<*>, vararg parameterTypes: Class<*>): Constructor<*>? { + for (constructor in getConstructors(clazz)) { + val expectedTypes = constructor.parameterTypes + if (expectedTypes.size != parameterTypes.size) continue + for (i in expectedTypes.indices) + if (expectedTypes[i] != parameterTypes[i]) continue + return constructor + } + return null + } + + override fun getDeclaredConstructor( + clazz: Class<*>, + vararg parameterTypes: Class<*> + ): Constructor<*>? { + for (constructor in getDeclaredConstructors(clazz)) { + val expectedTypes = constructor.parameterTypes + if (expectedTypes.size != parameterTypes.size) continue + for (i in expectedTypes.indices) + if (expectedTypes[i] != parameterTypes[i]) continue + return constructor + } + return null + } + + override fun getField(clazz: Class<*>, name: String): Field? { + for (field in getFields(clazz)) { + if (field.name != name) continue + return field + } + return null + } + + override fun getDeclaredField(clazz: Class<*>, name: String): Field? { + for (field in getDeclaredFields(clazz)) { + if (field.name != name) continue + return field + } + return null + } + + override fun getMethod( + clazz: Class<*>, + name: String, + vararg parameterTypes: Class<*> + ): Method? { + for (method in getMethods(clazz)) { + if (method.name != name) continue + val expectedTypes = method.parameterTypes + if (expectedTypes.size != parameterTypes.size) continue + for (i in expectedTypes.indices) + if (expectedTypes[i] != parameterTypes[i]) continue + return method + } + return null + } + + override fun getDeclaredMethod( + clazz: Class<*>, + name: String, + vararg parameterTypes: Class<*> + ): Method? { + for (method in getDeclaredMethods(clazz)) { + if (method.name != name) continue + val expectedTypes = method.parameterTypes + if (expectedTypes.size != parameterTypes.size) continue + for (i in expectedTypes.indices) + if (expectedTypes[i] != parameterTypes[i]) continue + return method + } + return null + } +} \ No newline at end of file diff --git a/app/src/main/java/com/rosan/dhizuku/data/reflect/repo/ReflectRepo.kt b/app/src/main/java/com/rosan/dhizuku/data/reflect/repo/ReflectRepo.kt new file mode 100644 index 0000000..84c3e84 --- /dev/null +++ b/app/src/main/java/com/rosan/dhizuku/data/reflect/repo/ReflectRepo.kt @@ -0,0 +1,33 @@ +package com.rosan.dhizuku.data.reflect.repo + +import java.lang.reflect.Constructor +import java.lang.reflect.Field +import java.lang.reflect.Method + +interface ReflectRepo { + fun getConstructors(clazz: Class<*>): Array> + + fun getDeclaredConstructors(clazz: Class<*>): Array> + + fun getFields(clazz: Class<*>): Array + + fun getDeclaredFields(clazz: Class<*>): Array + + fun getMethods(clazz: Class<*>): Array + + fun getDeclaredMethods(clazz: Class<*>): Array + + fun getConstructor(clazz: Class<*>, vararg parameterTypes: Class<*>): Constructor<*>? + + fun getDeclaredConstructor( + clazz: Class<*>, vararg parameterTypes: Class<*> + ): Constructor<*>? + + fun getField(clazz: Class<*>, name: String): Field? + + fun getDeclaredField(clazz: Class<*>, name: String): Field? + + fun getMethod(clazz: Class<*>, name: String, vararg parameterTypes: Class<*>): Method? + + fun getDeclaredMethod(clazz: Class<*>, name: String, vararg parameterTypes: Class<*>): Method? +} \ No newline at end of file diff --git a/app/src/main/java/com/rosan/dhizuku/di/init/process/context_module.kt b/app/src/main/java/com/rosan/dhizuku/di/init/process/context_module.kt new file mode 100644 index 0000000..7e91dd4 --- /dev/null +++ b/app/src/main/java/com/rosan/dhizuku/di/init/process/context_module.kt @@ -0,0 +1,56 @@ +package com.rosan.dhizuku.di.init.process + +import android.annotation.SuppressLint +import android.app.ActivityThread +import android.content.Context +import android.os.Build +import android.os.Looper +import android.os.Process +import com.rosan.dhizuku.data.reflect.repo.ReflectRepo +import org.koin.dsl.module + +@SuppressLint("PrivateApi") +val contextModule = module { + single { + // Normal app will have this. + var thread = ActivityThread.currentActivityThread() + if (thread == null) { + // If not normal app (launch by app_process or other). + if (Looper.getMainLooper() == null) Looper.prepareMainLooper() + thread = ActivityThread.systemMain() + } + // so get normal application or system context. + val context: Context = thread.application ?: thread.systemContext + val uid = Process.myUid() + val packageNames = + context.packageManager.getPackagesForUid(uid) + if (packageNames.isNullOrEmpty()) return@single context + // if current context isn't system context, return it. + if ( + context.packageName in packageNames && + (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || + context.opPackageName in packageNames) + ) return@single context + // create a context for packageName. + val packageName = packageNames.first() + val packageContext = context.createPackageContext( + packageName, + Context.CONTEXT_IGNORE_SECURITY or Context.CONTEXT_INCLUDE_CODE + ) + val reflect: ReflectRepo = get() + val apk = reflect.getDeclaredField(packageContext::class.java, "mPackageInfo")?.let { + it.isAccessible = true + return@let it.get(packageContext) + }!! + val appContext = reflect.getDeclaredMethod( + packageContext::class.java, + "createAppContext", + ActivityThread::class.java, + apk::class.java + )?.let { + it.isAccessible = true + return@let it.invoke(null, thread, apk) + } as Context + appContext + } +} \ No newline at end of file diff --git a/app/src/main/java/com/rosan/dhizuku/di/init/process_modules.kt b/app/src/main/java/com/rosan/dhizuku/di/init/process_modules.kt new file mode 100644 index 0000000..784d46b --- /dev/null +++ b/app/src/main/java/com/rosan/dhizuku/di/init/process_modules.kt @@ -0,0 +1,11 @@ +package com.rosan.dhizuku.di.init + +import com.rosan.dhizuku.di.init.process.contextModule +import com.rosan.dhizuku.di.reflectModule + +val processModules = listOf( + contextModule, + reflectModule +) + + diff --git a/app/src/main/java/com/rosan/dhizuku/di/reflect_module.kt b/app/src/main/java/com/rosan/dhizuku/di/reflect_module.kt new file mode 100644 index 0000000..715be6f --- /dev/null +++ b/app/src/main/java/com/rosan/dhizuku/di/reflect_module.kt @@ -0,0 +1,16 @@ +package com.rosan.dhizuku.di + +import android.os.Build +import com.rosan.dhizuku.data.reflect.model.impl.ReflectRepoImpl +import com.rosan.dhizuku.data.reflect.repo.ReflectRepo +import org.koin.dsl.module +import org.lsposed.hiddenapibypass.HiddenApiBypass + +val reflectModule = module { + single { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + HiddenApiBypass.setHiddenApiExemptions("") + } + ReflectRepoImpl() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/rosan/dhizuku/server/DhizukuConstants.kt b/app/src/main/java/com/rosan/dhizuku/server/DhizukuConstants.kt index c051776..49b1a8d 100644 --- a/app/src/main/java/com/rosan/dhizuku/server/DhizukuConstants.kt +++ b/app/src/main/java/com/rosan/dhizuku/server/DhizukuConstants.kt @@ -1,5 +1,5 @@ package com.rosan.dhizuku.server -val DHIZUKU_SERVRE_VERSION_CODE = 2 +val DHIZUKU_SERVRE_VERSION_CODE = 3 -val DHIZUKU_SERVER_VERSION_NAME = "1.1" \ No newline at end of file +val DHIZUKU_SERVER_VERSION_NAME = "1.2" \ No newline at end of file diff --git a/app/src/main/java/com/rosan/dhizuku/server/DhizukuProcessReceiver.kt b/app/src/main/java/com/rosan/dhizuku/server/DhizukuProcessReceiver.kt new file mode 100644 index 0000000..a4cfbbe --- /dev/null +++ b/app/src/main/java/com/rosan/dhizuku/server/DhizukuProcessReceiver.kt @@ -0,0 +1,59 @@ +package com.rosan.dhizuku.server + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import com.rosan.dhizuku.aidl.IDhizukuUserServiceManager + +object DhizukuProcessReceiver : BroadcastReceiver() { + const val ACTION_USER_SERVICE = "com.rosan.dhizuku.process.user_service" + + const val PARAM_MANAGER = "manager" + + const val PARAM_USER_SERVICE = "service" + + const val PARAM_TOKEN = "token" + + private var listeners = listOf() + + override fun onReceive(context: Context?, intent: Intent?) { + intent ?: return + when (intent.action) { + ACTION_USER_SERVICE -> onReceiveUserService(intent) + } + } + + private fun onReceiveUserService(intent: Intent) { + val extras = intent.extras ?: return + val manager = IDhizukuUserServiceManager.Stub.asInterface(extras.getBinder(PARAM_MANAGER)) + val service = extras.getBinder(PARAM_USER_SERVICE) ?: return + val args = DhizukuUserServiceArgs(extras) + + listeners.forEach { + it.onReceive(args, DhizukuUserServiceConnections.UserService(manager, service)) + } + } + + fun addUserServiceListener(listener: OnUserServiceReceiverListener) { + listeners = ArrayList(listeners).apply { + add(listener) + } + } + + fun removeUserServiceListener(listener: OnUserServiceReceiverListener) { + listeners = ArrayList(listeners).apply { + remove(listener) + } + } + + fun clearUserServiceListeners() { + listeners = listOf() + } + + interface OnUserServiceReceiverListener { + fun onReceive( + args: DhizukuUserServiceArgs, + service: DhizukuUserServiceConnections.UserService + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/rosan/dhizuku/server/DhizukuProvider.kt b/app/src/main/java/com/rosan/dhizuku/server/DhizukuProvider.kt index e3021be..615300e 100644 --- a/app/src/main/java/com/rosan/dhizuku/server/DhizukuProvider.kt +++ b/app/src/main/java/com/rosan/dhizuku/server/DhizukuProvider.kt @@ -5,12 +5,10 @@ import android.content.ContentValues import android.database.Cursor import android.net.Uri import android.os.Bundle -import com.rosan.dhizuku.aidl.IDhizuku import com.rosan.dhizuku.aidl.IDhizukuClient +import com.rosan.dhizuku.server.impl.IDhizukuImpl import com.rosan.dhizuku.shared.DhizukuVariables import org.koin.core.component.KoinComponent -import org.koin.core.component.get -import org.koin.core.parameter.parametersOf class DhizukuProvider : ContentProvider(), KoinComponent { override fun call(method: String, arg: String?, extras: Bundle?): Bundle? { @@ -20,10 +18,7 @@ class DhizukuProvider : ContentProvider(), KoinComponent { val client = if (clientIBinder != null) IDhizukuClient.Stub.asInterface(clientIBinder) else null - val iDhizuku: IDhizuku = get(parameters = { - parametersOf(client) - }) - putBinder(DhizukuVariables.PARAM_DHIZUKU_BINDER, iDhizuku.asBinder()) + putBinder(DhizukuVariables.PARAM_DHIZUKU_BINDER, IDhizukuImpl(client).asBinder()) } else -> null; diff --git a/app/src/main/java/com/rosan/dhizuku/server/DhizukuService.kt b/app/src/main/java/com/rosan/dhizuku/server/DhizukuService.kt index 7bbcd9f..7b71d7a 100644 --- a/app/src/main/java/com/rosan/dhizuku/server/DhizukuService.kt +++ b/app/src/main/java/com/rosan/dhizuku/server/DhizukuService.kt @@ -50,11 +50,17 @@ class DhizukuService : Service(), KoinComponent { override fun onDestroy() { unregisterReceiver(packageReceiver) + unregisterReceiver(DhizukuProcessReceiver) super.onDestroy() } private suspend fun run() { runForeground() + registerPackageReceiver() + registerProcessReceiver() + } + + private fun registerPackageReceiver() { val filter = IntentFilter() filter.addAction(Intent.ACTION_PACKAGE_ADDED) filter.addAction(Intent.ACTION_PACKAGE_REMOVED) @@ -62,6 +68,11 @@ class DhizukuService : Service(), KoinComponent { registerReceiver(packageReceiver, filter) } + private fun registerProcessReceiver() { + val filter = IntentFilter(DhizukuProcessReceiver.ACTION_USER_SERVICE) + registerReceiver(DhizukuProcessReceiver, filter) + } + private suspend fun runForeground() { val manager = NotificationManagerCompat.from(this) val channelName = "service_channel" diff --git a/app/src/main/java/com/rosan/dhizuku/server/DhizukuUserServiceArgs.kt b/app/src/main/java/com/rosan/dhizuku/server/DhizukuUserServiceArgs.kt new file mode 100644 index 0000000..25fbd19 --- /dev/null +++ b/app/src/main/java/com/rosan/dhizuku/server/DhizukuUserServiceArgs.kt @@ -0,0 +1,30 @@ +package com.rosan.dhizuku.server + +import android.content.ComponentName +import android.os.Build +import android.os.Bundle +import com.rosan.dhizuku.shared.DhizukuVariables + +class DhizukuUserServiceArgs { + private var bundle = Bundle(); + + var componentName: ComponentName + get() = (if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) + bundle.getParcelable(DhizukuVariables.PARAM_COMPONENT, ComponentName::class.java) + else bundle.getParcelable(DhizukuVariables.PARAM_COMPONENT))!! + set(value) = bundle.putParcelable(DhizukuVariables.PARAM_COMPONENT, value) + + constructor(bundle: Bundle) { + this.bundle = bundle + } + + constructor(args: DhizukuUserServiceArgs) : this(args.bundle) + + constructor(name: ComponentName) : this(Bundle().apply { + putParcelable(DhizukuVariables.PARAM_COMPONENT, name) + }) + + fun build(): Bundle { + return bundle; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/rosan/dhizuku/server/DhizukuUserServiceConnection.kt b/app/src/main/java/com/rosan/dhizuku/server/DhizukuUserServiceConnection.kt new file mode 100644 index 0000000..d29b053 --- /dev/null +++ b/app/src/main/java/com/rosan/dhizuku/server/DhizukuUserServiceConnection.kt @@ -0,0 +1,69 @@ +package com.rosan.dhizuku.server + +import android.os.IBinder +import com.rosan.dhizuku.aidl.IDhizukuUserServiceConnection + +class DhizukuUserServiceConnection( + val uid: Int, + val pid: Int, + connection: IDhizukuUserServiceConnection +) { + private var _connection = connection + + var connection: IDhizukuUserServiceConnection + get() = _connection + set(value) { + kotlin.runCatching { _connection.asBinder().unlinkToDeath(recipient, 0) } + _connection = value + kotlin.runCatching { + _connection.asBinder().linkToDeath(recipient, 0) + } + } + + private val recipient = IBinder.DeathRecipient { + DhizukuUserServiceConnections.unbind(uid, pid) + } + + private val tokens = mutableListOf() + + init { + kotlin.runCatching { + _connection.asBinder().linkToDeath(recipient, 0) + } + } + + fun register(args: DhizukuUserServiceArgs) { + val name = args.componentName + val token = name.flattenToString() + tokens.remove(token) + tokens.add(token) + } + + fun unregister(args: DhizukuUserServiceArgs) { + val name = args.componentName + val token = name.flattenToString() + tokens.remove(token) + } + + fun isRegister(token: String) = tokens.contains(token) + + fun connected( + args: DhizukuUserServiceArgs, + service: DhizukuUserServiceConnections.UserService + ) { + val name = args.componentName + val token = name.flattenToString() + if (token in tokens) kotlin.runCatching { + connection.connected( + args.build(), + service.service + ) + } + } + + fun died(args: DhizukuUserServiceArgs) { + val name = args.componentName + val token = name.flattenToString() + if (token in tokens) kotlin.runCatching { connection.died(args.build()) } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/rosan/dhizuku/server/DhizukuUserServiceConnections.kt b/app/src/main/java/com/rosan/dhizuku/server/DhizukuUserServiceConnections.kt new file mode 100644 index 0000000..38334be --- /dev/null +++ b/app/src/main/java/com/rosan/dhizuku/server/DhizukuUserServiceConnections.kt @@ -0,0 +1,159 @@ +package com.rosan.dhizuku.server + +import android.os.IBinder +import com.rosan.dhizuku.aidl.IDhizukuUserServiceConnection +import com.rosan.dhizuku.aidl.IDhizukuUserServiceManager +import com.rosan.dhizuku.data.process.model.impl.DhizukuUserServiceRepoImpl +import com.rosan.dhizuku.data.process.util.ProcessUtil +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.cancel +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.channels.trySendBlocking +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.coroutines.selects.select +import rikka.shizuku.Shizuku +import rikka.sui.Sui + +object DhizukuUserServiceConnections { + private fun onServiceConnected(args: DhizukuUserServiceArgs, service: UserService) { + connectionMap.values.forEach { + it.connected(args, service) + } + services[args.componentName.flattenToString()] = service + kotlin.runCatching { + service.service.linkToDeath({ + onServiceDisconnected(args) + }, 0) + } + } + + private fun onServiceDisconnected(args: DhizukuUserServiceArgs) { + connectionMap.values.forEach { + it.died(args) + } + services.remove(args.componentName.flattenToString()) + } + + private val connectionMap = mutableMapOf() + + private val services = mutableMapOf() + + fun start(args: DhizukuUserServiceArgs) { + val name = args.componentName + val token = name.flattenToString() + val service = services[token] + if (service != null) return + CoroutineScope(Dispatchers.IO).launch { + startInner(args) + } + } + + private suspend fun startInner(args: DhizukuUserServiceArgs): UserService? { + val scope = CoroutineScope(Dispatchers.IO) + var process: Process? = null + val serviceResult = scope.async { + callbackFlow { + val listener = object : DhizukuProcessReceiver.OnUserServiceReceiverListener { + override fun onReceive(_args: DhizukuUserServiceArgs, service: UserService) { + if (args.componentName.flattenToString() != _args.componentName.flattenToString()) return + trySendBlocking(service) + close() + } + } + DhizukuProcessReceiver.addUserServiceListener(listener) + process = ProcessUtil.start( + DhizukuUserServiceRepoImpl::class, + "-c='${args.componentName.flattenToString()}'" + ) + awaitClose { + DhizukuProcessReceiver.removeUserServiceListener(listener) + } + }.first() + } + val timeoutResult = scope.async { + delay(15 * 1000) + null + } + val service = select { + serviceResult.onAwait { it } + timeoutResult.onAwait { it } + } + scope.cancel() + if (service == null) { + kotlin.runCatching { process?.destroy() } + } else { + onServiceConnected(args, service) + } + return service + } + + fun stop(args: DhizukuUserServiceArgs) { + val name = args.componentName + val token = name.flattenToString() + stop(token) + } + + private fun stop(token: String) { + val service = services[token] ?: return + kotlin.runCatching { service.manager.destroy() } + } + + fun bind( + uid: Int, pid: Int, args: DhizukuUserServiceArgs, iConnection: IDhizukuUserServiceConnection + ) { + val owner = "$uid:$pid" + val name = args.componentName + val token = name.flattenToString() + val connection = + connectionMap[owner] ?: DhizukuUserServiceConnection(uid, pid, iConnection).apply { + connectionMap[owner] = this + } + connection.connection = iConnection + connection.register(args) + val service = services[token] + if (service == null) { + start(args) + return + } + connection.connected(args, service) + } + + fun unbind(uid: Int, pid: Int, args: DhizukuUserServiceArgs) { + val owner = "$uid:$pid" + val name = args.componentName + val token = name.flattenToString() + val connection = connectionMap[owner] ?: return + connection.unregister(args) + afterUnbind() + } + + fun unbind(uid: Int, pid: Int) { + val owner = "$uid:$pid" + connectionMap.remove(owner) + afterUnbind() + } + + private fun afterUnbind() { + val tokens = mutableListOf() + tokens.addAll(services.keys) + for (token in services.keys) { + for (connection in connectionMap.values) { + if (connection.isRegister(token)) { + tokens.remove(token) + break + } + } + } + for (token in tokens) { + stop(token) + } + Shizuku.checkSelfPermission() + } + + data class UserService(val manager: IDhizukuUserServiceManager, val service: IBinder) +} diff --git a/app/src/main/java/com/rosan/dhizuku/server/impl/IDhizukuImpl.kt b/app/src/main/java/com/rosan/dhizuku/server/impl/IDhizukuImpl.kt index df54c3e..ecc39b6 100644 --- a/app/src/main/java/com/rosan/dhizuku/server/impl/IDhizukuImpl.kt +++ b/app/src/main/java/com/rosan/dhizuku/server/impl/IDhizukuImpl.kt @@ -1,25 +1,24 @@ package com.rosan.dhizuku.server.impl -import android.content.Context import android.os.Binder import android.os.Bundle import android.os.IBinder -import android.os.IInterface import android.os.Parcel import com.rosan.dhizuku.aidl.IDhizuku import com.rosan.dhizuku.aidl.IDhizukuClient import com.rosan.dhizuku.aidl.IDhizukuRemoteProcess +import com.rosan.dhizuku.aidl.IDhizukuUserServiceConnection import com.rosan.dhizuku.data.settings.repo.AppRepo import com.rosan.dhizuku.server.DHIZUKU_SERVER_VERSION_NAME import com.rosan.dhizuku.server.DHIZUKU_SERVRE_VERSION_CODE +import com.rosan.dhizuku.server.DhizukuUserServiceArgs +import com.rosan.dhizuku.server.DhizukuUserServiceConnections import com.rosan.dhizuku.shared.DhizukuVariables import org.koin.core.component.KoinComponent import org.koin.core.component.inject import java.io.File class IDhizukuImpl(private val client: IDhizukuClient? = null) : IDhizuku.Stub(), KoinComponent { - private val context by inject() - private val appRepo by inject() override fun getVersionCode(): Int = DHIZUKU_SERVRE_VERSION_CODE @@ -54,6 +53,26 @@ class IDhizukuImpl(private val client: IDhizukuClient? = null) : IDhizuku.Stub() return IDhizukuRemoteProcessImpl(process, client?.asBinder() ?: this) } + override fun bindUserService( + connection: IDhizukuUserServiceConnection?, + bundle: Bundle? + ) { + bundle ?: return + connection ?: return + val uid = Binder.getCallingUid() + val pid = Binder.getCallingPid() + val args = DhizukuUserServiceArgs(bundle) + DhizukuUserServiceConnections.bind(uid, pid, args, connection) + } + + override fun unbindUserService(bundle: Bundle?) { + bundle ?: return + val uid = Binder.getCallingUid() + val pid = Binder.getCallingPid() + val args = DhizukuUserServiceArgs(bundle) + DhizukuUserServiceConnections.unbind(uid, pid, args) + } + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean { if (code == DhizukuVariables.TRANSACT_CODE_REMOTE_BINDER) { val targetData = Parcel.obtain() diff --git a/app/src/main/java/com/rosan/dhizuku/ui/page/settings/home/HomePage.kt b/app/src/main/java/com/rosan/dhizuku/ui/page/settings/home/HomePage.kt index 46535a2..161f09b 100644 --- a/app/src/main/java/com/rosan/dhizuku/ui/page/settings/home/HomePage.kt +++ b/app/src/main/java/com/rosan/dhizuku/ui/page/settings/home/HomePage.kt @@ -1,9 +1,9 @@ package com.rosan.dhizuku.ui.page.settings.home +import android.annotation.SuppressLint import android.app.admin.DevicePolicyManager import android.content.* import android.net.Uri -import android.os.Bundle import android.text.method.LinkMovementMethod import android.widget.TextView import androidx.compose.animation.AnimatedContent @@ -32,7 +32,6 @@ import com.rosan.dhizuku.data.console.repo.ConsoleRepo import com.rosan.dhizuku.data.console.util.ConsoleRepoUtil import com.rosan.dhizuku.data.console.util.ConsoleUtil import com.rosan.dhizuku.server.DhizukuDAReceiver -import com.rosan.dhizuku.ui.activity.RequestPermissionActivity import com.rosan.dhizuku.ui.widget.dialog.PositionDialog import com.rosan.dhizuku.util.toast import kotlinx.coroutines.Dispatchers @@ -72,6 +71,7 @@ fun HomePage(navController: NavController) { } } +@SuppressLint("MissingPermission") @Composable fun StatusWidget() { val context = LocalContext.current @@ -96,7 +96,6 @@ fun StatusWidget() { else if (isDeviceAdminer) R.string.device_admin_granted else R.string.device_admin_denied ) - CardWidget(colors = CardDefaults.elevatedCardColors( containerColor = containerColor, contentColor = onContainerColor ), content = { @@ -109,16 +108,6 @@ fun StatusWidget() { Spacer(modifier = Modifier.size(24.dp)) Text(text = text, style = MaterialTheme.typography.titleMedium) } - }, onClick = { - context.startActivity( - Intent(context, RequestPermissionActivity::class.java).putExtra( - "bundle", - Bundle().apply { - putInt("uid", 10330) - }).addFlags( - Intent.FLAG_ACTIVITY_NEW_TASK - ) - ) }) } @@ -184,6 +173,7 @@ fun ShizukuButton() { exception is IllegalStateException && exception?.message == "binder haven't been received" -> stringResource( R.string.shizuku_binder_not_received ) + else -> ByteArrayOutputStream().also { exception?.printStackTrace(PrintStream(it)) }.toByteArray().decodeToString() @@ -466,8 +456,7 @@ fun CardWidget( } if (buttons != null) { FlowRow( - modifier = Modifier - .padding(horizontal = 12.dp), + modifier = Modifier.padding(horizontal = 12.dp), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { buttons() diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3d78786..a347be5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ # https://mvnrepository.com/ [versions] -agp = "8.0.0" +agp = "8.0.1" kotlin = "1.8.10" compose = "1.4.0" room = "2.5.1" @@ -51,13 +51,19 @@ accompanist-flowlayout = { group = "com.google.accompanist", name = "accompanist accompanist-drawablepainter = { group = "com.google.accompanist", name = "accompanist-drawablepainter", version.ref = "accompanist" } accompanist-systemuicontroller = { group = "com.google.accompanist", name = "accompanist-systemuicontroller", version.ref = "accompanist" } +# AndroidHiddenApiBypass +# https://github.com/LSPosed/AndroidHiddenApiBypass +lsposed-hiddenapibypass = { group = "org.lsposed.hiddenapibypass", name = "hiddenapibypass", version = "4.3" } + # XXPermissions # https://github.com/getActivity/XXPermissions xxpermissions = { group = "com.github.getActivity", name = "XXPermissions", version = "16.6" } -# Shizuku -# https://github.com/getActivity/XXPermissions # Rikka Shizuku # https://github.com/RikkaApps/Shizuku-API rikka-shizuku-api = { group = "dev.rikka.shizuku", name = "api", version.ref = "rikka-shizuku" } -rikka-shizuku-provider = { group = "dev.rikka.shizuku", name = "provider", version.ref = "rikka-shizuku" } \ No newline at end of file +rikka-shizuku-provider = { group = "dev.rikka.shizuku", name = "provider", version.ref = "rikka-shizuku" } + +# Commons CLI +# https://search.maven.org/artifact/commons-cli/commons-cli +commons-cli = { group = "commons-cli", name = "commons-cli", version = "1.5.0" } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2871aa8..2a9f1c4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Tue Dec 13 22:04:53 CST 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/hidden-api/src/main/java/android/app/ActivityThread.java b/hidden-api/src/main/java/android/app/ActivityThread.java new file mode 100644 index 0000000..924afd5 --- /dev/null +++ b/hidden-api/src/main/java/android/app/ActivityThread.java @@ -0,0 +1,22 @@ +package android.app; + +public final class ActivityThread { + public static void main(String[] args) { + } + + public static ActivityThread systemMain() { + throw new RuntimeException("STUB"); + } + + public static ActivityThread currentActivityThread() { + throw new RuntimeException("STUB"); + } + + public ContextImpl getSystemContext() { + throw new RuntimeException("STUB"); + } + + public Application getApplication() { + throw new RuntimeException("STUB"); + } +} diff --git a/hidden-api/src/main/java/android/app/ContextImpl.java b/hidden-api/src/main/java/android/app/ContextImpl.java new file mode 100644 index 0000000..73cb972 --- /dev/null +++ b/hidden-api/src/main/java/android/app/ContextImpl.java @@ -0,0 +1,550 @@ +package android.app; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.IntentSender; +import android.content.ServiceConnection; +import android.content.SharedPreferences; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.res.AssetManager; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.database.DatabaseErrorHandler; +import android.database.sqlite.SQLiteDatabase; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.view.Display; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +public class ContextImpl extends Context { + @Override + public AssetManager getAssets() { + return null; + } + + @Override + public Resources getResources() { + return null; + } + + @Override + public PackageManager getPackageManager() { + return null; + } + + @Override + public ContentResolver getContentResolver() { + return null; + } + + @Override + public Looper getMainLooper() { + return null; + } + + @Override + public Context getApplicationContext() { + return null; + } + + @Override + public void setTheme(int resid) { + + } + + @Override + public Resources.Theme getTheme() { + return null; + } + + @Override + public ClassLoader getClassLoader() { + return null; + } + + @Override + public String getPackageName() { + return null; + } + + @Override + public ApplicationInfo getApplicationInfo() { + return null; + } + + @Override + public String getPackageResourcePath() { + return null; + } + + @Override + public String getPackageCodePath() { + return null; + } + + @Override + public SharedPreferences getSharedPreferences(String name, int mode) { + return null; + } + + @Override + public boolean moveSharedPreferencesFrom(Context sourceContext, String name) { + return false; + } + + @Override + public boolean deleteSharedPreferences(String name) { + return false; + } + + @Override + public FileInputStream openFileInput(String name) throws FileNotFoundException { + return null; + } + + @Override + public FileOutputStream openFileOutput(String name, int mode) throws FileNotFoundException { + return null; + } + + @Override + public boolean deleteFile(String name) { + return false; + } + + @Override + public File getFileStreamPath(String name) { + return null; + } + + @Override + public File getDataDir() { + return null; + } + + @Override + public File getFilesDir() { + return null; + } + + @Override + public File getNoBackupFilesDir() { + return null; + } + + @Override + public File getExternalFilesDir(String type) { + return null; + } + + @Override + public File[] getExternalFilesDirs(String type) { + return new File[0]; + } + + @Override + public File getObbDir() { + return null; + } + + @Override + public File[] getObbDirs() { + return new File[0]; + } + + @Override + public File getCacheDir() { + return null; + } + + @Override + public File getCodeCacheDir() { + return null; + } + + @Override + public File getExternalCacheDir() { + return null; + } + + @Override + public File[] getExternalCacheDirs() { + return new File[0]; + } + + @Override + public File[] getExternalMediaDirs() { + return new File[0]; + } + + @Override + public String[] fileList() { + return new String[0]; + } + + @Override + public File getDir(String name, int mode) { + return null; + } + + @Override + public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory) { + return null; + } + + @Override + public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory, DatabaseErrorHandler errorHandler) { + return null; + } + + @Override + public boolean moveDatabaseFrom(Context sourceContext, String name) { + return false; + } + + @Override + public boolean deleteDatabase(String name) { + return false; + } + + @Override + public File getDatabasePath(String name) { + return null; + } + + @Override + public String[] databaseList() { + return new String[0]; + } + + @Override + public Drawable getWallpaper() { + return null; + } + + @Override + public Drawable peekWallpaper() { + return null; + } + + @Override + public int getWallpaperDesiredMinimumWidth() { + return 0; + } + + @Override + public int getWallpaperDesiredMinimumHeight() { + return 0; + } + + @Override + public void setWallpaper(Bitmap bitmap) throws IOException { + + } + + @Override + public void setWallpaper(InputStream data) throws IOException { + + } + + @Override + public void clearWallpaper() throws IOException { + + } + + @Override + public void startActivity(Intent intent) { + + } + + @Override + public void startActivity(Intent intent, Bundle options) { + + } + + @Override + public void startActivities(Intent[] intents) { + + } + + @Override + public void startActivities(Intent[] intents, Bundle options) { + + } + + @Override + public void startIntentSender(IntentSender intent, Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) throws IntentSender.SendIntentException { + + } + + @Override + public void startIntentSender(IntentSender intent, Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) throws IntentSender.SendIntentException { + + } + + @Override + public void sendBroadcast(Intent intent) { + + } + + @Override + public void sendBroadcast(Intent intent, String receiverPermission) { + + } + + @Override + public void sendOrderedBroadcast(Intent intent, String receiverPermission) { + + } + + @Override + public void sendOrderedBroadcast(Intent intent, String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras) { + + } + + @Override + public void sendBroadcastAsUser(Intent intent, UserHandle user) { + + } + + @Override + public void sendBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission) { + + } + + @Override + public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras) { + + } + + @Override + public void sendStickyBroadcast(Intent intent) { + + } + + @Override + public void sendStickyOrderedBroadcast(Intent intent, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras) { + + } + + @Override + public void removeStickyBroadcast(Intent intent) { + + } + + @Override + public void sendStickyBroadcastAsUser(Intent intent, UserHandle user) { + + } + + @Override + public void sendStickyOrderedBroadcastAsUser(Intent intent, UserHandle user, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras) { + + } + + @Override + public void removeStickyBroadcastAsUser(Intent intent, UserHandle user) { + + } + + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { + return null; + } + + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags) { + return null; + } + + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler) { + return null; + } + + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler, int flags) { + return null; + } + + @Override + public void unregisterReceiver(BroadcastReceiver receiver) { + + } + + @Override + public ComponentName startService(Intent service) { + return null; + } + + @Override + public ComponentName startForegroundService(Intent service) { + return null; + } + + @Override + public boolean stopService(Intent service) { + return false; + } + + @Override + public boolean bindService(Intent service, ServiceConnection conn, int flags) { + return false; + } + + @Override + public void unbindService(ServiceConnection conn) { + + } + + @Override + public boolean startInstrumentation(ComponentName className, String profileFile, Bundle arguments) { + return false; + } + + @Override + public Object getSystemService(String name) { + return null; + } + + @Override + public String getSystemServiceName(Class serviceClass) { + return null; + } + + @Override + public int checkPermission(String permission, int pid, int uid) { + return 0; + } + + @Override + public int checkCallingPermission(String permission) { + return 0; + } + + @Override + public int checkCallingOrSelfPermission(String permission) { + return 0; + } + + @Override + public int checkSelfPermission(String permission) { + return 0; + } + + @Override + public void enforcePermission(String permission, int pid, int uid, String message) { + + } + + @Override + public void enforceCallingPermission(String permission, String message) { + + } + + @Override + public void enforceCallingOrSelfPermission(String permission, String message) { + + } + + @Override + public void grantUriPermission(String toPackage, Uri uri, int modeFlags) { + + } + + @Override + public void revokeUriPermission(Uri uri, int modeFlags) { + + } + + @Override + public void revokeUriPermission(String toPackage, Uri uri, int modeFlags) { + + } + + @Override + public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) { + return 0; + } + + @Override + public int checkCallingUriPermission(Uri uri, int modeFlags) { + return 0; + } + + @Override + public int checkCallingOrSelfUriPermission(Uri uri, int modeFlags) { + return 0; + } + + @Override + public int checkUriPermission(Uri uri, String readPermission, String writePermission, int pid, int uid, int modeFlags) { + return 0; + } + + @Override + public void enforceUriPermission(Uri uri, int pid, int uid, int modeFlags, String message) { + + } + + @Override + public void enforceCallingUriPermission(Uri uri, int modeFlags, String message) { + + } + + @Override + public void enforceCallingOrSelfUriPermission(Uri uri, int modeFlags, String message) { + + } + + @Override + public void enforceUriPermission(Uri uri, String readPermission, String writePermission, int pid, int uid, int modeFlags, String message) { + + } + + @Override + public Context createPackageContext(String packageName, int flags) throws PackageManager.NameNotFoundException { + return null; + } + + @Override + public Context createContextForSplit(String splitName) throws PackageManager.NameNotFoundException { + return null; + } + + @Override + public Context createConfigurationContext(Configuration overrideConfiguration) { + return null; + } + + @Override + public Context createDisplayContext(Display display) { + return null; + } + + @Override + public Context createDeviceProtectedStorageContext() { + return null; + } + + @Override + public boolean isDeviceProtectedStorage() { + return false; + } +}