diff --git a/app/build.gradle b/app/build.gradle index a93b0e7..92fe085 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -117,7 +117,7 @@ dependencies { implementation "dev.rikka.rikkax.insets:insets:1.3.0" implementation 'com.github.Dimezis:BlurView:version-1.6.6' implementation 'com.github.KieronQuinn:MonetCompat:0.4.1' - implementation 'com.github.Fox2Code:FoxCompat:0.1.3' + implementation 'com.github.Fox2Code:FoxCompat:0.1.4b' // Utils implementation 'androidx.work:work-runtime:2.7.1' diff --git a/app/src/main/java/com/fox2code/mmm/MainActivity.java b/app/src/main/java/com/fox2code/mmm/MainActivity.java index f58f47e..bbf6a75 100644 --- a/app/src/main/java/com/fox2code/mmm/MainActivity.java +++ b/app/src/main/java/com/fox2code/mmm/MainActivity.java @@ -34,6 +34,7 @@ import com.fox2code.mmm.settings.SettingsActivity; import com.fox2code.mmm.utils.ExternalHelper; import com.fox2code.mmm.utils.Http; import com.fox2code.mmm.utils.IntentHelper; +import com.fox2code.mmm.utils.NoodleDebug; import com.google.android.material.progressindicator.LinearProgressIndicator; import eightbitlab.com.blurview.BlurView; @@ -44,6 +45,7 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe OverScrollManager.OverScrollHelper { private static final String TAG = "MainActivity"; private static final int PRECISION = 10000; + public static boolean noodleDebugState = BuildConfig.DEBUG; public final ModuleViewListBuilder moduleViewListBuilder; public LinearProgressIndicator progressIndicator; private ModuleViewAdapter moduleViewAdapter; @@ -59,6 +61,7 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe private RecyclerView moduleList; private CardView searchCard; private SearchView searchView; + private NoodleDebug noodleDebug; private boolean initMode; public MainActivity() { @@ -106,6 +109,7 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe this.moduleList = findViewById(R.id.module_list); this.searchCard = findViewById(R.id.search_card); this.searchView = findViewById(R.id.search_bar); + this.noodleDebug = new NoodleDebug(this, R.id.noodle_debug); this.moduleViewAdapter = new ModuleViewAdapter(); this.moduleList.setAdapter(this.moduleViewAdapter); this.moduleList.setLayoutManager(new LinearLayoutManager(this)); @@ -152,6 +156,8 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe moduleViewListBuilder.addNotification(NotificationType.MAGISK_OUTDATED); if (!MainApplication.isShowcaseMode()) moduleViewListBuilder.addNotification(NotificationType.INSTALL_FROM_STORAGE); + noodleDebug.setEnabled(noodleDebugState); + noodleDebug.bind(); ModuleManager.getINSTANCE().scan(); ModuleManager.getINSTANCE().runAfterScan( moduleViewListBuilder::appendInstalledModules); @@ -161,18 +167,22 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe @Override public void onFailure(int error) { Log.i(TAG, "Failed to get magisk path!"); + noodleDebug.setEnabled(noodleDebugState); + noodleDebug.bind(); moduleViewListBuilder.addNotification( InstallerInitializer.getErrorNotification()); this.commonNext(); } public void commonNext() { + NoodleDebug noodleDebug = NoodleDebug.getNoodleDebug(); swipeRefreshBlocker = System.currentTimeMillis() + 5_000L; updateScreenInsets(); // Fix an edge case if (MainApplication.isShowcaseMode()) moduleViewListBuilder.addNotification(NotificationType.SHOWCASE_MODE); if (!Http.hasWebView()) // Check Http for WebView availability moduleViewListBuilder.addNotification(NotificationType.NO_WEB_VIEW); + noodleDebug.push("Apply"); moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter); runOnUiThread(() -> { progressIndicator.setIndeterminate(false); @@ -181,11 +191,14 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe updateScreenInsets(getResources().getConfiguration()); }); Log.i(TAG, "Scanning for modules!"); + noodleDebug.replace("Initialize Update"); final int max = ModuleManager.getINSTANCE().getUpdatableModuleCount(); if (RepoManager.getINSTANCE().getCustomRepoManager().needUpdate()) { Log.w(TAG, "Need update on create?"); } + noodleDebug.replace("Check Update Compat"); AppUpdateManager.getAppUpdateManager().checkUpdateCompat(); + noodleDebug.replace("Check Update"); RepoManager.getINSTANCE().update(value -> runOnUiThread(max == 0 ? () -> progressIndicator.setProgressCompat( (int) (value * PRECISION), true) :() -> @@ -196,13 +209,17 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe } else { // Compatibility data still needs to be updated AppUpdateManager appUpdateManager = AppUpdateManager.getAppUpdateManager(); + noodleDebug.replace("Check App Update"); if (BuildConfig.ENABLE_AUTO_UPDATER && appUpdateManager.checkUpdate(true)) moduleViewListBuilder.addNotification(NotificationType.UPDATE_AVAILABLE); + noodleDebug.replace("Check Json Update"); if (max != 0) { int current = 0; + noodleDebug.push(""); for (LocalModuleInfo localModuleInfo : ModuleManager.getINSTANCE().getModules().values()) { if (localModuleInfo.updateJson != null) { + noodleDebug.replace(localModuleInfo.id); try { localModuleInfo.checkModuleUpdate(); } catch (Exception e) { @@ -216,6 +233,7 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe + (PRECISION * 0.75F)), true)); } } + noodleDebug.pop(); } } runOnUiThread(() -> { @@ -225,10 +243,13 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe setActionBarBackground(null); updateScreenInsets(getResources().getConfiguration()); }); + noodleDebug.push("Apply"); RepoManager.getINSTANCE().runAfterUpdate( moduleViewListBuilder::appendRemoteModules); moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter); + noodleDebug.pop(); Log.i(TAG, "Finished app opening state!"); + noodleDebug.unbind(); } }, true); ExternalHelper.INSTANCE.refreshHelper(this); @@ -321,6 +342,8 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe moduleViewListBuilder.addNotification(NotificationType.MAGISK_OUTDATED); if (!MainApplication.isShowcaseMode()) moduleViewListBuilder.addNotification(NotificationType.INSTALL_FROM_STORAGE); + noodleDebug.setEnabled(noodleDebugState); + noodleDebug.bind(); ModuleManager.getINSTANCE().scan(); ModuleManager.getINSTANCE().runAfterScan( moduleViewListBuilder::appendInstalledModules); @@ -331,11 +354,14 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe public void onFailure(int error) { moduleViewListBuilder.addNotification( InstallerInitializer.getErrorNotification()); + noodleDebug.setEnabled(noodleDebugState); + noodleDebug.bind(); this.commonNext(); } public void commonNext() { Log.i(TAG, "Common Before"); + NoodleDebug noodleDebug = NoodleDebug.getNoodleDebug(); if (MainApplication.isShowcaseMode()) moduleViewListBuilder.addNotification(NotificationType.SHOWCASE_MODE); if (!NotificationType.NO_INTERNET.shouldRemove()) @@ -343,11 +369,13 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe else if (AppUpdateManager.getAppUpdateManager().checkUpdate(false)) moduleViewListBuilder.addNotification(NotificationType.UPDATE_AVAILABLE); RepoManager.getINSTANCE().updateEnabledStates(); + noodleDebug.push(""); if (RepoManager.getINSTANCE().getCustomRepoManager().needUpdate()) { runOnUiThread(() -> { progressIndicator.setIndeterminate(false); progressIndicator.setMax(PRECISION); }); + noodleDebug.replace("Check Update"); RepoManager.getINSTANCE().update(value -> runOnUiThread(() -> progressIndicator.setProgressCompat( (int) (value * PRECISION), true))); @@ -356,11 +384,14 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe progressIndicator.setVisibility(View.GONE); }); } + noodleDebug.replace("Apply"); RepoManager.getINSTANCE().runAfterUpdate( moduleViewListBuilder::appendRemoteModules); Log.i(TAG, "Common Before applyTo"); moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter); + noodleDebug.pop(); Log.i(TAG, "Common After"); + noodleDebug.unbind(); } }); this.initMode = false; @@ -384,14 +415,49 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe this.swipeRefreshBlocker = System.currentTimeMillis() + 5_000L; // this.swipeRefreshLayout.setRefreshing(true); ?? new Thread(() -> { + noodleDebug.setEnabled(noodleDebugState); + NoodleDebug noodleDebug = this.noodleDebug.bind(); Http.cleanDnsCache(); // Allow DNS reload from network - RepoManager.getINSTANCE().update(value -> runOnUiThread(() -> - this.progressIndicator.setProgressCompat( - (int) (value * PRECISION), true))); - if (!NotificationType.NO_INTERNET.shouldRemove()) + noodleDebug.push("Check Update"); + final int max = ModuleManager.getINSTANCE().getUpdatableModuleCount(); + RepoManager.getINSTANCE().update(value -> runOnUiThread(max == 0 ? () -> + progressIndicator.setProgressCompat( + (int) (value * PRECISION), true) :() -> + progressIndicator.setProgressCompat( + (int) (value * PRECISION * 0.75F), true))); + if (!NotificationType.NO_INTERNET.shouldRemove()) { moduleViewListBuilder.addNotification(NotificationType.NO_INTERNET); - else if (AppUpdateManager.getAppUpdateManager().checkUpdate(true)) - moduleViewListBuilder.addNotification(NotificationType.UPDATE_AVAILABLE); + } else { + // Compatibility data still needs to be updated + AppUpdateManager appUpdateManager = AppUpdateManager.getAppUpdateManager(); + noodleDebug.replace("Check App Update"); + if (BuildConfig.ENABLE_AUTO_UPDATER && appUpdateManager.checkUpdate(true)) + moduleViewListBuilder.addNotification(NotificationType.UPDATE_AVAILABLE); + noodleDebug.replace("Check Json Update"); + if (max != 0) { + int current = 0; + noodleDebug.push(""); + for (LocalModuleInfo localModuleInfo : + ModuleManager.getINSTANCE().getModules().values()) { + if (localModuleInfo.updateJson != null) { + noodleDebug.replace(localModuleInfo.id); + try { + localModuleInfo.checkModuleUpdate(); + } catch (Exception e) { + Log.e("MainActivity", "Failed to fetch update of: " + + localModuleInfo.id, e); + } + current++; + final int currentTmp = current; + runOnUiThread(() -> progressIndicator.setProgressCompat( + (int) ((1F * currentTmp / max) * PRECISION * 0.25F + + (PRECISION * 0.75F)), true)); + } + } + noodleDebug.pop(); + } + } + noodleDebug.replace("Apply"); runOnUiThread(() -> { this.progressIndicator.setVisibility(View.GONE); this.swipeRefreshLayout.setRefreshing(false); @@ -403,6 +469,8 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe RepoManager.getINSTANCE().runAfterUpdate( moduleViewListBuilder::appendRemoteModules); this.moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter); + noodleDebug.pop(); + noodleDebug.unbind(); },"Repo update thread").start(); } 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 a6a1547..6573682 100644 --- a/app/src/main/java/com/fox2code/mmm/manager/ModuleManager.java +++ b/app/src/main/java/com/fox2code/mmm/manager/ModuleManager.java @@ -8,6 +8,7 @@ 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.NoodleDebug; import com.fox2code.mmm.utils.PropUtils; import com.fox2code.mmm.utils.SyncManager; import com.topjohnwu.superuser.Shell; @@ -47,6 +48,8 @@ public final class ModuleManager extends SyncManager { } protected void scanInternal(@NonNull UpdateListener updateListener) { + NoodleDebug noodleDebug = NoodleDebug.getNoodleDebug(); + noodleDebug.push("Initialize scan"); boolean firstScan = this.bootPrefs.getBoolean("mm_first_scan", true); SharedPreferences.Editor editor = firstScan ? this.bootPrefs.edit() : null; for (ModuleInfo v : this.moduleInfos.values()) { @@ -67,10 +70,13 @@ public final class ModuleManager extends SyncManager { if (!FORCE_NEED_FALLBACK && needFallback) { Log.e(TAG, "Failed to detect modules folder, using fallback instead."); } + noodleDebug.replace("Scan"); if (modules != null) { + noodleDebug.push(""); for (String module : modules) { if (!new SuFile("/data/adb/modules/" + module).isDirectory()) continue; // Ignore non directory files inside modules folder + noodleDebug.replace(module); LocalModuleInfo moduleInfo = moduleInfos.get(module); if (moduleInfo == null) { moduleInfo = new LocalModuleInfo(module); @@ -112,10 +118,16 @@ public final class ModuleManager extends SyncManager { moduleInfo.flags |= FLAG_MM_INVALID; } } + noodleDebug.pop(); } + noodleDebug.replace("Scan update"); String[] modules_update = new SuFile("/data/adb/modules_update").list(); if (modules_update != null) { + noodleDebug.push(""); for (String module : modules_update) { + if (!new SuFile("/data/adb/modules_update/" + module).isDirectory()) + continue; // Ignore non directory files inside modules folder + noodleDebug.replace(module); LocalModuleInfo moduleInfo = moduleInfos.get(module); if (moduleInfo == null) { moduleInfo = new LocalModuleInfo(module); @@ -131,12 +143,16 @@ public final class ModuleManager extends SyncManager { moduleInfo.flags |= FLAG_MM_INVALID; } } + noodleDebug.pop(); } + noodleDebug.replace("Finalize scan"); this.updatableModuleCount = 0; Iterator moduleInfoIterator = this.moduleInfos.values().iterator(); + noodleDebug.push(""); while (moduleInfoIterator.hasNext()) { LocalModuleInfo moduleInfo = moduleInfoIterator.next(); + noodleDebug.replace(moduleInfo.id); if ((moduleInfo.flags & FLAG_MM_UNPROCESSED) != 0) { moduleInfoIterator.remove(); continue; // Don't process fallbacks if unreferenced @@ -158,10 +174,12 @@ public final class ModuleManager extends SyncManager { } moduleInfo.verify(); } + noodleDebug.pop(); if (firstScan) { editor.putBoolean("mm_first_scan", false); editor.apply(); } + noodleDebug.pop(); } public HashMap getModules() { 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 d5aed00..be55aca 100644 --- a/app/src/main/java/com/fox2code/mmm/repo/RepoManager.java +++ b/app/src/main/java/com/fox2code/mmm/repo/RepoManager.java @@ -13,6 +13,7 @@ import com.fox2code.mmm.manager.ModuleInfo; import com.fox2code.mmm.utils.Files; import com.fox2code.mmm.utils.Hashes; import com.fox2code.mmm.utils.Http; +import com.fox2code.mmm.utils.NoodleDebug; import com.fox2code.mmm.utils.PropUtils; import com.fox2code.mmm.utils.SyncManager; @@ -185,6 +186,8 @@ public final class RepoManager extends SyncManager { private static final double STEP3 = 0.1D; protected void scanInternal(@NonNull UpdateListener updateListener) { + NoodleDebug noodleDebug = NoodleDebug.getNoodleDebug(); + noodleDebug.push("Downloading indexes"); this.modules.clear(); updateListener.update(0D); // Using LinkedHashSet to deduplicate Androidacy entry. @@ -192,18 +195,26 @@ public final class RepoManager extends SyncManager { this.repoData.values()).toArray(new RepoData[0]); RepoUpdater[] repoUpdaters = new RepoUpdater[repoDatas.length]; int moduleToUpdate = 0; + noodleDebug.push(""); for (int i = 0; i < repoDatas.length; i++) { + noodleDebug.replace(repoDatas[i].getName()); moduleToUpdate += (repoUpdaters[i] = new RepoUpdater(repoDatas[i])).fetchIndex(); updateListener.update(STEP1 / repoDatas.length * (i + 1)); } + noodleDebug.pop(); + noodleDebug.replace("Updating meta-data"); int updatedModules = 0; boolean allowLowQualityModules = MainApplication.isDisableLowQualityModuleFilter(); + noodleDebug.push(""); for (int i = 0; i < repoUpdaters.length; i++) { List repoModules = repoUpdaters[i].toUpdate(); RepoData repoData = repoDatas[i]; + noodleDebug.replace(repoData.getName()); Log.d(TAG, "Registering " + repoData.getName()); + noodleDebug.push(""); for (RepoModule repoModule:repoModules) { + noodleDebug.replace(repoModule.id); try { if (repoModule.propUrl != null && !repoModule.propUrl.isEmpty()) { @@ -229,6 +240,7 @@ public final class RepoManager extends SyncManager { updatedModules++; updateListener.update(STEP1 + (STEP2 / moduleToUpdate * updatedModules)); } + noodleDebug.pop(); for (RepoModule repoModule:repoUpdaters[i].toApply()) { if ((repoModule.moduleInfo.flags & ModuleInfo.FLAG_METADATA_INVALID) == 0) { RepoModule registeredRepoModule = this.modules.get(repoModule.id); @@ -241,14 +253,20 @@ public final class RepoManager extends SyncManager { } } } + noodleDebug.pop(); + noodleDebug.replace("Finishing update"); + noodleDebug.push(""); boolean hasInternet = false; for (int i = 0; i < repoDatas.length; i++) { + noodleDebug.replace(repoUpdaters[i].repoData.getName()); hasInternet |= repoUpdaters[i].finish(); updateListener.update(STEP1 + STEP2 + (STEP3 / repoDatas.length * (i + 1))); } + noodleDebug.pop(); Log.i(TAG, "Got " + this.modules.size() + " modules!"); updateListener.update(1D); this.repoLastResult = hasInternet; + noodleDebug.pop(); // pop "Finishing update" } public void updateEnabledStates() { diff --git a/app/src/main/java/com/fox2code/mmm/utils/NoodleDebug.java b/app/src/main/java/com/fox2code/mmm/utils/NoodleDebug.java new file mode 100644 index 0000000..cdfe3ff --- /dev/null +++ b/app/src/main/java/com/fox2code/mmm/utils/NoodleDebug.java @@ -0,0 +1,163 @@ +package com.fox2code.mmm.utils; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.util.Log; +import android.widget.TextView; + +import androidx.annotation.IdRes; +import androidx.annotation.NonNull; + +import java.lang.ref.WeakReference; +import java.util.LinkedList; +import java.util.Objects; + +public class NoodleDebug { + private static final String TAG = "NoodleDebug"; + private static final WeakReference NULL_THREAD_REF = new WeakReference<>(null); + private static final ThreadLocal THREAD_NOODLE = new ThreadLocal<>(); + @SuppressLint("StaticFieldLeak") // <- Null initialized + private static final NoodleDebug NULL = new NoodleDebug() { + @Override + public void setEnabled(boolean enabled) {} + + @Override + protected void markDirty() {} + }; + private final Activity activity; + private final TextView textView; + private final LinkedList tokens; + private final StringBuilder debug; + private WeakReference thread; + private boolean enabled, updating; + + private NoodleDebug() { + this.activity = null; + this.textView = null; + this.tokens = new LinkedList<>(); + this.debug = new StringBuilder(0); + this.thread = NULL_THREAD_REF; + } + + public NoodleDebug(Activity activity,@IdRes int textViewId) { + this(activity, activity.findViewById(textViewId)); + } + + public NoodleDebug(Activity activity, TextView textView) { + this.activity = Objects.requireNonNull(activity); + this.textView = Objects.requireNonNull(textView); + this.tokens = new LinkedList<>(); + this.debug = new StringBuilder(64); + this.thread = NULL_THREAD_REF; + } + + public NoodleDebug bind() { + synchronized (this.tokens) { + Thread thread; + if ((thread = this.thread.get()) != null) { + Log.e(TAG, "Trying to bind to thread \"" + Thread.currentThread().getName() + + "\" while already bound to \"" + thread.getName() + "\""); + return NULL; + } + this.tokens.clear(); + } + if (this.enabled) { + this.thread = new WeakReference<>(Thread.currentThread()); + THREAD_NOODLE.set(this); + } else { + this.thread = null; + THREAD_NOODLE.remove(); + } + return this; + } + + public void unbind() { + this.thread = NULL_THREAD_REF; + boolean markDirty; + synchronized (this.tokens) { + markDirty = !this.tokens.isEmpty(); + this.tokens.clear(); + } + if (markDirty) this.markDirty(); + } + + public boolean isBound() { + return this.thread.get() != null; + } + + public void push(String token) { + if (!this.enabled) return; + synchronized (this.tokens) { + this.tokens.add(token); + } + if (!token.isEmpty()) + this.markDirty(); + } + + public void pop() { + if (!this.enabled) return; + String last; + synchronized (this.tokens) { + last = this.tokens.removeLast(); + } + if (!last.isEmpty()) + this.markDirty(); + } + + public void replace(String token) { + if (!this.enabled) return; + String last; + synchronized (this.tokens) { + last = this.tokens.removeLast(); + this.tokens.add(token); + } + if (!last.equals(token)) + this.markDirty(); + } + + public void setEnabled(boolean enabled) { + if (this.enabled && !enabled) { + this.thread = NULL_THREAD_REF; + synchronized (this.tokens) { + this.tokens.clear(); + } + this.markDirty(); + } + this.enabled = enabled; + } + + protected void markDirty() { + assert this.activity != null; + assert this.textView != null; + if (this.updating) return; + this.updating = true; + this.activity.runOnUiThread(() -> { + String debugText; + synchronized (this.tokens) { + StringBuilder debug = this.debug; + debug.setLength(0); + boolean first = true; + for (String text : this.tokens) { + if (text.isEmpty()) continue; + if (first) first = false; + else debug.append(" > "); + debug.append(text); + } + debugText = debug.toString(); + } + this.updating = false; + this.textView.setText(debugText); + }); + } + + @NonNull + public static NoodleDebug getNoodleDebug() { + NoodleDebug noodleDebug = THREAD_NOODLE.get(); + if (noodleDebug == null) return NULL; + if (noodleDebug.thread.get() != Thread.currentThread()) { + THREAD_NOODLE.remove(); + return NULL; + } + return noodleDebug; + } +} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 8da9a8c..b6d0647 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -51,6 +51,13 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/action_bar_blur" /> + +