Fix and Refactor both RepoManager and BackgroundUpdateChecker

pull/27/head
Fox2Code 3 years ago
parent 23c153a6ed
commit 248865d617

@ -37,7 +37,7 @@ android {
buildConfigField( buildConfigField(
"java.util.List<String>", "java.util.List<String>",
"ENABLED_REPOS", "ENABLED_REPOS",
"java.util.Arrays.asList(\"magisk_alt_repo\", \"dg_magisk_repo\", \"androidacy_repo\")", "java.util.Arrays.asList(\"magisk_alt_repo\", \"androidacy_repo\")",
) )
} }

@ -18,6 +18,8 @@
<!-- Supposed to fix bugs with old firmware, only requested on pre Marshmallow --> <!-- Supposed to fix bugs with old firmware, only requested on pre Marshmallow -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="22" /> android:maxSdkVersion="22" />
<!-- Post background notifications -->
<uses-permission-sdk-23 android:name="android.permission.POST_NOTIFICATIONS" />
<application <application
android:name=".MainApplication" android:name=".MainApplication"

@ -16,6 +16,11 @@ import com.fox2code.mmm.repo.RepoManager;
*/ */
@Keep @Keep
public class XHooks { public class XHooks {
@Keep
public static void onRepoManagerInitialize() {
// Call addXRepo here if you are an XPosed module
}
@Keep @Keep
public static void onRepoManagerInitialized() {} public static void onRepoManagerInitialized() {}

@ -12,8 +12,9 @@ public class BackgroundBootListener extends BroadcastReceiver {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
if (!BOOT_COMPLETED.equals(intent.getAction())) return; if (!BOOT_COMPLETED.equals(intent.getAction())) return;
if (!MainApplication.isBackgroundUpdateCheckEnabled()) return; synchronized (BackgroundUpdateChecker.lock) {
BackgroundUpdateChecker.onMainActivityCreate(context); BackgroundUpdateChecker.onMainActivityCreate(context);
BackgroundUpdateChecker.doCheck(context); BackgroundUpdateChecker.doCheck(context);
}
} }
} }

@ -24,12 +24,15 @@ import com.fox2code.mmm.manager.LocalModuleInfo;
import com.fox2code.mmm.manager.ModuleManager; import com.fox2code.mmm.manager.ModuleManager;
import com.fox2code.mmm.repo.RepoManager; import com.fox2code.mmm.repo.RepoManager;
import com.fox2code.mmm.repo.RepoModule; import com.fox2code.mmm.repo.RepoModule;
import com.fox2code.mmm.utils.PropUtils;
import java.util.HashMap;
import java.util.Random; import java.util.Random;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public class BackgroundUpdateChecker extends Worker { public class BackgroundUpdateChecker extends Worker {
private static boolean easterEggActive = false; private static boolean easterEggActive = false;
static final Object lock = new Object(); // Avoid concurrency issues
public static final String NOTIFICATION_CHANNEL_ID = "background_update"; public static final String NOTIFICATION_CHANNEL_ID = "background_update";
public static final int NOTIFICATION_ID = 1; public static final int NOTIFICATION_ID = 1;
@ -43,9 +46,9 @@ public class BackgroundUpdateChecker extends Worker {
public Result doWork() { public Result doWork() {
if (!NotificationManagerCompat.from(this.getApplicationContext()).areNotificationsEnabled() if (!NotificationManagerCompat.from(this.getApplicationContext()).areNotificationsEnabled()
|| !MainApplication.isBackgroundUpdateCheckEnabled()) return Result.success(); || !MainApplication.isBackgroundUpdateCheckEnabled()) return Result.success();
synchronized (lock) {
doCheck(this.getApplicationContext()); doCheck(this.getApplicationContext());
}
return Result.success(); return Result.success();
} }
@ -53,18 +56,20 @@ public class BackgroundUpdateChecker extends Worker {
Thread.currentThread().setPriority(Thread.MIN_PRIORITY); Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
RepoManager.getINSTANCE().update(null); RepoManager.getINSTANCE().update(null);
ModuleManager.getINSTANCE().scan(); ModuleManager.getINSTANCE().scan();
ModuleManager.getINSTANCE().scan();
int moduleUpdateCount = 0; int moduleUpdateCount = 0;
HashMap<String, RepoModule> repoModules =
RepoManager.getINSTANCE().getModules();
for (LocalModuleInfo localModuleInfo : for (LocalModuleInfo localModuleInfo :
ModuleManager.getINSTANCE().getModules().values()) { ModuleManager.getINSTANCE().getModules().values()) {
if ("twrp-keep".equals(localModuleInfo.id)) continue; if ("twrp-keep".equals(localModuleInfo.id)) continue;
RepoModule repoModule = RepoManager.getINSTANCE() RepoModule repoModule = repoModules.get(localModuleInfo.id);
.getModules().get(localModuleInfo.id);
localModuleInfo.checkModuleUpdate(); localModuleInfo.checkModuleUpdate();
if (localModuleInfo.updateVersionCode > localModuleInfo.versionCode) { if (localModuleInfo.updateVersionCode > localModuleInfo.versionCode &&
!PropUtils.isNullString(localModuleInfo.updateVersion)) {
moduleUpdateCount++; moduleUpdateCount++;
} else if (repoModule != null && } else if (repoModule != null &&
repoModule.moduleInfo.versionCode > localModuleInfo.versionCode) { repoModule.moduleInfo.versionCode > localModuleInfo.versionCode &&
!PropUtils.isNullString(repoModule.moduleInfo.version)) {
moduleUpdateCount++; moduleUpdateCount++;
} }
} }

@ -3,9 +3,13 @@ package com.fox2code.mmm.manager;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull;
import com.fox2code.mmm.MainApplication; import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.installer.InstallerInitializer; import com.fox2code.mmm.installer.InstallerInitializer;
import com.fox2code.mmm.utils.Http;
import com.fox2code.mmm.utils.PropUtils; import com.fox2code.mmm.utils.PropUtils;
import com.fox2code.mmm.utils.SyncManager;
import com.topjohnwu.superuser.Shell; import com.topjohnwu.superuser.Shell;
import com.topjohnwu.superuser.io.SuFile; import com.topjohnwu.superuser.io.SuFile;
import com.topjohnwu.superuser.io.SuFileInputStream; import com.topjohnwu.superuser.io.SuFileInputStream;
@ -17,7 +21,7 @@ import java.nio.charset.StandardCharsets;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
public final class ModuleManager { public final class ModuleManager extends SyncManager {
private static final String TAG = "ModuleManager"; private static final String TAG = "ModuleManager";
private static final int FLAG_MM_INVALID = ModuleInfo.FLAG_METADATA_INVALID; private static final int FLAG_MM_INVALID = ModuleInfo.FLAG_METADATA_INVALID;
@ -28,9 +32,7 @@ public final class ModuleManager {
private static final int FLAGS_RESET_UPDATE = FLAG_MM_INVALID | FLAG_MM_UNPROCESSED; private static final int FLAGS_RESET_UPDATE = FLAG_MM_INVALID | FLAG_MM_UNPROCESSED;
private final HashMap<String, LocalModuleInfo> moduleInfos; private final HashMap<String, LocalModuleInfo> moduleInfos;
private final SharedPreferences bootPrefs; private final SharedPreferences bootPrefs;
private final Object scanLock = new Object();
private int updatableModuleCount = 0; private int updatableModuleCount = 0;
private boolean scanning;
private static final ModuleManager INSTANCE = new ModuleManager(); private static final ModuleManager INSTANCE = new ModuleManager();
@ -43,36 +45,7 @@ public final class ModuleManager {
this.bootPrefs = MainApplication.getBootSharedPreferences(); this.bootPrefs = MainApplication.getBootSharedPreferences();
} }
// MultiThread friendly method protected void scanInternal(@NonNull UpdateListener updateListener) {
public void scan() {
if (!this.scanning) {
// Do scan
synchronized (scanLock) {
this.scanning = true;
try {
this.scanInternal();
} finally {
this.scanning = false;
}
}
} else {
// Wait for current scan
synchronized (scanLock) {}
}
}
// Pause execution until the scan is completed if one is currently running
public void afterScan() {
if (this.scanning) synchronized (this.scanLock) {}
}
public void runAfterScan(Runnable runnable) {
synchronized (this.scanLock) {
runnable.run();
}
}
private void scanInternal() {
boolean firstBoot = MainApplication.isFirstBoot(); boolean firstBoot = MainApplication.isFirstBoot();
boolean firstScan = this.bootPrefs.getBoolean("mm_first_scan", true); boolean firstScan = this.bootPrefs.getBoolean("mm_first_scan", true);
SharedPreferences.Editor editor = firstScan ? this.bootPrefs.edit() : null; SharedPreferences.Editor editor = firstScan ? this.bootPrefs.edit() : null;

@ -4,6 +4,8 @@ import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull;
import com.fox2code.mmm.MainApplication; import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.XHooks; import com.fox2code.mmm.XHooks;
import com.fox2code.mmm.androidacy.AndroidacyRepoData; import com.fox2code.mmm.androidacy.AndroidacyRepoData;
@ -12,14 +14,16 @@ import com.fox2code.mmm.utils.Files;
import com.fox2code.mmm.utils.Hashes; import com.fox2code.mmm.utils.Hashes;
import com.fox2code.mmm.utils.Http; import com.fox2code.mmm.utils.Http;
import com.fox2code.mmm.utils.PropUtils; import com.fox2code.mmm.utils.PropUtils;
import com.fox2code.mmm.utils.SyncManager;
import java.io.File; import java.io.File;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
public final class RepoManager { public final class RepoManager extends SyncManager {
private static final String TAG = "RepoManager"; private static final String TAG = "RepoManager";
private static final String MAGISK_REPO_MANAGER = private static final String MAGISK_REPO_MANAGER =
@ -75,6 +79,7 @@ public final class RepoManager {
private boolean initialized; private boolean initialized;
private RepoManager(MainApplication mainApplication) { private RepoManager(MainApplication mainApplication) {
INSTANCE = this; // Set early fox XHooks
this.initialized = false; this.initialized = false;
this.mainApplication = mainApplication; this.mainApplication = mainApplication;
this.repoData = new LinkedHashMap<>(); this.repoData = new LinkedHashMap<>();
@ -90,6 +95,7 @@ public final class RepoManager {
dgRepo.defaultWebsite = "https://dergoogler.com/repo"; dgRepo.defaultWebsite = "https://dergoogler.com/repo";
this.androidacyRepoData = this.addAndroidacyRepoData(); this.androidacyRepoData = this.addAndroidacyRepoData();
this.customRepoManager = new CustomRepoManager(mainApplication, this); this.customRepoManager = new CustomRepoManager(mainApplication, this);
XHooks.onRepoManagerInitialize();
// Populate default cache // Populate default cache
boolean x = false; boolean x = false;
for (RepoData repoData:this.repoData.values()) { for (RepoData repoData:this.repoData.values()) {
@ -136,7 +142,7 @@ public final class RepoManager {
if (DG_MAGISK_REPO.equals(url)) if (DG_MAGISK_REPO.equals(url))
url = DG_MAGISK_REPO_GITHUB; url = DG_MAGISK_REPO_GITHUB;
RepoData repoData; RepoData repoData;
synchronized (this.repoUpdateLock) { synchronized (this.syncLock) {
repoData = this.repoData.get(url); repoData = this.repoData.get(url);
if (repoData == null) { if (repoData == null) {
if (ANDROIDACY_TEST_MAGISK_REPO_ENDPOINT.equals(url) || if (ANDROIDACY_TEST_MAGISK_REPO_ENDPOINT.equals(url) ||
@ -152,57 +158,18 @@ public final class RepoManager {
return repoData; return repoData;
} }
public interface UpdateListener {
void update(double value);
}
private final Object repoUpdateLock = new Object();
private boolean repoUpdating;
private boolean repoLastResult = true; private boolean repoLastResult = true;
public boolean isRepoUpdating() {
return this.repoUpdating;
}
public void afterUpdate() {
if (this.repoUpdating) synchronized (this.repoUpdateLock) {}
}
public void runAfterUpdate(Runnable runnable) {
synchronized (this.repoUpdateLock) {
runnable.run();
}
}
// MultiThread friendly method
public void update(UpdateListener updateListener) {
if (updateListener == null)
updateListener = value -> {};
if (!this.repoUpdating) {
// Do scan
synchronized (this.repoUpdateLock) {
this.repoUpdating = true;
try {
this.repoLastResult =
this.scanInternal(updateListener);
} finally {
this.repoUpdating = false;
}
}
} else {
// Wait for current scan
synchronized (this.repoUpdateLock) {}
}
}
private static final double STEP1 = 0.1D; private static final double STEP1 = 0.1D;
private static final double STEP2 = 0.8D; private static final double STEP2 = 0.8D;
private static final double STEP3 = 0.1D; private static final double STEP3 = 0.1D;
private boolean scanInternal(UpdateListener updateListener) { protected void scanInternal(@NonNull UpdateListener updateListener) {
this.modules.clear(); this.modules.clear();
updateListener.update(0D); updateListener.update(0D);
RepoData[] repoDatas = this.repoData.values().toArray(new RepoData[0]); // Using LinkedHashSet to deduplicate Androidacy entry.
RepoData[] repoDatas = new LinkedHashSet<>(
this.repoData.values()).toArray(new RepoData[0]);
RepoUpdater[] repoUpdaters = new RepoUpdater[repoDatas.length]; RepoUpdater[] repoUpdaters = new RepoUpdater[repoDatas.length];
int moduleToUpdate = 0; int moduleToUpdate = 0;
for (int i = 0; i < repoDatas.length; i++) { for (int i = 0; i < repoDatas.length; i++) {
@ -215,7 +182,7 @@ public final class RepoManager {
for (int i = 0; i < repoUpdaters.length; i++) { for (int i = 0; i < repoUpdaters.length; i++) {
List<RepoModule> repoModules = repoUpdaters[i].toUpdate(); List<RepoModule> repoModules = repoUpdaters[i].toUpdate();
RepoData repoData = repoDatas[i]; RepoData repoData = repoDatas[i];
Log.d(TAG, "Registering " + repoData.name); Log.d(TAG, "Registering " + repoData.getName());
for (RepoModule repoModule:repoModules) { for (RepoModule repoModule:repoModules) {
try { try {
if (repoModule.propUrl != null && if (repoModule.propUrl != null &&
@ -261,7 +228,7 @@ public final class RepoManager {
} }
Log.i(TAG, "Got " + this.modules.size() + " modules!"); Log.i(TAG, "Got " + this.modules.size() + " modules!");
updateListener.update(1D); updateListener.update(1D);
return hasInternet; this.repoLastResult = hasInternet;
} }
public void updateEnabledStates() { public void updateEnabledStates() {

@ -48,7 +48,7 @@ public class RepoUpdater {
// Return repo to update // Return repo to update
return this.toUpdate.size(); return this.toUpdate.size();
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "Failed to get manifest", e); Log.e(TAG, "Failed to get manifest of " + this.repoData.id, e);
this.indexRaw = null; this.indexRaw = null;
this.toUpdate = Collections.emptyList(); this.toUpdate = Collections.emptyList();
this.toApply = Collections.emptySet(); this.toApply = Collections.emptySet();

@ -380,10 +380,13 @@ public class PropUtils {
moduleId.substring(1).replace('_', ' '); moduleId.substring(1).replace('_', ' ');
} }
public static boolean isNullString(String string) {
return string == null || string.isEmpty() || "null".equals(string);
}
// Make versionName no longer than 16 charters to avoid UI overflow. // Make versionName no longer than 16 charters to avoid UI overflow.
public static String shortenVersionName(String versionName, long versionCode) { public static String shortenVersionName(String versionName, long versionCode) {
if (versionName == null || versionName.isEmpty() || if (isNullString(versionName)) return "v" + versionCode;
"null".equals(versionName)) return "v" + versionCode;
if (versionName.length() <= 16) return versionName; if (versionName.length() <= 16) return versionName;
int i = versionName.lastIndexOf('.'); int i = versionName.lastIndexOf('.');
if (i != -1 && i <= 16 && versionName.indexOf('.') != i if (i != -1 && i <= 16 && versionName.indexOf('.') != i

@ -0,0 +1,73 @@
package com.fox2code.mmm.utils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* Manager that want both to be thread safe and not to worry about thread safety
* {@link #scan()} and {@link #update(UpdateListener)} can be called from multiple
* thread at the same time, {@link #scanInternal(UpdateListener)} will only be
* called from one thread at a time only.
*/
public abstract class SyncManager {
private static final UpdateListener NO_OP = value -> {};
protected final Object syncLock = new Object();
private boolean syncing;
public final void scan() {
this.update(null);
}
// MultiThread friendly method
public final void update(@Nullable UpdateListener updateListener) {
if (updateListener == null) updateListener = NO_OP;
if (!this.syncing) {
// Do scan
synchronized (this.syncLock) {
this.syncing = true;
try {
this.scanInternal(updateListener);
} finally {
this.syncing = false;
}
}
} else {
// Wait for current scan
synchronized (this.syncLock) {
Thread.yield();
}
}
}
// Pause execution until the scan is completed if one is currently running
public final void afterScan() {
if (this.syncing) synchronized (this.syncLock) { Thread.yield(); }
}
public final void runAfterScan(Runnable runnable) {
synchronized (this.syncLock) {
runnable.run();
}
}
public final boolean isRepoUpdating() {
return this.syncing;
}
public final void afterUpdate() {
if (this.syncing) synchronized (this.syncLock) { Thread.yield(); }
}
public final void runAfterUpdate(Runnable runnable) {
synchronized (this.syncLock) {
runnable.run();
}
}
// This method can't be called twice at the same time.
protected abstract void scanInternal(@NonNull UpdateListener updateListener);
public interface UpdateListener {
void update(double value);
}
}
Loading…
Cancel
Save