No metadata required for modern modules

This commit is contained in:
LoveSy 2023-02-26 16:24:11 +08:00 committed by LoveSy
parent b069da0323
commit d7c7069114
5 changed files with 138 additions and 45 deletions

View File

@ -70,6 +70,7 @@ import rikka.material.app.DayNightDelegate;
import rikka.material.app.LocaleDelegate; import rikka.material.app.LocaleDelegate;
public class App extends Application { public class App extends Application {
public static final int PER_USER_RANGE = 100000;
public static final FutureTask<String> HTML_TEMPLATE = new FutureTask<>(() -> readWebviewHTML("template.html")); public static final FutureTask<String> HTML_TEMPLATE = new FutureTask<>(() -> readWebviewHTML("template.html"));
public static final FutureTask<String> HTML_TEMPLATE_DARK = new FutureTask<>(() -> readWebviewHTML("template_dark.html")); public static final FutureTask<String> HTML_TEMPLATE_DARK = new FutureTask<>(() -> readWebviewHTML("template_dark.html"));

View File

@ -177,7 +177,7 @@ public class ScopeAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<Scope
} }
} }
if (preferences.getBoolean("filter_modules", true)) { if (preferences.getBoolean("filter_modules", true)) {
if (info.applicationInfo.metaData != null && info.applicationInfo.metaData.containsKey("xposedminversion")) { if (ModuleUtil.getInstance().getModule(info.packageName, info.applicationInfo.uid / App.PER_USER_RANGE) != null) {
return true; return true;
} }
} }
@ -311,7 +311,7 @@ public class ScopeAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<Scope
} }
int itemId = item.getItemId(); int itemId = item.getItemId();
if (itemId == R.id.menu_launch) { if (itemId == R.id.menu_launch) {
Intent launchIntent = AppHelper.getLaunchIntentForPackage(info.packageName, info.uid / 100000); Intent launchIntent = AppHelper.getLaunchIntentForPackage(info.packageName, info.uid / App.PER_USER_RANGE);
if (launchIntent != null) { if (launchIntent != null) {
ConfigManager.startActivityAsUserWithFeature(launchIntent, module.userId); ConfigManager.startActivityAsUserWithFeature(launchIntent, module.userId);
} }
@ -397,7 +397,7 @@ public class ScopeAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<Scope
holder.root.setAlpha(!deny && enabled ? 1.0f : .5f); holder.root.setAlpha(!deny && enabled ? 1.0f : .5f);
boolean android = appInfo.packageName.equals("android"); boolean android = appInfo.packageName.equals("android");
CharSequence appName; CharSequence appName;
int userId = appInfo.applicationInfo.uid / 100000; int userId = appInfo.applicationInfo.uid / App.PER_USER_RANGE;
appName = android ? activity.getString(R.string.android_framework) : appInfo.label; appName = android ? activity.getString(R.string.android_framework) : appInfo.label;
holder.appName.setText(appName); holder.appName.setText(appName);
GlideApp.with(holder.appIcon).load(appInfo.packageInfo).into(new CustomTarget<Drawable>() { GlideApp.with(holder.appIcon).load(appInfo.packageInfo).into(new CustomTarget<Drawable>() {
@ -490,7 +490,7 @@ public class ScopeAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<Scope
@Override @Override
public long getItemId(int position) { public long getItemId(int position) {
PackageInfo info = showList.get(position).packageInfo; PackageInfo info = showList.get(position).packageInfo;
return (info.packageName + "!" + info.applicationInfo.uid / 100000).hashCode(); return (info.packageName + "!" + info.applicationInfo.uid / App.PER_USER_RANGE).hashCode();
} }
@Override @Override
@ -520,7 +520,7 @@ public class ScopeAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<Scope
List<String> scopeList = module.getScopeList(); List<String> scopeList = module.getScopeList();
boolean emptyCheckedList = tmpChkList.isEmpty(); boolean emptyCheckedList = tmpChkList.isEmpty();
appList.parallelStream().forEach(info -> { appList.parallelStream().forEach(info -> {
int userId = info.applicationInfo.uid / 100000; int userId = info.applicationInfo.uid / App.PER_USER_RANGE;
String packageName = info.packageName; String packageName = info.packageName;
if (packageName.equals("android") && userId != 0 || if (packageName.equals("android") && userId != 0 ||
packageName.equals(module.packageName) || packageName.equals(module.packageName) ||

View File

@ -35,6 +35,8 @@ import com.bumptech.glide.load.model.ModelLoaderFactory;
import com.bumptech.glide.load.model.MultiModelLoaderFactory; import com.bumptech.glide.load.model.MultiModelLoaderFactory;
import com.bumptech.glide.signature.ObjectKey; import com.bumptech.glide.signature.ObjectKey;
import org.lsposed.manager.App;
import me.zhanghai.android.appiconloader.AppIconLoader; import me.zhanghai.android.appiconloader.AppIconLoader;
public class AppIconModelLoader implements ModelLoader<PackageInfo, Bitmap> { public class AppIconModelLoader implements ModelLoader<PackageInfo, Bitmap> {
@ -43,8 +45,6 @@ public class AppIconModelLoader implements ModelLoader<PackageInfo, Bitmap> {
@NonNull @NonNull
private final Context mContext; private final Context mContext;
private static final int PER_USER_RANGE = 100000;
private AppIconModelLoader(@Px int iconSize, boolean shrinkNonAdaptiveIcons, private AppIconModelLoader(@Px int iconSize, boolean shrinkNonAdaptiveIcons,
@NonNull Context context) { @NonNull Context context) {
mLoader = new AppIconLoader(iconSize, shrinkNonAdaptiveIcons, context); mLoader = new AppIconLoader(iconSize, shrinkNonAdaptiveIcons, context);
@ -61,7 +61,7 @@ public class AppIconModelLoader implements ModelLoader<PackageInfo, Bitmap> {
public LoadData<Bitmap> buildLoadData(@NonNull PackageInfo model, int width, int height, public LoadData<Bitmap> buildLoadData(@NonNull PackageInfo model, int width, int height,
@NonNull Options options) { @NonNull Options options) {
var warpApplicationInfo = new ApplicationInfo(model.applicationInfo); 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(); var warpPackageInfo = new PackageInfo();
warpPackageInfo.applicationInfo = warpApplicationInfo; warpPackageInfo.applicationInfo = warpApplicationInfo;
warpPackageInfo.versionCode = model.versionCode; warpPackageInfo.versionCode = model.versionCode;

View File

@ -25,6 +25,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build; import android.os.Build;
import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -36,14 +37,20 @@ import org.lsposed.manager.ConfigManager;
import org.lsposed.manager.repo.RepoLoader; import org.lsposed.manager.repo.RepoLoader;
import org.lsposed.manager.repo.model.OnlineModule; 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.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
public final class ModuleUtil { public final class ModuleUtil {
// xposedminversion below this // xposedminversion below this
@ -88,6 +95,31 @@ public final class ModuleUtil {
return result; 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() { synchronized public void reloadInstalledModules() {
modulesLoaded = false; modulesLoaded = false;
if (!ConfigManager.isBinderAlive()) { if (!ConfigManager.isBinderAlive()) {
@ -100,8 +132,9 @@ public final class ModuleUtil {
for (PackageInfo pkg : ConfigManager.getInstalledPackagesFromAllUsers(PackageManager.GET_META_DATA | MATCH_ALL_FLAGS, false)) { for (PackageInfo pkg : ConfigManager.getInstalledPackagesFromAllUsers(PackageManager.GET_META_DATA | MATCH_ALL_FLAGS, false)) {
ApplicationInfo app = pkg.applicationInfo; ApplicationInfo app = pkg.applicationInfo;
if (app.metaData != null && app.metaData.containsKey("xposedminversion")) { var modernApk = getModernModuleApk(app);
modules.computeIfAbsent(Pair.create(pkg.packageName, app.uid / 100000), k -> new InstalledModule(pkg)); 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; ApplicationInfo app = pkg.applicationInfo;
if (app.metaData != null && app.metaData.containsKey("xposedminversion")) { var modernApk = getModernModuleApk(app);
InstalledModule module = new InstalledModule(pkg); if (modernApk != null || isLegacyModule(app)) {
InstalledModule module = new InstalledModule(pkg, modernApk);
installedModules.put(Pair.create(packageName, userId), module); installedModules.put(Pair.create(packageName, userId), module);
listeners.forEach(i -> i.onSingleModuleReloaded(module)); listeners.forEach(i -> i.onSingleModuleReloaded(module));
return module; return module;
@ -182,6 +216,21 @@ public final class ModuleUtil {
return enabledModules.contains(packageName); 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() { public int getEnabledModulesCount() {
return modulesLoaded ? enabledModules.size() : -1; return modulesLoaded ? enabledModules.size() : -1;
} }
@ -214,6 +263,7 @@ public final class ModuleUtil {
public final String packageName; public final String packageName;
public final String versionName; public final String versionName;
public final long versionCode; public final long versionCode;
public final boolean legacy;
public final int minVersion; public final int minVersion;
public final int targetVersion; public final int targetVersion;
public final long installTime; public final long installTime;
@ -224,35 +274,54 @@ public final class ModuleUtil {
private String description; // loaded lazyily private String description; // loaded lazyily
private List<String> scopeList; // loaded lazyily private List<String> scopeList; // loaded lazyily
private InstalledModule(PackageInfo pkg) { private InstalledModule(PackageInfo pkg, ZipFile modernModuleApk) {
this.app = pkg.applicationInfo; app = pkg.applicationInfo;
this.pkg = pkg; this.pkg = pkg;
this.userId = pkg.applicationInfo.uid / 100000; userId = pkg.applicationInfo.uid / App.PER_USER_RANGE;
this.packageName = pkg.packageName; packageName = pkg.packageName;
this.versionName = pkg.versionName; versionName = pkg.versionName;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
this.versionCode = pkg.versionCode; versionCode = pkg.versionCode;
} else { } else {
this.versionCode = pkg.getLongVersionCode(); versionCode = pkg.getLongVersionCode();
} }
this.installTime = pkg.firstInstallTime; installTime = pkg.firstInstallTime;
this.updateTime = pkg.lastUpdateTime; updateTime = pkg.lastUpdateTime;
legacy = modernModuleApk == null;
Object minVersionRaw = app.metaData.get("xposedminversion"); if (legacy) {
if (minVersionRaw instanceof Integer) { Object minVersionRaw = app.metaData.get("xposedminversion");
this.minVersion = (Integer) minVersionRaw; if (minVersionRaw instanceof Integer) {
} else if (minVersionRaw instanceof String) { minVersion = (Integer) minVersionRaw;
this.minVersion = extractIntPart((String) minVersionRaw); } else if (minVersionRaw instanceof String) {
minVersion = extractIntPart((String) minVersionRaw);
} else {
minVersion = 0;
}
targetVersion = minVersion; // legacy modules don't have a target version
} else { } else {
this.minVersion = 0; int minVersion = 100;
} int targetVersion = 100;
Object targetVersionRaw = app.metaData.get("xposedtargetversion"); try (modernModuleApk) {
if (targetVersionRaw instanceof Integer) { var minVersionEntry = modernModuleApk.getEntry("META-INF/xposed/minversion");
this.targetVersion = (Integer) targetVersionRaw; if (minVersionEntry != null) {
} else if (targetVersionRaw instanceof String) { minVersion = extractIntPart(readZipEntryToString(modernModuleApk, minVersionEntry));
this.targetVersion = extractIntPart((String) targetVersionRaw); }
} else { var targetVersionEntry = modernModuleApk.getEntry("META-INF/xposed/targetversion");
this.targetVersion = this.minVersion; 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() { public String getDescription() {
if (this.description == null) { if (this.description == null) {
Object descriptionRaw = app.metaData.get("xposeddescription");
String descriptionTmp = null; String descriptionTmp = null;
if (descriptionRaw instanceof String) { if (legacy) {
descriptionTmp = ((String) descriptionRaw).trim(); Object descriptionRaw = app.metaData.get("xposeddescription");
} else if (descriptionRaw instanceof Integer) { if (descriptionRaw instanceof String) {
try { descriptionTmp = ((String) descriptionRaw).trim();
int resId = (Integer) descriptionRaw; } else if (descriptionRaw instanceof Integer) {
if (resId != 0) try {
descriptionTmp = pm.getResourcesForApplication(app).getString(resId).trim(); int resId = (Integer) descriptionRaw;
} catch (Exception ignored) { if (resId != 0)
descriptionTmp = pm.getResourcesForApplication(app).getString(resId).trim();
} catch (Exception ignored) {
}
} }
} else {
descriptionTmp = app.loadDescription(pm).toString();
} }
this.description = (descriptionTmp != null) ? descriptionTmp : ""; this.description = (descriptionTmp != null) ? descriptionTmp : "";
} }

View File

@ -45,9 +45,11 @@ import android.util.Log;
import org.lsposed.daemon.BuildConfig; import org.lsposed.daemon.BuildConfig;
import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.zip.ZipFile;
import hidden.HiddenApiBridge; import hidden.HiddenApiBridge;
import io.github.libxposed.service.IXposedScopeCallback; 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_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 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 @Override
public ILSPApplicationService requestApplicationService(int uid, int pid, String processName, IBinder heartBeat) { public ILSPApplicationService requestApplicationService(int uid, int pid, String processName, IBinder heartBeat) {
if (Binder.getCallingUid() != 1000) { 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) { switch (intentAction) {
case Intent.ACTION_PACKAGE_FULLY_REMOVED: { case Intent.ACTION_PACKAGE_FULLY_REMOVED: {