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;
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_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 (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;
}
}
@ -311,7 +311,7 @@ public class ScopeAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<Scope
}
int itemId = item.getItemId();
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) {
ConfigManager.startActivityAsUserWithFeature(launchIntent, module.userId);
}
@ -397,7 +397,7 @@ public class ScopeAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<Scope
holder.root.setAlpha(!deny && enabled ? 1.0f : .5f);
boolean android = appInfo.packageName.equals("android");
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;
holder.appName.setText(appName);
GlideApp.with(holder.appIcon).load(appInfo.packageInfo).into(new CustomTarget<Drawable>() {
@ -490,7 +490,7 @@ public class ScopeAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<Scope
@Override
public long getItemId(int position) {
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
@ -520,7 +520,7 @@ public class ScopeAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<Scope
List<String> 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) ||

View File

@ -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<PackageInfo, Bitmap> {
@ -43,8 +45,6 @@ public class AppIconModelLoader implements ModelLoader<PackageInfo, Bitmap> {
@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<PackageInfo, Bitmap> {
public LoadData<Bitmap> 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;

View File

@ -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<String> 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 : "";
}

View File

@ -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: {