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.
FoxMagiskModuleManager/app/src/main/java/com/fox2code/mmm/AppUpdateManager.java

176 lines
7.2 KiB
Java

package com.fox2code.mmm;
import com.fox2code.mmm.utils.io.Files;
import com.fox2code.mmm.utils.io.net.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;
public static final String RELEASES_API_URL = "https://api.github.com/repos/Androidacy/MagiskModuleManager/releases/latest";
private static final AppUpdateManager INSTANCE = new AppUpdateManager();
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();
try {
JSONObject release = new JSONObject(new String(Http.doHttpGet(RELEASES_API_URL, false), StandardCharsets.UTF_8));
String latestRelease = null;
boolean preRelease = false;
// get latest_release from tag_name translated to int
if (release.has("tag_name")) {
latestRelease = release.getString("tag_name");
preRelease = release.getBoolean("prerelease");
}
Timber.d("Latest release: %s, isPreRelease: %s", latestRelease, preRelease);
if (latestRelease == null)
return false;
if (preRelease) {
this.latestRelease = "99999999"; // prevent updating to pre-release
return false;
}
this.latestRelease = latestRelease;
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 -> {
}
case "lowQuality" -> value |= FLAG_COMPAT_LOW_QUALITY;
case "noExt" -> value |= FLAG_COMPAT_NO_EXT;
case "magiskCmd" -> value |= FLAG_COMPAT_MAGISK_CMD;
case "need32bit" -> value |= FLAG_COMPAT_NEED_32BIT;
case "malware" -> value |= FLAG_COMPAT_MALWARE;
case "noANSI" -> value |= FLAG_COMPAT_NO_ANSI;
case "forceANSI" -> value |= FLAG_COMPAT_FORCE_ANSI;
case "forceHide" -> value |= FLAG_COMPAT_FORCE_HIDE;
case "mmtReborn" -> value |= FLAG_COMPAT_MMT_REBORN;
case "wrapper" -> value |= FLAG_COMPAT_ZIP_WRAPPER;
}
}
compatDataId.put(line.substring(0, i), value);
}
bufferedReader.close();
}
public int getCompatibilityFlags(String moduleId) {
Integer compatFlags = compatDataId.get(moduleId);
return compatFlags == null ? 0 : compatFlags;
}
}