diff --git a/app/src/main/java/org/lsposed/manager/App.java b/app/src/main/java/org/lsposed/manager/App.java index 251a4064..e0f27180 100644 --- a/app/src/main/java/org/lsposed/manager/App.java +++ b/app/src/main/java/org/lsposed/manager/App.java @@ -70,6 +70,7 @@ import rikka.material.app.DayNightDelegate; import rikka.material.app.LocaleDelegate; public class App extends Application { + public static final int PER_USER_RANGE = 100000; public static final FutureTask HTML_TEMPLATE = new FutureTask<>(() -> readWebviewHTML("template.html")); public static final FutureTask HTML_TEMPLATE_DARK = new FutureTask<>(() -> readWebviewHTML("template_dark.html")); diff --git a/app/src/main/java/org/lsposed/manager/adapters/ScopeAdapter.java b/app/src/main/java/org/lsposed/manager/adapters/ScopeAdapter.java index a8933fe6..b9c65940 100644 --- a/app/src/main/java/org/lsposed/manager/adapters/ScopeAdapter.java +++ b/app/src/main/java/org/lsposed/manager/adapters/ScopeAdapter.java @@ -177,7 +177,7 @@ public class ScopeAdapter extends EmptyStateRecyclerView.EmptyStateAdapter() { @@ -490,7 +490,7 @@ public class ScopeAdapter extends EmptyStateRecyclerView.EmptyStateAdapter scopeList = module.getScopeList(); boolean emptyCheckedList = tmpChkList.isEmpty(); appList.parallelStream().forEach(info -> { - int userId = info.applicationInfo.uid / 100000; + int userId = info.applicationInfo.uid / App.PER_USER_RANGE; String packageName = info.packageName; if (packageName.equals("android") && userId != 0 || packageName.equals(module.packageName) || diff --git a/app/src/main/java/org/lsposed/manager/util/AppIconModelLoader.java b/app/src/main/java/org/lsposed/manager/util/AppIconModelLoader.java index 50eb729b..fb65b0fd 100644 --- a/app/src/main/java/org/lsposed/manager/util/AppIconModelLoader.java +++ b/app/src/main/java/org/lsposed/manager/util/AppIconModelLoader.java @@ -35,6 +35,8 @@ import com.bumptech.glide.load.model.ModelLoaderFactory; import com.bumptech.glide.load.model.MultiModelLoaderFactory; import com.bumptech.glide.signature.ObjectKey; +import org.lsposed.manager.App; + import me.zhanghai.android.appiconloader.AppIconLoader; public class AppIconModelLoader implements ModelLoader { @@ -43,8 +45,6 @@ public class AppIconModelLoader implements ModelLoader { @NonNull private final Context mContext; - private static final int PER_USER_RANGE = 100000; - private AppIconModelLoader(@Px int iconSize, boolean shrinkNonAdaptiveIcons, @NonNull Context context) { mLoader = new AppIconLoader(iconSize, shrinkNonAdaptiveIcons, context); @@ -61,7 +61,7 @@ public class AppIconModelLoader implements ModelLoader { public LoadData buildLoadData(@NonNull PackageInfo model, int width, int height, @NonNull Options options) { var warpApplicationInfo = new ApplicationInfo(model.applicationInfo); - warpApplicationInfo.uid = warpApplicationInfo.uid % PER_USER_RANGE; + warpApplicationInfo.uid = warpApplicationInfo.uid % App.PER_USER_RANGE; var warpPackageInfo = new PackageInfo(); warpPackageInfo.applicationInfo = warpApplicationInfo; warpPackageInfo.versionCode = model.versionCode; diff --git a/app/src/main/java/org/lsposed/manager/util/ModuleUtil.java b/app/src/main/java/org/lsposed/manager/util/ModuleUtil.java index 6ec65b34..fb122807 100644 --- a/app/src/main/java/org/lsposed/manager/util/ModuleUtil.java +++ b/app/src/main/java/org/lsposed/manager/util/ModuleUtil.java @@ -25,6 +25,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Build; +import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -36,14 +37,20 @@ import org.lsposed.manager.ConfigManager; import org.lsposed.manager.repo.RepoLoader; import org.lsposed.manager.repo.model.OnlineModule; +import java.io.EOFException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; public final class ModuleUtil { // xposedminversion below this @@ -88,6 +95,31 @@ public final class ModuleUtil { return result; } + public static ZipFile getModernModuleApk(ApplicationInfo info) { + String[] apks; + if (info.splitSourceDirs != null) { + apks = Arrays.copyOf(info.splitSourceDirs, info.splitSourceDirs.length + 1); + apks[info.splitSourceDirs.length] = info.sourceDir; + } else apks = new String[]{info.sourceDir}; + ZipFile zip = null; + for (var apk : apks) { + try { + zip = new ZipFile(apk); + if (zip.getEntry("META-INF/xposed/java_init.list") != null) { + return zip; + } + zip.close(); + zip = null; + } catch (IOException ignored) { + } + } + return zip; + } + + public static boolean isLegacyModule(ApplicationInfo info) { + return info.metaData != null && info.metaData.containsKey("xposedminversion"); + } + synchronized public void reloadInstalledModules() { modulesLoaded = false; if (!ConfigManager.isBinderAlive()) { @@ -100,8 +132,9 @@ public final class ModuleUtil { for (PackageInfo pkg : ConfigManager.getInstalledPackagesFromAllUsers(PackageManager.GET_META_DATA | MATCH_ALL_FLAGS, false)) { ApplicationInfo app = pkg.applicationInfo; - if (app.metaData != null && app.metaData.containsKey("xposedminversion")) { - modules.computeIfAbsent(Pair.create(pkg.packageName, app.uid / 100000), k -> new InstalledModule(pkg)); + var modernApk = getModernModuleApk(app); + if (modernApk != null || isLegacyModule(app)) { + modules.computeIfAbsent(Pair.create(pkg.packageName, app.uid / App.PER_USER_RANGE), k -> new InstalledModule(pkg, modernApk)); } } @@ -139,8 +172,9 @@ public final class ModuleUtil { } ApplicationInfo app = pkg.applicationInfo; - if (app.metaData != null && app.metaData.containsKey("xposedminversion")) { - InstalledModule module = new InstalledModule(pkg); + var modernApk = getModernModuleApk(app); + if (modernApk != null || isLegacyModule(app)) { + InstalledModule module = new InstalledModule(pkg, modernApk); installedModules.put(Pair.create(packageName, userId), module); listeners.forEach(i -> i.onSingleModuleReloaded(module)); return module; @@ -182,6 +216,21 @@ public final class ModuleUtil { return enabledModules.contains(packageName); } + private static String readZipEntryToString(ZipFile file, ZipEntry entry) throws IOException { + try (var is = file.getInputStream(entry)) { + var bytes = new byte[(int) entry.getSize()]; + int offset = 0; + while (offset < bytes.length) { + int read = is.read(bytes, offset, bytes.length - offset); + if (read < 0) { + throw new EOFException(); + } + offset += read; + } + return new String(bytes, StandardCharsets.UTF_8); + } + } + public int getEnabledModulesCount() { return modulesLoaded ? enabledModules.size() : -1; } @@ -214,6 +263,7 @@ public final class ModuleUtil { public final String packageName; public final String versionName; public final long versionCode; + public final boolean legacy; public final int minVersion; public final int targetVersion; public final long installTime; @@ -224,35 +274,54 @@ public final class ModuleUtil { private String description; // loaded lazyily private List scopeList; // loaded lazyily - private InstalledModule(PackageInfo pkg) { - this.app = pkg.applicationInfo; + private InstalledModule(PackageInfo pkg, ZipFile modernModuleApk) { + app = pkg.applicationInfo; this.pkg = pkg; - this.userId = pkg.applicationInfo.uid / 100000; - this.packageName = pkg.packageName; - this.versionName = pkg.versionName; + userId = pkg.applicationInfo.uid / App.PER_USER_RANGE; + packageName = pkg.packageName; + versionName = pkg.versionName; if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { - this.versionCode = pkg.versionCode; + versionCode = pkg.versionCode; } else { - this.versionCode = pkg.getLongVersionCode(); + versionCode = pkg.getLongVersionCode(); } - this.installTime = pkg.firstInstallTime; - this.updateTime = pkg.lastUpdateTime; + installTime = pkg.firstInstallTime; + updateTime = pkg.lastUpdateTime; + legacy = modernModuleApk == null; - Object minVersionRaw = app.metaData.get("xposedminversion"); - if (minVersionRaw instanceof Integer) { - this.minVersion = (Integer) minVersionRaw; - } else if (minVersionRaw instanceof String) { - this.minVersion = extractIntPart((String) minVersionRaw); + if (legacy) { + Object minVersionRaw = app.metaData.get("xposedminversion"); + if (minVersionRaw instanceof Integer) { + minVersion = (Integer) minVersionRaw; + } else if (minVersionRaw instanceof String) { + minVersion = extractIntPart((String) minVersionRaw); + } else { + minVersion = 0; + } + targetVersion = minVersion; // legacy modules don't have a target version } else { - this.minVersion = 0; - } - Object targetVersionRaw = app.metaData.get("xposedtargetversion"); - if (targetVersionRaw instanceof Integer) { - this.targetVersion = (Integer) targetVersionRaw; - } else if (targetVersionRaw instanceof String) { - this.targetVersion = extractIntPart((String) targetVersionRaw); - } else { - this.targetVersion = this.minVersion; + int minVersion = 100; + int targetVersion = 100; + try (modernModuleApk) { + var minVersionEntry = modernModuleApk.getEntry("META-INF/xposed/minversion"); + if (minVersionEntry != null) { + minVersion = extractIntPart(readZipEntryToString(modernModuleApk, minVersionEntry)); + } + var targetVersionEntry = modernModuleApk.getEntry("META-INF/xposed/targetversion"); + if (targetVersionEntry != null) { + targetVersion = extractIntPart(readZipEntryToString(modernModuleApk, targetVersionEntry)); + } + var scopeEntry = modernModuleApk.getEntry("META-INF/xposed/scope"); + if (scopeEntry != null) { + scopeList = Arrays.asList(readZipEntryToString(modernModuleApk, scopeEntry).split("\\n|\\r\\n")); + } else { + scopeList = Collections.emptyList(); + } + } catch (IOException e) { + Log.e(App.TAG, "Error while closing modern module APK", e); + } + this.minVersion = minVersion; + this.targetVersion = targetVersion; } } @@ -268,17 +337,21 @@ public final class ModuleUtil { public String getDescription() { if (this.description == null) { - Object descriptionRaw = app.metaData.get("xposeddescription"); String descriptionTmp = null; - if (descriptionRaw instanceof String) { - descriptionTmp = ((String) descriptionRaw).trim(); - } else if (descriptionRaw instanceof Integer) { - try { - int resId = (Integer) descriptionRaw; - if (resId != 0) - descriptionTmp = pm.getResourcesForApplication(app).getString(resId).trim(); - } catch (Exception ignored) { + if (legacy) { + Object descriptionRaw = app.metaData.get("xposeddescription"); + if (descriptionRaw instanceof String) { + descriptionTmp = ((String) descriptionRaw).trim(); + } else if (descriptionRaw instanceof Integer) { + try { + int resId = (Integer) descriptionRaw; + if (resId != 0) + descriptionTmp = pm.getResourcesForApplication(app).getString(resId).trim(); + } catch (Exception ignored) { + } } + } else { + descriptionTmp = app.loadDescription(pm).toString(); } this.description = (descriptionTmp != null) ? descriptionTmp : ""; } diff --git a/daemon/src/main/java/org/lsposed/lspd/service/LSPosedService.java b/daemon/src/main/java/org/lsposed/lspd/service/LSPosedService.java index aa61aa82..3fb40e28 100644 --- a/daemon/src/main/java/org/lsposed/lspd/service/LSPosedService.java +++ b/daemon/src/main/java/org/lsposed/lspd/service/LSPosedService.java @@ -45,9 +45,11 @@ import android.util.Log; import org.lsposed.daemon.BuildConfig; +import java.io.IOException; import java.util.Arrays; import java.util.List; import java.util.function.Consumer; +import java.util.zip.ZipFile; import hidden.HiddenApiBridge; import io.github.libxposed.service.IXposedScopeCallback; @@ -60,6 +62,23 @@ public class LSPosedService extends ILSPosedService.Stub { private static final String EXTRA_USER_HANDLE = "android.intent.extra.user_handle"; private static final String EXTRA_REMOVED_FOR_ALL_USERS = "android.intent.extra.REMOVED_FOR_ALL_USERS"; + private static boolean isModernModules(ApplicationInfo info) { + String[] apks; + if (info.splitSourceDirs != null) { + apks = Arrays.copyOf(info.splitSourceDirs, info.splitSourceDirs.length + 1); + apks[info.splitSourceDirs.length] = info.sourceDir; + } else apks = new String[]{info.sourceDir}; + for (var apk : apks) { + try (var zip = new ZipFile(apk)) { + if (zip.getEntry("META-INF/xposed/java_init.list") != null) { + return true; + } + } catch (IOException ignored) { + } + } + return false; + } + @Override public ILSPApplicationService requestApplicationService(int uid, int pid, String processName, IBinder heartBeat) { if (Binder.getCallingUid() != 1000) { @@ -108,7 +127,7 @@ public class LSPosedService extends ILSPosedService.Stub { } } - boolean isXposedModule = applicationInfo != null && applicationInfo.metaData != null && applicationInfo.metaData.containsKey("xposedminversion"); + boolean isXposedModule = applicationInfo != null && ((applicationInfo.metaData != null && applicationInfo.metaData.containsKey("xposedminversion")) || isModernModules(applicationInfo)); switch (intentAction) { case Intent.ACTION_PACKAGE_FULLY_REMOVED: {