You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
MagiskModuleManager/app/src/main/java/com/fox2code/mmm/module/ModuleHolder.java

375 lines
16 KiB
Java

/*
* Copyright (c) 2023 to present Androidacy and contributors. Names, logos, icons, and the Androidacy name are all trademarks of Androidacy and may not be used without license. See LICENSE for more information.
*/
package com.fox2code.mmm.module;
4 years ago
import android.content.Context;
import android.content.pm.PackageManager;
import android.view.View;
4 years ago
import androidx.annotation.NonNull;
4 years ago
import androidx.annotation.StringRes;
import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.NotificationType;
import com.fox2code.mmm.R;
import com.fox2code.mmm.XHooks;
4 years ago
import com.fox2code.mmm.manager.LocalModuleInfo;
4 years ago
import com.fox2code.mmm.manager.ModuleInfo;
import com.fox2code.mmm.repo.RepoModule;
import com.fox2code.mmm.utils.IntentHelper;
import com.fox2code.mmm.utils.io.PropUtils;
import com.fox2code.mmm.utils.io.net.Http;
4 years ago
import java.util.Comparator;
import java.util.HashSet;
4 years ago
import java.util.List;
import java.util.Locale;
4 years ago
import java.util.Objects;
import java.util.Set;
4 years ago
import timber.log.Timber;
4 years ago
public final class ModuleHolder implements Comparable<ModuleHolder> {
public final String moduleId;
public final NotificationType notificationType;
public final Type separator;
public int footerPx;
public View.OnClickListener onClickListener;
4 years ago
public LocalModuleInfo moduleInfo;
4 years ago
public RepoModule repoModule;
public int filterLevel;
4 years ago
public ModuleHolder(String moduleId) {
this.moduleId = Objects.requireNonNull(moduleId);
this.notificationType = null;
this.separator = null;
this.footerPx = -1;
4 years ago
}
public ModuleHolder(NotificationType notificationType) {
this.moduleId = "";
this.notificationType = Objects.requireNonNull(notificationType);
this.separator = null;
this.footerPx = -1;
4 years ago
}
public ModuleHolder(Type separator) {
this.moduleId = "";
this.notificationType = null;
this.separator = separator;
this.footerPx = -1;
4 years ago
}
@SuppressWarnings("unused")
public ModuleHolder(int footerPx, boolean header) {
4 years ago
this.moduleId = "";
this.notificationType = null;
this.separator = null;
this.footerPx = footerPx;
this.filterLevel = header ? 1 : 0;
4 years ago
}
public boolean isModuleHolder() {
return this.notificationType == null && this.separator == null && this.footerPx == -1;
4 years ago
}
public ModuleInfo getMainModuleInfo() {
return this.repoModule != null && (this.moduleInfo == null || this.moduleInfo.versionCode < this.repoModule.moduleInfo.versionCode) ? this.repoModule.moduleInfo : this.moduleInfo;
4 years ago
}
public String getUpdateZipUrl() {
return this.moduleInfo == null || (this.repoModule != null && this.moduleInfo.updateVersionCode < this.repoModule.moduleInfo.versionCode) ? this.repoModule.zipUrl : this.moduleInfo.updateZipUrl;
4 years ago
}
public String getUpdateZipRepo() {
return this.moduleInfo == null || (this.repoModule != null && this.moduleInfo.updateVersionCode < this.repoModule.moduleInfo.versionCode) ? this.repoModule.repoData.id : "update_json";
}
public String getUpdateZipChecksum() {
return this.moduleInfo == null || (this.repoModule != null && this.moduleInfo.updateVersionCode < this.repoModule.moduleInfo.versionCode) ? this.repoModule.checksum : this.moduleInfo.updateChecksum;
}
4 years ago
public String getMainModuleName() {
ModuleInfo moduleInfo = this.getMainModuleInfo();
if (moduleInfo == null || moduleInfo.name == null)
throw new Error("Error for " + this.getType().name() + " id " + this.moduleId);
return moduleInfo.name;
}
public String getMainModuleNameLowercase() {
return this.getMainModuleName().toLowerCase(Locale.ROOT);
}
4 years ago
public String getMainModuleConfig() {
if (this.moduleInfo == null) return null;
String config = this.moduleInfo.config;
if (config == null && this.repoModule != null) {
config = this.repoModule.moduleInfo.config;
}
return config;
}
public String getUpdateTimeText() {
if (this.repoModule == null) return "";
long timeStamp = this.repoModule.lastUpdated;
return timeStamp <= 0 ? "" : MainApplication.formatTime(timeStamp);
4 years ago
}
public String getRepoName() {
if (this.repoModule == null) return "";
return this.repoModule.repoName;
}
4 years ago
public boolean hasFlag(int flag) {
return this.moduleInfo != null && this.moduleInfo.hasFlag(flag);
}
public Type getType() {
if (this.footerPx != -1) {
Timber.i("Module %s is footer", this.moduleId);
4 years ago
return Type.FOOTER;
} else if (this.separator != null) {
Timber.i("Module %s is separator", this.moduleId);
4 years ago
return Type.SEPARATOR;
} else if (this.notificationType != null) {
Timber.i("Module %s is notification", this.moduleId);
4 years ago
return Type.NOTIFICATION;
} else if (this.moduleInfo == null) {
return Type.INSTALLABLE;
} else if (this.moduleInfo.versionCode < this.moduleInfo.updateVersionCode || (this.repoModule != null && this.moduleInfo.versionCode < this.repoModule.moduleInfo.versionCode)) {
boolean ignoreUpdate = false;
try {
if (Objects.requireNonNull(MainApplication.getSharedPreferences("mmm").getStringSet("pref_background_update_check_excludes", new HashSet<>())).contains(moduleInfo.id))
ignoreUpdate = true;
} catch (Exception ignored) {
}
// now, we just had to make it more fucking complicated, didn't we?
// we now have pref_background_update_check_excludes_version, which is a id:version stringset of versions the user may want to "skip"
// oh, and because i hate myself, i made ^ at the beginning match that version and newer, and $ at the end match that version and older
Set<String> stringSetT = MainApplication.getSharedPreferences("mmm").getStringSet("pref_background_update_check_excludes_version", new HashSet<>());
String version = "";
Timber.d(stringSetT.toString());
// unfortunately, stringsett.contains() doesn't work for partial matches
// so we have to iterate through the set
for (String s : stringSetT) {
if (s.startsWith(this.moduleInfo.id)) {
version = s;
Timber.d("igV: %s", version);
break;
}
}
String remoteVersionCode = String.valueOf(moduleInfo.updateVersionCode);
if (repoModule != null) {
remoteVersionCode = String.valueOf(repoModule.moduleInfo.versionCode);
}
if (!version.isEmpty()) {
// now, coerce everything into an int
int remoteVersionCodeInt = Integer.parseInt(remoteVersionCode);
int wantsVersion = Integer.parseInt(version.split(":")[1].replaceAll("[^0-9]", ""));
// now find out if user wants up to and including this version, or this version and newer
Timber.d("igV start with");
version = version.split(":")[1];
// this version and newer
if (version.startsWith("^")) {
Timber.d("igV: newer");
// the wantsversion and newer
if (remoteVersionCodeInt >= wantsVersion) {
Timber.d("igV: skipping");
// if it is, we skip it
ignoreUpdate = true;
}
} else if (version.endsWith("$")) {
Timber.d("igV: older");
// this wantsversion and older
if (remoteVersionCodeInt <= wantsVersion) {
Timber.d("igV: skipping");
// if it is, we skip it
ignoreUpdate = true;
}
} else if (wantsVersion == remoteVersionCodeInt) {
Timber.d("igV: equal");
// if it is, we skip it
ignoreUpdate = true;
}
}
if (ignoreUpdate) {
Timber.d("Module %s has update, but is ignored", this.moduleId);
return Type.INSTALLABLE;
} else {
MainApplication.getINSTANCE().modulesHaveUpdates = true;
if (!MainApplication.getINSTANCE().updateModules.contains(this.moduleId)) {
MainApplication.getINSTANCE().updateModules.add(this.moduleId);
MainApplication.getINSTANCE().updateModuleCount++;
}
Timber.d("modulesHaveUpdates = %s, updateModuleCount = %s", MainApplication.getINSTANCE().modulesHaveUpdates, MainApplication.getINSTANCE().updateModuleCount);
Timber.d("Module %s has update", this.moduleId);
return Type.UPDATABLE;
}
4 years ago
} else {
return Type.INSTALLED;
}
}
public Type getCompareType(Type type) {
if (this.separator != null) {
return this.separator;
} else if (this.notificationType != null && this.notificationType.special) {
4 years ago
return Type.SPECIAL_NOTIFICATIONS;
} else {
return type;
}
}
public boolean shouldRemove() {
if (this.repoModule != null && this.moduleInfo != null && !hasUpdate()) {
return true;
}
return this.notificationType != null ? this.notificationType.shouldRemove() : this.footerPx == -1 && this.moduleInfo == null && (this.repoModule == null || !this.repoModule.repoData.isEnabled() || (PropUtils.isLowQualityModule(this.repoModule.moduleInfo) && !MainApplication.Companion.isDisableLowQualityModuleFilter()));
4 years ago
}
public void getButtons(Context context, List<ActionButtonType> buttonTypeList, boolean showcaseMode) {
if (!this.isModuleHolder()) return;
LocalModuleInfo localModuleInfo = this.moduleInfo;
// Add warning button if module id begins with a dot - this is a hidden module which could indicate malware
if (this.moduleId.startsWith(".") || !this.moduleId.matches("^[a-zA-Z][a-zA-Z0-9._-]+$")) {
buttonTypeList.add(ActionButtonType.WARNING);
}
if (localModuleInfo != null && !showcaseMode) {
4 years ago
buttonTypeList.add(ActionButtonType.UNINSTALL);
}
if (this.repoModule != null && this.repoModule.notesUrl != null) {
4 years ago
buttonTypeList.add(ActionButtonType.INFO);
}
if ((this.repoModule != null || (localModuleInfo != null && localModuleInfo.updateZipUrl != null))) {
4 years ago
buttonTypeList.add(ActionButtonType.UPDATE_INSTALL);
}
String config = this.getMainModuleConfig();
if (config != null) {
if (config.startsWith("https://www.androidacy.com/") && Http.hasWebView()) {
4 years ago
buttonTypeList.add(ActionButtonType.CONFIG);
} else {
String pkg = IntentHelper.getPackageOfConfig(config);
try {
XHooks.checkConfigTargetExists(context, pkg, config);
buttonTypeList.add(ActionButtonType.CONFIG);
} catch (PackageManager.NameNotFoundException e) {
Timber.w("Config package \"" + pkg + "\" missing for module \"" + this.moduleId + "\"");
}
4 years ago
}
}
ModuleInfo moduleInfo = this.getMainModuleInfo();
if (moduleInfo == null) { // Avoid concurrency NPE
if (localModuleInfo == null) return;
moduleInfo = localModuleInfo;
}
4 years ago
if (moduleInfo.support != null) {
buttonTypeList.add(ActionButtonType.SUPPORT);
}
if (moduleInfo.donate != null) {
buttonTypeList.add(ActionButtonType.DONATE);
}
if (moduleInfo.safe) {
buttonTypeList.add(ActionButtonType.SAFE);
} else {
Timber.d("Module %s is not safe", this.moduleId);
}
4 years ago
}
public boolean hasUpdate() {
return this.moduleInfo != null && this.repoModule != null && this.moduleInfo.versionCode < this.repoModule.moduleInfo.versionCode;
4 years ago
}
@Override
public int compareTo(ModuleHolder o) {
// Compare depend on type, also allow type spoofing
Type selfTypeReal = this.getType();
Type otherTypeReal = o.getType();
Type selfType = this.getCompareType(selfTypeReal);
Type otherType = o.getCompareType(otherTypeReal);
int compare = selfType.compareTo(otherType);
return compare != 0 ? compare : selfTypeReal == otherTypeReal ? selfTypeReal.compare(this, o) : selfTypeReal.compareTo(otherTypeReal);
}
@NonNull
@Override
public String toString() {
return "ModuleHolder{" + "moduleId='" + moduleId + '\'' + ", notificationType=" + notificationType + ", separator=" + separator + ", footerPx=" + footerPx + '}';
4 years ago
}
public enum Type implements Comparator<ModuleHolder> {
HEADER(R.string.loading, false, false), SEPARATOR(R.string.loading, false, false) {
4 years ago
@Override
@SuppressWarnings("ConstantConditions")
public int compare(ModuleHolder o1, ModuleHolder o2) {
return o1.separator.compareTo(o2.separator);
}
}, NOTIFICATION(R.string.loading, true, false) {
4 years ago
@Override
@SuppressWarnings("ConstantConditions")
public int compare(ModuleHolder o1, ModuleHolder o2) {
return o1.notificationType.compareTo(o2.notificationType);
}
}, UPDATABLE(R.string.updatable, true, true) {
4 years ago
@Override
public int compare(ModuleHolder o1, ModuleHolder o2) {
int cmp = Integer.compare(o1.filterLevel, o2.filterLevel);
if (cmp != 0) return cmp;
4 years ago
long lastUpdated1 = o1.repoModule == null ? 0L : o1.repoModule.lastUpdated;
long lastUpdated2 = o2.repoModule == null ? 0L : o2.repoModule.lastUpdated;
cmp = Long.compare(lastUpdated2, lastUpdated1);
if (cmp != 0) return cmp;
return o1.getMainModuleName().compareTo(o2.getMainModuleName());
4 years ago
}
}, INSTALLED(R.string.installed, true, true) {
// get stacktrace for debugging
4 years ago
@Override
public int compare(ModuleHolder o1, ModuleHolder o2) {
int cmp = Integer.compare(o1.filterLevel, o2.filterLevel);
if (cmp != 0) return cmp;
return o1.getMainModuleNameLowercase().compareTo(o2.getMainModuleNameLowercase());
4 years ago
}
}, SPECIAL_NOTIFICATIONS(R.string.loading, true, false), INSTALLABLE(R.string.online_repo, true, true) {
4 years ago
@Override
public int compare(ModuleHolder o1, ModuleHolder o2) {
int cmp = Integer.compare(o1.filterLevel, o2.filterLevel);
if (cmp != 0) return cmp;
4 years ago
long lastUpdated1 = o1.repoModule == null ? 0L : o1.repoModule.lastUpdated;
long lastUpdated2 = o2.repoModule == null ? 0L : o2.repoModule.lastUpdated;
cmp = Long.compare(lastUpdated2, lastUpdated1);
if (cmp != 0) return cmp;
return o1.getMainModuleName().compareTo(o2.getMainModuleName());
4 years ago
}
}, FOOTER(R.string.loading, false, false);
4 years ago
@StringRes
public final int title;
public final boolean hasBackground;
public final boolean moduleHolder;
4 years ago
Type(@StringRes int title, boolean hasBackground, boolean moduleHolder) {
4 years ago
this.title = title;
this.hasBackground = hasBackground;
this.moduleHolder = moduleHolder;
4 years ago
}
// Note: This method should only be called if both element have the same type
@Override
public int compare(ModuleHolder o1, ModuleHolder o2) {
if (o1 == o2) {
return 0;
} else if (o1 == null) {
return -1;
} else if (o2 == null) {
return 1;
} else {
return o1.moduleId.compareTo(o2.moduleId);
}
4 years ago
}
}
}