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 ;
}
}