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/ModuleViewListBuilder.java

245 lines
11 KiB
Java

package com.fox2code.mmm;
import android.app.Activity;
import android.os.Build;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.fox2code.mmm.installer.InstallerInitializer;
import com.fox2code.mmm.manager.ModuleInfo;
import com.fox2code.mmm.manager.ModuleManager;
import com.fox2code.mmm.repo.RepoManager;
import com.fox2code.mmm.repo.RepoModule;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
public class ModuleViewListBuilder {
private static final String TAG = "ModuleViewListBuilder";
private final EnumSet<NotificationType> notifications = EnumSet.noneOf(NotificationType.class);
private final HashMap<String, ModuleHolder> mappedModuleHolders = new HashMap<>();
private final Object updateLock = new Object();
private final Object queryLock = new Object();
private final Activity activity;
@NonNull
private String query = "";
private int footerPx;
private boolean noUpdate;
public ModuleViewListBuilder(Activity activity) {
this.activity = activity;
}
public void addNotification(NotificationType notificationType) {
synchronized (this.updateLock) {
this.notifications.add(notificationType);
}
}
public void appendInstalledModules() {
synchronized (this.updateLock) {
for (ModuleHolder moduleHolder : this.mappedModuleHolders.values()) {
moduleHolder.moduleInfo = null;
}
ModuleManager moduleManager = ModuleManager.getINSTANCE();
moduleManager.runAfterScan(() -> {
Log.i(TAG, "A1: " + moduleManager.getModules().size());
for (ModuleInfo moduleInfo : moduleManager.getModules().values()) {
ModuleHolder moduleHolder = this.mappedModuleHolders.get(moduleInfo.id);
if (moduleHolder == null) {
this.mappedModuleHolders.put(moduleInfo.id,
moduleHolder = new ModuleHolder(moduleInfo.id));
}
moduleHolder.moduleInfo = moduleInfo;
}
});
}
}
public void appendRemoteModules() {
synchronized (this.updateLock) {
boolean showIncompatible = MainApplication.isShowIncompatibleModules();
for (ModuleHolder moduleHolder : this.mappedModuleHolders.values()) {
moduleHolder.repoModule = null;
}
RepoManager repoManager = RepoManager.getINSTANCE();
repoManager.runAfterUpdate(() -> {
Log.i(TAG, "A2: " + repoManager.getModules().size());
for (RepoModule repoModule : repoManager.getModules().values()) {
if (!showIncompatible && (repoModule.moduleInfo.minApi > Build.VERSION.SDK_INT ||
// Only check Magisk compatibility if root is present
(InstallerInitializer.peekMagiskPath() != null &&
repoModule.moduleInfo.minMagisk >
InstallerInitializer.peekMagiskVersion()
)))
continue; // Skip adding incompatible modules
ModuleHolder moduleHolder = this.mappedModuleHolders.get(repoModule.id);
if (moduleHolder == null) {
this.mappedModuleHolders.put(repoModule.id,
moduleHolder = new ModuleHolder(repoModule.id));
}
moduleHolder.repoModule = repoModule;
}
});
}
}
public void applyTo(RecyclerView moduleList, ModuleViewAdapter moduleViewAdapter) {
if (this.noUpdate) return;
this.noUpdate = true;
final ArrayList<ModuleHolder> moduleHolders;
final int newNotificationsLen;
try {
synchronized (this.updateLock) {
// Build start
moduleHolders = new ArrayList<>();
int special = 0;
Iterator<NotificationType> notificationTypeIterator = this.notifications.iterator();
while (notificationTypeIterator.hasNext()) {
NotificationType notificationType = notificationTypeIterator.next();
if (notificationType.shouldRemove()) {
notificationTypeIterator.remove();
} else {
if (notificationType.special) special++;
moduleHolders.add(new ModuleHolder(notificationType));
}
}
newNotificationsLen = this.notifications.size() - special;
EnumSet<ModuleHolder.Type> headerTypes = EnumSet.of(
ModuleHolder.Type.NOTIFICATION, ModuleHolder.Type.SEPARATOR);
Iterator<ModuleHolder> moduleHolderIterator = this.mappedModuleHolders.values().iterator();
synchronized (this.queryLock) {
while (moduleHolderIterator.hasNext()) {
ModuleHolder moduleHolder = moduleHolderIterator.next();
if (moduleHolder.shouldRemove()) {
moduleHolderIterator.remove();
} else {
ModuleHolder.Type type = moduleHolder.getType();
if (matchFilter(moduleHolder)) {
if (headerTypes.add(type)) {
moduleHolders.add(new ModuleHolder(type));
}
moduleHolders.add(moduleHolder);
}
}
}
}
Collections.sort(moduleHolders, ModuleHolder::compareTo);
Log.i(TAG, "Got " + moduleHolders.size() + " entries!");
// Build end
}
} finally {
this.noUpdate = false;
}
this.activity.runOnUiThread(() -> {
final EnumSet<NotificationType> oldNotifications =
EnumSet.noneOf(NotificationType.class);
if (this.footerPx != 0) {
moduleHolders.add(new ModuleHolder(this.footerPx));
}
boolean isTop = !moduleList.canScrollVertically(-1);
boolean isBottom = !isTop && !moduleList.canScrollVertically(1);
int oldNotificationsLen = 0;
int oldOfflineModulesLen = 0;
for (ModuleHolder moduleHolder : moduleViewAdapter.moduleHolders) {
NotificationType notificationType = moduleHolder.notificationType;
if (notificationType != null) {
oldNotifications.add(notificationType);
if (!notificationType.special)
oldNotificationsLen++;
}
if (moduleHolder.separator == ModuleHolder.Type.INSTALLABLE)
break;
oldOfflineModulesLen++;
}
oldOfflineModulesLen -= oldNotificationsLen;
int newOfflineModulesLen = 0;
for (ModuleHolder moduleHolder : moduleHolders) {
if (moduleHolder.separator == ModuleHolder.Type.INSTALLABLE)
break;
newOfflineModulesLen++;
}
newOfflineModulesLen -= newNotificationsLen;
moduleViewAdapter.moduleHolders.size();
int newLen = moduleHolders.size();
int oldLen = moduleViewAdapter.moduleHolders.size();
moduleViewAdapter.moduleHolders.clear();
moduleViewAdapter.moduleHolders.addAll(moduleHolders);
if (oldNotificationsLen != newNotificationsLen ||
!oldNotifications.equals(this.notifications)) {
notifySizeChanged(moduleViewAdapter, 0,
oldNotificationsLen, newNotificationsLen);
}
if (newLen - newNotificationsLen == 0) {
notifySizeChanged(moduleViewAdapter, newNotificationsLen,
oldLen - oldNotificationsLen, 0);
} else {
notifySizeChanged(moduleViewAdapter, newNotificationsLen,
oldOfflineModulesLen, newOfflineModulesLen);
notifySizeChanged(moduleViewAdapter,
newNotificationsLen + newOfflineModulesLen,
oldLen - oldNotificationsLen - oldOfflineModulesLen,
newLen - newNotificationsLen - newOfflineModulesLen);
}
if (isTop) moduleList.scrollToPosition(0);
if (isBottom) moduleList.scrollToPosition(newLen);
});
}
public void setFooterPx(int footerPx) {
this.footerPx = Math.max(footerPx, 0);
}
private boolean matchFilter(ModuleHolder moduleHolder) {
if (this.query.isEmpty()) return true;
ModuleInfo moduleInfo = moduleHolder.getMainModuleInfo();
return moduleInfo.id.toLowerCase(Locale.ROOT).contains(this.query) ||
moduleInfo.name.toLowerCase(Locale.ROOT).contains(this.query);
}
private static void notifySizeChanged(ModuleViewAdapter moduleViewAdapter,
int index, int oldLen, int newLen) {
// Log.i(TAG, "A: " + index + " " + oldLen + " " + newLen);
if (oldLen == newLen) {
if (newLen != 0)
moduleViewAdapter.notifyItemRangeChanged(index, newLen);
} else if (oldLen < newLen) {
if (oldLen != 0)
moduleViewAdapter.notifyItemRangeChanged(index, oldLen);
moduleViewAdapter.notifyItemRangeInserted(
index + oldLen, newLen - oldLen);
} else {
if (newLen != 0)
moduleViewAdapter.notifyItemRangeChanged(index, newLen);
moduleViewAdapter.notifyItemRangeRemoved(
index + newLen, oldLen - newLen);
}
}
public void setQuery(String query) {
synchronized (this.queryLock) {
Log.i(TAG, "Query " + this.query + " -> " + query);
this.query = query == null ? "" :
query.trim().toLowerCase(Locale.ROOT);
}
}
public boolean setQueryChange(String query) {
synchronized (this.queryLock) {
String newQuery = query == null ? "" :
query.trim().toLowerCase(Locale.ROOT);
Log.i(TAG, "Query change " + this.query + " -> " + newQuery);
if (this.query.equals(newQuery))
return false;
this.query = newQuery;
}
return true;
}
}