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.
218 lines
8.7 KiB
Java
218 lines
8.7 KiB
Java
package com.fox2code.mmm;
|
|
|
|
import com.fox2code.mmm.utils.io.Files;
|
|
import com.fox2code.mmm.utils.io.Http;
|
|
|
|
import org.json.JSONObject;
|
|
|
|
import java.io.BufferedReader;
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.InputStreamReader;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.util.HashMap;
|
|
|
|
import timber.log.Timber;
|
|
|
|
// See https://docs.github.com/en/rest/reference/repos#releases
|
|
public class AppUpdateManager {
|
|
public static final int FLAG_COMPAT_LOW_QUALITY = 0x0001;
|
|
public static final int FLAG_COMPAT_NO_EXT = 0x0002;
|
|
public static final int FLAG_COMPAT_MAGISK_CMD = 0x0004;
|
|
public static final int FLAG_COMPAT_NEED_32BIT = 0x0008;
|
|
public static final int FLAG_COMPAT_MALWARE = 0x0010;
|
|
public static final int FLAG_COMPAT_NO_ANSI = 0x0020;
|
|
public static final int FLAG_COMPAT_FORCE_ANSI = 0x0040;
|
|
public static final int FLAG_COMPAT_FORCE_HIDE = 0x0080;
|
|
public static final int FLAG_COMPAT_MMT_REBORN = 0x0100;
|
|
public static final int FLAG_COMPAT_ZIP_WRAPPER = 0x0200;
|
|
private static final AppUpdateManager INSTANCE = new AppUpdateManager();
|
|
public static final String RELEASES_API_URL = "https://api.github.com/repos/Fox2Code/FoxMagiskModuleManager/releases/latest";
|
|
private final HashMap<String, Integer> compatDataId = new HashMap<>();
|
|
private final Object updateLock = new Object();
|
|
private final File compatFile;
|
|
private String latestRelease;
|
|
private long lastChecked;
|
|
|
|
private AppUpdateManager() {
|
|
this.compatFile = new File(MainApplication.getINSTANCE().getFilesDir(), "compat.txt");
|
|
this.latestRelease = MainApplication.getBootSharedPreferences().getString("updater_latest_release", BuildConfig.VERSION_NAME);
|
|
this.lastChecked = 0;
|
|
if (this.compatFile.isFile()) {
|
|
try {
|
|
this.parseCompatibilityFlags(new FileInputStream(this.compatFile));
|
|
} catch (
|
|
IOException ignored) {
|
|
}
|
|
}
|
|
}
|
|
|
|
public static AppUpdateManager getAppUpdateManager() {
|
|
return INSTANCE;
|
|
}
|
|
|
|
public static int getFlagsForModule(String moduleId) {
|
|
return INSTANCE.getCompatibilityFlags(moduleId);
|
|
}
|
|
|
|
public static boolean shouldForceHide(String repoId) {
|
|
if (BuildConfig.DEBUG || repoId.startsWith("repo_") || repoId.equals("magisk_alt_repo"))
|
|
return false;
|
|
return !repoId.startsWith("repo_") && (INSTANCE.getCompatibilityFlags(repoId) & FLAG_COMPAT_FORCE_HIDE) != 0;
|
|
}
|
|
|
|
// Return true if should show a notification
|
|
public boolean checkUpdate(boolean force) {
|
|
if (!BuildConfig.ENABLE_AUTO_UPDATER)
|
|
return false;
|
|
if (!force && this.peekShouldUpdate())
|
|
return true;
|
|
long lastChecked = this.lastChecked;
|
|
if (lastChecked != 0 &&
|
|
// Avoid spam calls by putting a 60 seconds timer
|
|
lastChecked < System.currentTimeMillis() - 60000L)
|
|
return force && this.peekShouldUpdate();
|
|
synchronized (this.updateLock) {
|
|
if (lastChecked != this.lastChecked)
|
|
return this.peekShouldUpdate();
|
|
boolean preReleaseNewer = true;
|
|
try {
|
|
JSONObject releases = new JSONObject(new String(Http.doHttpGet(RELEASES_API_URL, false), StandardCharsets.UTF_8));
|
|
String latestRelease = null, latestPreRelease = null;
|
|
for (int i = 0; i < releases.length(); i++) {
|
|
JSONObject release;
|
|
try {
|
|
release = releases.getJSONObject(String.valueOf(i));
|
|
} catch (
|
|
Exception e) {
|
|
continue;
|
|
}
|
|
// Skip invalid entries
|
|
if (release.getBoolean("draft"))
|
|
continue;
|
|
boolean preRelease = release.getBoolean("prerelease");
|
|
String version = release.getString("tag_name");
|
|
if (version.startsWith("v"))
|
|
version = version.substring(1);
|
|
if (preRelease) {
|
|
if (latestPreRelease == null)
|
|
latestPreRelease = version;
|
|
} else if (latestRelease == null) {
|
|
latestRelease = version;
|
|
if (latestPreRelease == null)
|
|
preReleaseNewer = false;
|
|
}
|
|
if (latestRelease != null && latestPreRelease != null) {
|
|
break; // We read everything we needed to read.
|
|
}
|
|
}
|
|
if (latestRelease != null)
|
|
this.latestRelease = latestRelease;
|
|
if (BuildConfig.DEBUG)
|
|
Timber.d("Latest release: %s", latestRelease);
|
|
if (BuildConfig.DEBUG)
|
|
Timber.d("Latest pre-release: %s", latestPreRelease);
|
|
if (BuildConfig.DEBUG)
|
|
Timber.d("Latest pre-release newer: %s", preReleaseNewer);
|
|
this.lastChecked = System.currentTimeMillis();
|
|
} catch (
|
|
Exception ioe) {
|
|
Timber.e(ioe);
|
|
}
|
|
}
|
|
return this.peekShouldUpdate();
|
|
}
|
|
|
|
public void checkUpdateCompat() {
|
|
compatDataId.clear();
|
|
try {
|
|
Files.write(compatFile, new byte[0]);
|
|
} catch (
|
|
IOException e) {
|
|
Timber.e(e);
|
|
}
|
|
// There once lived an implementation that used a GitHub API to get the compatibility flags. It was removed because it was too slow and the API was rate limited.
|
|
Timber.w("Remote compatibility data flags are not implemented.");
|
|
}
|
|
|
|
public boolean peekShouldUpdate() {
|
|
if (!BuildConfig.ENABLE_AUTO_UPDATER || BuildConfig.DEBUG)
|
|
return false;
|
|
// Convert both BuildConfig.VERSION_NAME and latestRelease to int
|
|
int currentVersion = 0, latestVersion = 0;
|
|
try {
|
|
currentVersion = Integer.parseInt(BuildConfig.VERSION_NAME.replaceAll("\\D", ""));
|
|
latestVersion = Integer.parseInt(this.latestRelease.replace("v", "").replaceAll("\\D", ""));
|
|
} catch (
|
|
NumberFormatException ignored) {
|
|
}
|
|
return currentVersion < latestVersion;
|
|
}
|
|
|
|
public boolean peekHasUpdate() {
|
|
if (!BuildConfig.ENABLE_AUTO_UPDATER || BuildConfig.DEBUG)
|
|
return false;
|
|
return this.peekShouldUpdate();
|
|
}
|
|
|
|
private void parseCompatibilityFlags(InputStream inputStream) throws IOException {
|
|
compatDataId.clear();
|
|
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
|
|
String line;
|
|
while ((line = bufferedReader.readLine()) != null) {
|
|
line = line.trim();
|
|
if (line.isEmpty() || line.startsWith("#"))
|
|
continue;
|
|
int i = line.indexOf('/');
|
|
if (i == -1)
|
|
continue;
|
|
int value = 0;
|
|
for (String arg : line.substring(i + 1).split(",")) {
|
|
switch (arg) {
|
|
default:
|
|
break;
|
|
case "lowQuality":
|
|
value |= FLAG_COMPAT_LOW_QUALITY;
|
|
break;
|
|
case "noExt":
|
|
value |= FLAG_COMPAT_NO_EXT;
|
|
break;
|
|
case "magiskCmd":
|
|
value |= FLAG_COMPAT_MAGISK_CMD;
|
|
break;
|
|
case "need32bit":
|
|
value |= FLAG_COMPAT_NEED_32BIT;
|
|
break;
|
|
case "malware":
|
|
value |= FLAG_COMPAT_MALWARE;
|
|
break;
|
|
case "noANSI":
|
|
value |= FLAG_COMPAT_NO_ANSI;
|
|
break;
|
|
case "forceANSI":
|
|
value |= FLAG_COMPAT_FORCE_ANSI;
|
|
break;
|
|
case "forceHide":
|
|
value |= FLAG_COMPAT_FORCE_HIDE;
|
|
break;
|
|
case "mmtReborn":
|
|
value |= FLAG_COMPAT_MMT_REBORN;
|
|
break;
|
|
case "wrapper":
|
|
value |= FLAG_COMPAT_ZIP_WRAPPER;
|
|
break;
|
|
}
|
|
}
|
|
compatDataId.put(line.substring(0, i), value);
|
|
}
|
|
bufferedReader.close();
|
|
}
|
|
|
|
public int getCompatibilityFlags(String moduleId) {
|
|
Integer compatFlags = compatDataId.get(moduleId);
|
|
return compatFlags == null ? 0 : compatFlags;
|
|
}
|
|
}
|