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.
375 lines
16 KiB
Kotlin
375 lines
16 KiB
Kotlin
|
3 years ago
|
/*
|
||
|
|
* 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.
|
||
|
|
*/
|
||
|
|
|
||
|
4 years ago
|
package com.fox2code.mmm.module;
|
||
|
5 years ago
|
|
||
|
|
import android.content.Context;
|
||
|
|
import android.content.pm.PackageManager;
|
||
|
5 years ago
|
import android.view.View;
|
||
|
5 years ago
|
|
||
|
5 years ago
|
import androidx.annotation.NonNull;
|
||
|
5 years ago
|
import androidx.annotation.StringRes;
|
||
|
|
|
||
|
4 years ago
|
import com.fox2code.mmm.MainApplication;
|
||
|
|
import com.fox2code.mmm.NotificationType;
|
||
|
|
import com.fox2code.mmm.R;
|
||
|
|
import com.fox2code.mmm.XHooks;
|
||
|
5 years ago
|
import com.fox2code.mmm.manager.LocalModuleInfo;
|
||
|
5 years ago
|
import com.fox2code.mmm.manager.ModuleInfo;
|
||
|
|
import com.fox2code.mmm.repo.RepoModule;
|
||
|
|
import com.fox2code.mmm.utils.IntentHelper;
|
||
|
4 years ago
|
import com.fox2code.mmm.utils.io.PropUtils;
|
||
|
3 years ago
|
import com.fox2code.mmm.utils.io.net.Http;
|
||
|
5 years ago
|
|
||
|
|
import java.util.Comparator;
|
||
|
3 years ago
|
import java.util.HashSet;
|
||
|
5 years ago
|
import java.util.List;
|
||
|
4 years ago
|
import java.util.Locale;
|
||
|
5 years ago
|
import java.util.Objects;
|
||
|
3 years ago
|
import java.util.Set;
|
||
|
5 years ago
|
|
||
|
4 years ago
|
import timber.log.Timber;
|
||
|
|
|
||
|
5 years ago
|
public final class ModuleHolder implements Comparable<ModuleHolder> {
|
||
|
|
|
||
|
|
public final String moduleId;
|
||
|
|
public final NotificationType notificationType;
|
||
|
|
public final Type separator;
|
||
|
5 years ago
|
public int footerPx;
|
||
|
5 years ago
|
public View.OnClickListener onClickListener;
|
||
|
3 years ago
|
|
||
|
5 years ago
|
public LocalModuleInfo moduleInfo;
|
||
|
5 years ago
|
public RepoModule repoModule;
|
||
|
5 years ago
|
public int filterLevel;
|
||
|
5 years ago
|
|
||
|
|
public ModuleHolder(String moduleId) {
|
||
|
|
this.moduleId = Objects.requireNonNull(moduleId);
|
||
|
|
this.notificationType = null;
|
||
|
|
this.separator = null;
|
||
|
5 years ago
|
this.footerPx = -1;
|
||
|
5 years ago
|
}
|
||
|
|
|
||
|
|
public ModuleHolder(NotificationType notificationType) {
|
||
|
|
this.moduleId = "";
|
||
|
|
this.notificationType = Objects.requireNonNull(notificationType);
|
||
|
|
this.separator = null;
|
||
|
5 years ago
|
this.footerPx = -1;
|
||
|
5 years ago
|
}
|
||
|
|
|
||
|
|
public ModuleHolder(Type separator) {
|
||
|
|
this.moduleId = "";
|
||
|
|
this.notificationType = null;
|
||
|
|
this.separator = separator;
|
||
|
5 years ago
|
this.footerPx = -1;
|
||
|
5 years ago
|
}
|
||
|
|
|
||
|
4 years ago
|
@SuppressWarnings("unused")
|
||
|
|
public ModuleHolder(int footerPx, boolean header) {
|
||
|
5 years ago
|
this.moduleId = "";
|
||
|
|
this.notificationType = null;
|
||
|
|
this.separator = null;
|
||
|
|
this.footerPx = footerPx;
|
||
|
5 years ago
|
this.filterLevel = header ? 1 : 0;
|
||
|
5 years ago
|
}
|
||
|
|
|
||
|
|
public boolean isModuleHolder() {
|
||
|
5 years ago
|
return this.notificationType == null && this.separator == null && this.footerPx == -1;
|
||
|
5 years ago
|
}
|
||
|
|
|
||
|
|
public ModuleInfo getMainModuleInfo() {
|
||
|
3 years ago
|
return this.repoModule != null && (this.moduleInfo == null || this.moduleInfo.versionCode < this.repoModule.moduleInfo.versionCode) ? this.repoModule.moduleInfo : this.moduleInfo;
|
||
|
5 years ago
|
}
|
||
|
|
|
||
|
|
public String getUpdateZipUrl() {
|
||
|
3 years ago
|
return this.moduleInfo == null || (this.repoModule != null && this.moduleInfo.updateVersionCode < this.repoModule.moduleInfo.versionCode) ? this.repoModule.zipUrl : this.moduleInfo.updateZipUrl;
|
||
|
5 years ago
|
}
|
||
|
|
|
||
|
4 years ago
|
public String getUpdateZipRepo() {
|
||
|
3 years ago
|
return this.moduleInfo == null || (this.repoModule != null && this.moduleInfo.updateVersionCode < this.repoModule.moduleInfo.versionCode) ? this.repoModule.repoData.preferenceId : "update_json";
|
||
|
4 years ago
|
}
|
||
|
|
|
||
|
5 years ago
|
public String getUpdateZipChecksum() {
|
||
|
3 years ago
|
return this.moduleInfo == null || (this.repoModule != null && this.moduleInfo.updateVersionCode < this.repoModule.moduleInfo.versionCode) ? this.repoModule.checksum : this.moduleInfo.updateChecksum;
|
||
|
5 years ago
|
}
|
||
|
|
|
||
|
5 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;
|
||
|
|
}
|
||
|
|
|
||
|
4 years ago
|
public String getMainModuleNameLowercase() {
|
||
|
|
return this.getMainModuleName().toLowerCase(Locale.ROOT);
|
||
|
|
}
|
||
|
|
|
||
|
5 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;
|
||
|
3 years ago
|
return timeStamp <= 0 ? "" : MainApplication.formatTime(timeStamp);
|
||
|
5 years ago
|
}
|
||
|
5 years ago
|
|
||
|
5 years ago
|
public String getRepoName() {
|
||
|
|
if (this.repoModule == null) return "";
|
||
|
|
return this.repoModule.repoName;
|
||
|
|
}
|
||
|
5 years ago
|
|
||
|
|
public boolean hasFlag(int flag) {
|
||
|
|
return this.moduleInfo != null && this.moduleInfo.hasFlag(flag);
|
||
|
|
}
|
||
|
|
|
||
|
|
public Type getType() {
|
||
|
5 years ago
|
if (this.footerPx != -1) {
|
||
|
3 years ago
|
Timber.i("Module %s is footer", this.moduleId);
|
||
|
5 years ago
|
return Type.FOOTER;
|
||
|
|
} else if (this.separator != null) {
|
||
|
3 years ago
|
Timber.i("Module %s is separator", this.moduleId);
|
||
|
5 years ago
|
return Type.SEPARATOR;
|
||
|
|
} else if (this.notificationType != null) {
|
||
|
3 years ago
|
Timber.i("Module %s is notification", this.moduleId);
|
||
|
5 years ago
|
return Type.NOTIFICATION;
|
||
|
|
} else if (this.moduleInfo == null) {
|
||
|
|
return Type.INSTALLABLE;
|
||
|
3 years ago
|
} else if (this.moduleInfo.versionCode < this.moduleInfo.updateVersionCode || (this.repoModule != null && this.moduleInfo.versionCode < this.repoModule.moduleInfo.versionCode)) {
|
||
|
3 years ago
|
boolean ignoreUpdate = false;
|
||
|
|
try {
|
||
|
3 years ago
|
if (Objects.requireNonNull(Objects.requireNonNull(MainApplication.getSharedPreferences("mmm")).getStringSet("pref_background_update_check_excludes", new HashSet<>())).contains(moduleInfo.id))
|
||
|
3 years ago
|
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
|
||
|
3 years ago
|
Set<String> stringSetT = Objects.requireNonNull(MainApplication.getSharedPreferences("mmm")).getStringSet("pref_background_update_check_excludes_version", new HashSet<>());
|
||
|
3 years ago
|
String version = "";
|
||
|
3 years ago
|
Timber.d(stringSetT.toString());
|
||
|
3 years ago
|
// unfortunately, stringset.contains() doesn't work for partial matches
|
||
|
3 years ago
|
// 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;
|
||
|
|
}
|
||
|
3 years ago
|
}
|
||
|
|
String remoteVersionCode = String.valueOf(moduleInfo.updateVersionCode);
|
||
|
|
if (repoModule != null) {
|
||
|
|
remoteVersionCode = String.valueOf(repoModule.moduleInfo.versionCode);
|
||
|
|
}
|
||
|
3 years ago
|
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
|
||
|
3 years ago
|
Timber.d("igV start with");
|
||
|
|
version = version.split(":")[1];
|
||
|
|
// this version and newer
|
||
|
3 years ago
|
if (version.startsWith("^")) {
|
||
|
3 years ago
|
Timber.d("igV: newer");
|
||
|
|
// the wantsversion and newer
|
||
|
|
if (remoteVersionCodeInt >= wantsVersion) {
|
||
|
|
Timber.d("igV: skipping");
|
||
|
3 years ago
|
// if it is, we skip it
|
||
|
|
ignoreUpdate = true;
|
||
|
|
}
|
||
|
|
} else if (version.endsWith("$")) {
|
||
|
3 years ago
|
Timber.d("igV: older");
|
||
|
|
// this wantsversion and older
|
||
|
|
if (remoteVersionCodeInt <= wantsVersion) {
|
||
|
|
Timber.d("igV: skipping");
|
||
|
3 years ago
|
// if it is, we skip it
|
||
|
|
ignoreUpdate = true;
|
||
|
|
}
|
||
|
3 years ago
|
} else if (wantsVersion == remoteVersionCodeInt) {
|
||
|
|
Timber.d("igV: equal");
|
||
|
3 years ago
|
// if it is, we skip it
|
||
|
|
ignoreUpdate = true;
|
||
|
|
}
|
||
|
3 years ago
|
|
||
|
3 years ago
|
}
|
||
|
|
if (ignoreUpdate) {
|
||
|
|
Timber.d("Module %s has update, but is ignored", this.moduleId);
|
||
|
|
return Type.INSTALLABLE;
|
||
|
3 years ago
|
} else {
|
||
|
3 years ago
|
Objects.requireNonNull(MainApplication.getINSTANCE()).modulesHaveUpdates = true;
|
||
|
3 years ago
|
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;
|
||
|
3 years ago
|
}
|
||
|
5 years ago
|
} else {
|
||
|
|
return Type.INSTALLED;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public Type getCompareType(Type type) {
|
||
|
|
if (this.separator != null) {
|
||
|
|
return this.separator;
|
||
|
3 years ago
|
} else if (this.notificationType != null && this.notificationType.special) {
|
||
|
5 years ago
|
return Type.SPECIAL_NOTIFICATIONS;
|
||
|
|
} else {
|
||
|
|
return type;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public boolean shouldRemove() {
|
||
|
3 years ago
|
if (this.repoModule != null && this.moduleInfo != null && !hasUpdate()) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
3 years ago
|
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()));
|
||
|
5 years ago
|
}
|
||
|
|
|
||
|
|
public void getButtons(Context context, List<ActionButtonType> buttonTypeList, boolean showcaseMode) {
|
||
|
|
if (!this.isModuleHolder()) return;
|
||
|
5 years ago
|
LocalModuleInfo localModuleInfo = this.moduleInfo;
|
||
|
4 years ago
|
// 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);
|
||
|
|
}
|
||
|
5 years ago
|
if (localModuleInfo != null && !showcaseMode) {
|
||
|
5 years ago
|
buttonTypeList.add(ActionButtonType.UNINSTALL);
|
||
|
|
}
|
||
|
5 years ago
|
if (this.repoModule != null && this.repoModule.notesUrl != null) {
|
||
|
5 years ago
|
buttonTypeList.add(ActionButtonType.INFO);
|
||
|
|
}
|
||
|
3 years ago
|
if ((this.repoModule != null || (localModuleInfo != null && localModuleInfo.updateZipUrl != null))) {
|
||
|
5 years ago
|
buttonTypeList.add(ActionButtonType.UPDATE_INSTALL);
|
||
|
|
}
|
||
|
|
String config = this.getMainModuleConfig();
|
||
|
|
if (config != null) {
|
||
|
5 years ago
|
if (config.startsWith("https://www.androidacy.com/") && Http.hasWebView()) {
|
||
|
5 years ago
|
buttonTypeList.add(ActionButtonType.CONFIG);
|
||
|
5 years ago
|
} else {
|
||
|
|
String pkg = IntentHelper.getPackageOfConfig(config);
|
||
|
|
try {
|
||
|
5 years ago
|
XHooks.checkConfigTargetExists(context, pkg, config);
|
||
|
5 years ago
|
buttonTypeList.add(ActionButtonType.CONFIG);
|
||
|
|
} catch (PackageManager.NameNotFoundException e) {
|
||
|
3 years ago
|
Timber.w("Config package \"" + pkg + "\" missing for module \"" + this.moduleId + "\"");
|
||
|
5 years ago
|
}
|
||
|
5 years ago
|
}
|
||
|
|
}
|
||
|
|
ModuleInfo moduleInfo = this.getMainModuleInfo();
|
||
|
5 years ago
|
if (moduleInfo == null) { // Avoid concurrency NPE
|
||
|
|
if (localModuleInfo == null) return;
|
||
|
|
moduleInfo = localModuleInfo;
|
||
|
|
}
|
||
|
5 years ago
|
if (moduleInfo.support != null) {
|
||
|
|
buttonTypeList.add(ActionButtonType.SUPPORT);
|
||
|
|
}
|
||
|
|
if (moduleInfo.donate != null) {
|
||
|
|
buttonTypeList.add(ActionButtonType.DONATE);
|
||
|
|
}
|
||
|
4 years ago
|
if (moduleInfo.safe) {
|
||
|
|
buttonTypeList.add(ActionButtonType.SAFE);
|
||
|
4 years ago
|
} else {
|
||
|
|
Timber.d("Module %s is not safe", this.moduleId);
|
||
|
4 years ago
|
}
|
||
|
5 years ago
|
}
|
||
|
|
|
||
|
|
public boolean hasUpdate() {
|
||
|
3 years ago
|
return this.moduleInfo != null && this.repoModule != null && this.moduleInfo.versionCode < this.repoModule.moduleInfo.versionCode;
|
||
|
5 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);
|
||
|
3 years ago
|
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 + '}';
|
||
|
5 years ago
|
}
|
||
|
|
|
||
|
|
public enum Type implements Comparator<ModuleHolder> {
|
||
|
3 years ago
|
HEADER(R.string.loading, false, false), SEPARATOR(R.string.loading, false, false) {
|
||
|
5 years ago
|
@Override
|
||
|
|
@SuppressWarnings("ConstantConditions")
|
||
|
|
public int compare(ModuleHolder o1, ModuleHolder o2) {
|
||
|
|
return o1.separator.compareTo(o2.separator);
|
||
|
|
}
|
||
|
3 years ago
|
}, NOTIFICATION(R.string.loading, true, false) {
|
||
|
5 years ago
|
@Override
|
||
|
|
@SuppressWarnings("ConstantConditions")
|
||
|
|
public int compare(ModuleHolder o1, ModuleHolder o2) {
|
||
|
|
return o1.notificationType.compareTo(o2.notificationType);
|
||
|
|
}
|
||
|
3 years ago
|
}, UPDATABLE(R.string.updatable, true, true) {
|
||
|
5 years ago
|
@Override
|
||
|
|
public int compare(ModuleHolder o1, ModuleHolder o2) {
|
||
|
5 years ago
|
int cmp = Integer.compare(o1.filterLevel, o2.filterLevel);
|
||
|
|
if (cmp != 0) return cmp;
|
||
|
5 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());
|
||
|
5 years ago
|
}
|
||
|
3 years ago
|
}, INSTALLED(R.string.installed, true, true) {
|
||
|
|
// get stacktrace for debugging
|
||
|
5 years ago
|
@Override
|
||
|
|
public int compare(ModuleHolder o1, ModuleHolder o2) {
|
||
|
5 years ago
|
int cmp = Integer.compare(o1.filterLevel, o2.filterLevel);
|
||
|
|
if (cmp != 0) return cmp;
|
||
|
3 years ago
|
return o1.getMainModuleNameLowercase().compareTo(o2.getMainModuleNameLowercase());
|
||
|
5 years ago
|
}
|
||
|
3 years ago
|
}, SPECIAL_NOTIFICATIONS(R.string.loading, true, false), INSTALLABLE(R.string.online_repo, true, true) {
|
||
|
5 years ago
|
@Override
|
||
|
|
public int compare(ModuleHolder o1, ModuleHolder o2) {
|
||
|
5 years ago
|
int cmp = Integer.compare(o1.filterLevel, o2.filterLevel);
|
||
|
|
if (cmp != 0) return cmp;
|
||
|
5 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());
|
||
|
5 years ago
|
}
|
||
|
3 years ago
|
}, FOOTER(R.string.loading, false, false);
|
||
|
5 years ago
|
|
||
|
|
@StringRes
|
||
|
|
public final int title;
|
||
|
|
public final boolean hasBackground;
|
||
|
5 years ago
|
public final boolean moduleHolder;
|
||
|
5 years ago
|
|
||
|
5 years ago
|
Type(@StringRes int title, boolean hasBackground, boolean moduleHolder) {
|
||
|
5 years ago
|
this.title = title;
|
||
|
|
this.hasBackground = hasBackground;
|
||
|
5 years ago
|
this.moduleHolder = moduleHolder;
|
||
|
5 years ago
|
}
|
||
|
|
|
||
|
|
// Note: This method should only be called if both element have the same type
|
||
|
|
@Override
|
||
|
|
public int compare(ModuleHolder o1, ModuleHolder o2) {
|
||
|
4 years ago
|
if (o1 == o2) {
|
||
|
|
return 0;
|
||
|
|
} else if (o1 == null) {
|
||
|
|
return -1;
|
||
|
|
} else if (o2 == null) {
|
||
|
|
return 1;
|
||
|
|
} else {
|
||
|
|
return o1.moduleId.compareTo(o2.moduleId);
|
||
|
|
}
|
||
|
5 years ago
|
}
|
||
|
|
}
|
||
|
|
}
|