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

367 lines
16 KiB
Java

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 = "";
Set<String> stringSet = stringSetT;
Timber.d(stringSet.toString());
if (stringSet.contains(this.moduleInfo.id)) {
// get the one matching
Timber.d("found mod in ig ver");
version = stringSet.stream().filter(s -> s.startsWith(this.moduleInfo.id)).findFirst().orElse("");
Timber.d("igV:%s", version);
}
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 localVersionCode = Integer.parseInt(String.valueOf(moduleInfo.versionCode));
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
// if it starts with ^, it's this version and newer, if it ends with $, it's this version and older
if (version.startsWith("^")) {
Timber.d("igV start with");
// this version and newer
if (wantsVersion <= remoteVersionCodeInt || wantsVersion <= localVersionCode) {
// if it is, we skip it
Timber.d("igu true");
ignoreUpdate = true;
}
} else if (version.endsWith("$")) {
Timber.d("igV end with");
// this version and older
if (wantsVersion >= remoteVersionCodeInt || wantsVersion >= localVersionCode) {
// if it is, we skip it
Timber.d("igu true");
ignoreUpdate = true;
}
} else if (wantsVersion == remoteVersionCodeInt || wantsVersion == localVersionCode) {
// if it is, we skip it
Timber.d("igu true");
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.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
}
}
}