diff --git a/app/build.gradle b/app/build.gradle index a85e10c..aa4c246 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,7 +1,6 @@ plugins { id 'com.android.application' id 'com.mikepenz.aboutlibraries.plugin' - id "io.sentry.android.gradle" version "3.1.4" } android { @@ -11,8 +10,8 @@ android { applicationId "com.fox2code.mmm" minSdk 21 targetSdk 33 - versionCode 54 - versionName "0.6.2" + versionCode 53 + versionName "0.6.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -35,11 +34,10 @@ android { "default" { dimension "type" buildConfigField "boolean", "ENABLE_AUTO_UPDATER", "true" - buildConfigField "boolean", "DEFAULT_ENABLE_CRASH_REPORTING", "true" buildConfigField( - "java.util.List", - "ENABLED_REPOS", - "java.util.Arrays.asList(\"magisk_alt_repo\", \"dg_magisk_repo\", \"androidacy_repo\")", + "java.util.List", + "ENABLED_REPOS", + "java.util.Arrays.asList(\"magisk_alt_repo\", \"androidacy_repo\")", ) } @@ -52,15 +50,12 @@ android { // with our keys, so the APK wouldn't install anyways). buildConfigField "boolean", "ENABLE_AUTO_UPDATER", "false" - // Respect privacy paranoiac nature of F-Droid builds - buildConfigField "boolean", "DEFAULT_ENABLE_CRASH_REPORTING", "false" - // Repo with ads or tracking feature are disabled by default for the // F-Droid flavor. buildConfigField( - "java.util.List", - "ENABLED_REPOS", - "java.util.Arrays.asList(\"magisk_alt_repo\")", + "java.util.List", + "ENABLED_REPOS", + "java.util.Arrays.asList(\"magisk_alt_repo\")", ) } } @@ -80,7 +75,7 @@ aboutLibraries { } configurations { - implementation.exclude group: 'org.jetbrains', module: 'annotations' + implementation.exclude group: 'org.jetbrains' , module: 'annotations' } dependencies { @@ -92,9 +87,8 @@ dependencies { implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' - implementation 'androidx.webkit:webkit:1.5.0' + implementation 'androidx.webkit:webkit:1.4.0' implementation 'com.google.android.material:material:1.6.1' - // Update root build.gradle instead. implementation "com.mikepenz:aboutlibraries:${latestAboutLibsRelease}" implementation "dev.rikka.rikkax.layoutinflater:layoutinflater:1.2.0" implementation "dev.rikka.rikkax.insets:insets:1.3.0" @@ -119,7 +113,7 @@ dependencies { implementation "com.caverock:androidsvg:1.4" // Test - testImplementation 'junit:junit:4.13.2' + testImplementation 'junit:junit:4.+' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 49205b5..d55947b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -21,6 +21,8 @@ + + repoModules = + RepoManager.getINSTANCE().getModules(); for (LocalModuleInfo localModuleInfo : ModuleManager.getINSTANCE().getModules().values()) { if ("twrp-keep".equals(localModuleInfo.id)) continue; - RepoModule repoModule = RepoManager.getINSTANCE() - .getModules().get(localModuleInfo.id); + RepoModule repoModule = repoModules.get(localModuleInfo.id); localModuleInfo.checkModuleUpdate(); - if (localModuleInfo.updateVersionCode > localModuleInfo.versionCode) { + if (localModuleInfo.updateVersionCode > localModuleInfo.versionCode && + !PropUtils.isNullString(localModuleInfo.updateVersion)) { moduleUpdateCount++; } else if (repoModule != null && - repoModule.moduleInfo.versionCode > localModuleInfo.versionCode) { + repoModule.moduleInfo.versionCode > localModuleInfo.versionCode && + !PropUtils.isNullString(repoModule.moduleInfo.version)) { moduleUpdateCount++; } } diff --git a/app/src/main/java/com/fox2code/mmm/manager/ModuleManager.java b/app/src/main/java/com/fox2code/mmm/manager/ModuleManager.java index a2483f3..4eccb72 100644 --- a/app/src/main/java/com/fox2code/mmm/manager/ModuleManager.java +++ b/app/src/main/java/com/fox2code/mmm/manager/ModuleManager.java @@ -3,9 +3,13 @@ package com.fox2code.mmm.manager; import android.content.SharedPreferences; import android.util.Log; +import androidx.annotation.NonNull; + import com.fox2code.mmm.MainApplication; import com.fox2code.mmm.installer.InstallerInitializer; +import com.fox2code.mmm.utils.Http; import com.fox2code.mmm.utils.PropUtils; +import com.fox2code.mmm.utils.SyncManager; import com.topjohnwu.superuser.Shell; import com.topjohnwu.superuser.io.SuFile; import com.topjohnwu.superuser.io.SuFileInputStream; @@ -17,7 +21,7 @@ import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Iterator; -public final class ModuleManager { +public final class ModuleManager extends SyncManager { private static final String TAG = "ModuleManager"; 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 final HashMap moduleInfos; private final SharedPreferences bootPrefs; - private final Object scanLock = new Object(); private int updatableModuleCount = 0; - private boolean scanning; private static final ModuleManager INSTANCE = new ModuleManager(); @@ -43,36 +45,7 @@ public final class ModuleManager { this.bootPrefs = MainApplication.getBootSharedPreferences(); } - // MultiThread friendly method - 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() { + protected void scanInternal(@NonNull UpdateListener updateListener) { boolean firstBoot = MainApplication.isFirstBoot(); boolean firstScan = this.bootPrefs.getBoolean("mm_first_scan", true); SharedPreferences.Editor editor = firstScan ? this.bootPrefs.edit() : null; diff --git a/app/src/main/java/com/fox2code/mmm/repo/RepoManager.java b/app/src/main/java/com/fox2code/mmm/repo/RepoManager.java index 26b85bd..4d9baca 100644 --- a/app/src/main/java/com/fox2code/mmm/repo/RepoManager.java +++ b/app/src/main/java/com/fox2code/mmm/repo/RepoManager.java @@ -4,6 +4,8 @@ import android.content.Context; import android.content.SharedPreferences; import android.util.Log; +import androidx.annotation.NonNull; + import com.fox2code.mmm.MainApplication; import com.fox2code.mmm.XHooks; 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.Http; import com.fox2code.mmm.utils.PropUtils; +import com.fox2code.mmm.utils.SyncManager; import java.io.File; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; -public final class RepoManager { +public final class RepoManager extends SyncManager { private static final String TAG = "RepoManager"; private static final String MAGISK_REPO_MANAGER = @@ -75,6 +79,7 @@ public final class RepoManager { private boolean initialized; private RepoManager(MainApplication mainApplication) { + INSTANCE = this; // Set early fox XHooks this.initialized = false; this.mainApplication = mainApplication; this.repoData = new LinkedHashMap<>(); @@ -90,6 +95,7 @@ public final class RepoManager { dgRepo.defaultWebsite = "https://dergoogler.com/repo"; this.androidacyRepoData = this.addAndroidacyRepoData(); this.customRepoManager = new CustomRepoManager(mainApplication, this); + XHooks.onRepoManagerInitialize(); // Populate default cache boolean x = false; for (RepoData repoData:this.repoData.values()) { @@ -136,7 +142,7 @@ public final class RepoManager { if (DG_MAGISK_REPO.equals(url)) url = DG_MAGISK_REPO_GITHUB; RepoData repoData; - synchronized (this.repoUpdateLock) { + synchronized (this.syncLock) { repoData = this.repoData.get(url); if (repoData == null) { if (ANDROIDACY_TEST_MAGISK_REPO_ENDPOINT.equals(url) || @@ -152,57 +158,18 @@ public final class RepoManager { return repoData; } - public interface UpdateListener { - void update(double value); - } - - private final Object repoUpdateLock = new Object(); - private boolean repoUpdating; 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 STEP2 = 0.8D; private static final double STEP3 = 0.1D; - private boolean scanInternal(UpdateListener updateListener) { + protected void scanInternal(@NonNull UpdateListener updateListener) { this.modules.clear(); 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]; int moduleToUpdate = 0; for (int i = 0; i < repoDatas.length; i++) { @@ -215,7 +182,7 @@ public final class RepoManager { for (int i = 0; i < repoUpdaters.length; i++) { List repoModules = repoUpdaters[i].toUpdate(); RepoData repoData = repoDatas[i]; - Log.d(TAG, "Registering " + repoData.name); + Log.d(TAG, "Registering " + repoData.getName()); for (RepoModule repoModule:repoModules) { try { if (repoModule.propUrl != null && @@ -261,7 +228,7 @@ public final class RepoManager { } Log.i(TAG, "Got " + this.modules.size() + " modules!"); updateListener.update(1D); - return hasInternet; + this.repoLastResult = hasInternet; } public void updateEnabledStates() { diff --git a/app/src/main/java/com/fox2code/mmm/repo/RepoUpdater.java b/app/src/main/java/com/fox2code/mmm/repo/RepoUpdater.java index cd5c301..e2614b9 100644 --- a/app/src/main/java/com/fox2code/mmm/repo/RepoUpdater.java +++ b/app/src/main/java/com/fox2code/mmm/repo/RepoUpdater.java @@ -48,7 +48,7 @@ public class RepoUpdater { // Return repo to update return this.toUpdate.size(); } 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.toUpdate = Collections.emptyList(); this.toApply = Collections.emptySet(); diff --git a/app/src/main/java/com/fox2code/mmm/utils/PropUtils.java b/app/src/main/java/com/fox2code/mmm/utils/PropUtils.java index 3403d94..246307c 100644 --- a/app/src/main/java/com/fox2code/mmm/utils/PropUtils.java +++ b/app/src/main/java/com/fox2code/mmm/utils/PropUtils.java @@ -380,10 +380,13 @@ public class PropUtils { 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. public static String shortenVersionName(String versionName, long versionCode) { - if (versionName == null || versionName.isEmpty() || - "null".equals(versionName)) return "v" + versionCode; + if (isNullString(versionName)) return "v" + versionCode; if (versionName.length() <= 16) return versionName; int i = versionName.lastIndexOf('.'); if (i != -1 && i <= 16 && versionName.indexOf('.') != i diff --git a/app/src/main/java/com/fox2code/mmm/utils/SyncManager.java b/app/src/main/java/com/fox2code/mmm/utils/SyncManager.java new file mode 100644 index 0000000..718286b --- /dev/null +++ b/app/src/main/java/com/fox2code/mmm/utils/SyncManager.java @@ -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); + } +}