[app] Optimize module and app list loading (#1401)
Co-authored-by: LoveSy <shana@zju.edu.cn>
This commit is contained in:
parent
806bdcf4fd
commit
4bf7c04ca7
|
|
@ -167,7 +167,7 @@ dependencies {
|
||||||
implementation("androidx.browser:browser:1.4.0")
|
implementation("androidx.browser:browser:1.4.0")
|
||||||
implementation("androidx.constraintlayout:constraintlayout:2.1.1")
|
implementation("androidx.constraintlayout:constraintlayout:2.1.1")
|
||||||
implementation("androidx.core:core:1.7.0")
|
implementation("androidx.core:core:1.7.0")
|
||||||
implementation("androidx.fragment:fragment:1.3.6")
|
implementation("androidx.fragment:fragment:1.4.0-rc01")
|
||||||
implementation("androidx.navigation:navigation-fragment:$navVersion")
|
implementation("androidx.navigation:navigation-fragment:$navVersion")
|
||||||
implementation("androidx.navigation:navigation-ui:$navVersion")
|
implementation("androidx.navigation:navigation-ui:$navVersion")
|
||||||
implementation("androidx.preference:preference:1.1.1")
|
implementation("androidx.preference:preference:1.1.1")
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.os.Looper;
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
import android.system.Os;
|
import android.system.Os;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
@ -36,6 +37,7 @@ import androidx.annotation.NonNull;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
import org.lsposed.hiddenapibypass.HiddenApiBypass;
|
import org.lsposed.hiddenapibypass.HiddenApiBypass;
|
||||||
|
import org.lsposed.manager.adapters.AppHelper;
|
||||||
import org.lsposed.manager.repo.RepoLoader;
|
import org.lsposed.manager.repo.RepoLoader;
|
||||||
import org.lsposed.manager.ui.activity.CrashReportActivity;
|
import org.lsposed.manager.ui.activity.CrashReportActivity;
|
||||||
import org.lsposed.manager.util.DoHDNS;
|
import org.lsposed.manager.util.DoHDNS;
|
||||||
|
|
@ -84,6 +86,33 @@ public class App extends Application {
|
||||||
// TODO: set specific class name
|
// TODO: set specific class name
|
||||||
HiddenApiBypass.addHiddenApiExemptions("");
|
HiddenApiBypass.addHiddenApiExemptions("");
|
||||||
}
|
}
|
||||||
|
Looper.myQueue().addIdleHandler(() -> {
|
||||||
|
if (App.getInstance() == null || App.getExecutorService() == null) return true;
|
||||||
|
App.getExecutorService().submit(() -> {
|
||||||
|
var list = AppHelper.getAppList(false);
|
||||||
|
var pm = App.getInstance().getPackageManager();
|
||||||
|
list.parallelStream().forEach(i -> AppHelper.getAppLabel(i, pm));
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
Looper.myQueue().addIdleHandler(() -> {
|
||||||
|
if (App.getInstance() == null || App.getExecutorService() == null) return true;
|
||||||
|
App.getExecutorService().submit(() -> {
|
||||||
|
AppHelper.getDenyList(false);
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
Looper.myQueue().addIdleHandler(() -> {
|
||||||
|
if (App.getInstance() == null || App.getExecutorService() == null) return true;
|
||||||
|
App.getExecutorService().submit((Runnable) ModuleUtil::getInstance);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
Looper.myQueue().addIdleHandler(() -> {
|
||||||
|
if (App.getInstance() == null || App.getExecutorService() == null) return true;
|
||||||
|
App.getExecutorService().submit((Runnable) RepoLoader::getInstance);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final String TAG = "LSPosedManager";
|
public static final String TAG = "LSPosedManager";
|
||||||
|
|
@ -91,7 +120,7 @@ public class App extends Application {
|
||||||
private static OkHttpClient okHttpClient;
|
private static OkHttpClient okHttpClient;
|
||||||
private static Cache okHttpCache;
|
private static Cache okHttpCache;
|
||||||
private SharedPreferences pref;
|
private SharedPreferences pref;
|
||||||
private ExecutorService executorService;
|
private final ExecutorService executorService = Executors.newCachedThreadPool();
|
||||||
|
|
||||||
public static App getInstance() {
|
public static App getInstance() {
|
||||||
return instance;
|
return instance;
|
||||||
|
|
@ -143,8 +172,6 @@ public class App extends Application {
|
||||||
|
|
||||||
instance = this;
|
instance = this;
|
||||||
|
|
||||||
executorService = Executors.newCachedThreadPool();
|
|
||||||
|
|
||||||
pref = PreferenceManager.getDefaultSharedPreferences(this);
|
pref = PreferenceManager.getDefaultSharedPreferences(this);
|
||||||
if ("CN".equals(Locale.getDefault().getCountry())) {
|
if ("CN".equals(Locale.getDefault().getCountry())) {
|
||||||
if (!pref.contains("doh")) {
|
if (!pref.contains("doh")) {
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import io.github.xposed.xposedservice.utils.ParceledListSlice;
|
import io.github.xposed.xposedservice.utils.ParceledListSlice;
|
||||||
|
|
||||||
|
|
@ -103,7 +104,7 @@ public class ConfigManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean setModuleScope(String packageName, HashSet<ScopeAdapter.ApplicationWithEquals> applications) {
|
public static boolean setModuleScope(String packageName, Set<ScopeAdapter.ApplicationWithEquals> applications) {
|
||||||
try {
|
try {
|
||||||
List<Application> list = new ArrayList<>();
|
List<Application> list = new ArrayList<>();
|
||||||
applications.forEach(application -> {
|
applications.forEach(application -> {
|
||||||
|
|
|
||||||
|
|
@ -26,14 +26,18 @@ import android.content.pm.ApplicationInfo;
|
||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.pm.ResolveInfo;
|
import android.content.pm.ResolveInfo;
|
||||||
|
import android.os.Looper;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
|
||||||
|
import org.lsposed.lspd.models.Application;
|
||||||
|
import org.lsposed.manager.App;
|
||||||
import org.lsposed.manager.ConfigManager;
|
import org.lsposed.manager.ConfigManager;
|
||||||
import org.lsposed.manager.R;
|
import org.lsposed.manager.R;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
public class AppHelper {
|
public class AppHelper {
|
||||||
|
|
||||||
|
|
@ -41,6 +45,7 @@ public class AppHelper {
|
||||||
public static final int FLAG_SHOW_FOR_ALL_USERS = 0x0400;
|
public static final int FLAG_SHOW_FOR_ALL_USERS = 0x0400;
|
||||||
private static List<String> denyList;
|
private static List<String> denyList;
|
||||||
private static List<PackageInfo> appList;
|
private static List<PackageInfo> appList;
|
||||||
|
private static final ConcurrentHashMap<PackageInfo, CharSequence> appLabel = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public static Intent getSettingsIntent(String packageName, int userId) {
|
public static Intent getSettingsIntent(String packageName, int userId) {
|
||||||
Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
|
Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
|
||||||
|
|
@ -132,17 +137,22 @@ public class AppHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<PackageInfo> getAppList(boolean force) {
|
synchronized public static List<PackageInfo> getAppList(boolean force) {
|
||||||
if (appList == null || force) {
|
if (appList == null || force) {
|
||||||
appList = ConfigManager.getInstalledPackagesFromAllUsers(PackageManager.GET_META_DATA | PackageManager.MATCH_UNINSTALLED_PACKAGES, true);
|
appList = ConfigManager.getInstalledPackagesFromAllUsers(PackageManager.GET_META_DATA | PackageManager.MATCH_UNINSTALLED_PACKAGES, true);
|
||||||
}
|
}
|
||||||
return appList;
|
return appList;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<String> getDenyList(boolean force) {
|
synchronized public static List<String> getDenyList(boolean force) {
|
||||||
if (denyList == null || force) {
|
if (denyList == null || force) {
|
||||||
denyList = ConfigManager.getDenyListPackages();
|
denyList = ConfigManager.getDenyListPackages();
|
||||||
}
|
}
|
||||||
return denyList;
|
return denyList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static CharSequence getAppLabel(PackageInfo info, PackageManager pm) {
|
||||||
|
if (info == null || info.applicationInfo == null) return null;
|
||||||
|
return appLabel.computeIfAbsent(info, i->i.applicationInfo.loadLabel(pm));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,25 +72,25 @@ import org.lsposed.manager.R;
|
||||||
import org.lsposed.manager.databinding.ItemModuleBinding;
|
import org.lsposed.manager.databinding.ItemModuleBinding;
|
||||||
import org.lsposed.manager.ui.fragment.AppListFragment;
|
import org.lsposed.manager.ui.fragment.AppListFragment;
|
||||||
import org.lsposed.manager.ui.fragment.CompileDialogFragment;
|
import org.lsposed.manager.ui.fragment.CompileDialogFragment;
|
||||||
|
import org.lsposed.manager.ui.widget.EmptyStateRecyclerView;
|
||||||
import org.lsposed.manager.util.GlideApp;
|
import org.lsposed.manager.util.GlideApp;
|
||||||
import org.lsposed.manager.util.ModuleUtil;
|
import org.lsposed.manager.util.ModuleUtil;
|
||||||
import org.lsposed.manager.util.SimpleStatefulAdaptor;
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import rikka.core.util.ResourceUtils;
|
import rikka.core.util.ResourceUtils;
|
||||||
import rikka.widget.switchbar.SwitchBar;
|
import rikka.widget.switchbar.SwitchBar;
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
public class ScopeAdapter extends SimpleStatefulAdaptor<ScopeAdapter.ViewHolder> implements Filterable {
|
public class ScopeAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<ScopeAdapter.ViewHolder> implements Filterable {
|
||||||
|
|
||||||
private final Activity activity;
|
private final Activity activity;
|
||||||
private final AppListFragment fragment;
|
private final AppListFragment fragment;
|
||||||
|
|
@ -102,11 +102,11 @@ public class ScopeAdapter extends SimpleStatefulAdaptor<ScopeAdapter.ViewHolder>
|
||||||
|
|
||||||
private final ModuleUtil.InstalledModule module;
|
private final ModuleUtil.InstalledModule module;
|
||||||
|
|
||||||
private final HashSet<ApplicationWithEquals> recommendedList = new HashSet<>();
|
private Set<ApplicationWithEquals> recommendedList = new HashSet<>();
|
||||||
private final HashSet<ApplicationWithEquals> checkedList = new HashSet<>();
|
private Set<ApplicationWithEquals> checkedList = new HashSet<>();
|
||||||
private final ConcurrentLinkedQueue<AppInfo> searchList = new ConcurrentLinkedQueue<>();
|
private List<AppInfo> searchList = new ArrayList<>();
|
||||||
private final List<AppInfo> showList = new ArrayList<>();
|
private List<AppInfo> showList = new ArrayList<>();
|
||||||
private final List<String> denyList = new ArrayList<>();
|
private List<String> denyList = new ArrayList<>();
|
||||||
|
|
||||||
private final SwitchBar.OnCheckedChangeListener switchBarOnCheckedChangeListener = new SwitchBar.OnCheckedChangeListener() {
|
private final SwitchBar.OnCheckedChangeListener switchBarOnCheckedChangeListener = new SwitchBar.OnCheckedChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -119,21 +119,6 @@ public class ScopeAdapter extends SimpleStatefulAdaptor<ScopeAdapter.ViewHolder>
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private final Runnable dataReadyRunnable = new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
synchronized (this) {
|
|
||||||
if (fragment == null || fragment.binding == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fragment.binding.progress.setIndeterminate(false);
|
|
||||||
fragment.binding.swipeRefreshLayout.setRefreshing(false);
|
|
||||||
String queryStr = fragment.searchView != null ? fragment.searchView.getQuery().toString() : "";
|
|
||||||
getFilter().filter(queryStr);
|
|
||||||
this.notify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private ApplicationInfo selectedInfo;
|
private ApplicationInfo selectedInfo;
|
||||||
private boolean refreshing = false;
|
private boolean refreshing = false;
|
||||||
|
|
@ -188,7 +173,7 @@ public class ScopeAdapter extends SimpleStatefulAdaptor<ScopeAdapter.ViewHolder>
|
||||||
return preferences.getBoolean("filter_system_apps", true) && (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
|
return preferences.getBoolean("filter_system_apps", true) && (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sortApps(List<AppInfo> list) {
|
private int sortApps(AppInfo x, AppInfo y) {
|
||||||
Comparator<PackageInfo> comparator = AppHelper.getAppListComparator(preferences.getInt("list_sort", 0), pm);
|
Comparator<PackageInfo> comparator = AppHelper.getAppListComparator(preferences.getInt("list_sort", 0), pm);
|
||||||
Comparator<AppInfo> frameworkComparator = (a, b) -> {
|
Comparator<AppInfo> frameworkComparator = (a, b) -> {
|
||||||
if (a.packageName.equals("android") == b.packageName.equals("android")) {
|
if (a.packageName.equals("android") == b.packageName.equals("android")) {
|
||||||
|
|
@ -210,23 +195,26 @@ public class ScopeAdapter extends SimpleStatefulAdaptor<ScopeAdapter.ViewHolder>
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
list.sort((a, b) -> {
|
boolean aChecked = checkedList.contains(x.application);
|
||||||
boolean aChecked = checkedList.contains(a.application);
|
boolean bChecked = checkedList.contains(y.application);
|
||||||
boolean bChecked = checkedList.contains(b.application);
|
if (aChecked == bChecked) {
|
||||||
if (aChecked == bChecked) {
|
return recommendedComparator.compare(x, y);
|
||||||
return recommendedComparator.compare(a, b);
|
} else if (aChecked) {
|
||||||
} else if (aChecked) {
|
return -1;
|
||||||
return -1;
|
} else {
|
||||||
} else {
|
return 1;
|
||||||
return 1;
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkRecommended() {
|
private void checkRecommended() {
|
||||||
checkedList.removeIf(i -> i.userId == module.userId);
|
fragment.runAsync(() -> {
|
||||||
checkedList.addAll(recommendedList);
|
var tmpChkList = new HashSet<>(checkedList);
|
||||||
ConfigManager.setModuleScope(module.packageName, checkedList);
|
tmpChkList.removeIf(i -> i.userId == module.userId);
|
||||||
|
tmpChkList.addAll(recommendedList);
|
||||||
|
ConfigManager.setModuleScope(module.packageName, tmpChkList);
|
||||||
|
checkedList = tmpChkList;
|
||||||
|
fragment.runOnUiThread(this::notifyDataSetChanged);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
|
@ -237,13 +225,11 @@ public class ScopeAdapter extends SimpleStatefulAdaptor<ScopeAdapter.ViewHolder>
|
||||||
.setMessage(R.string.use_recommended_message)
|
.setMessage(R.string.use_recommended_message)
|
||||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||||
checkRecommended();
|
checkRecommended();
|
||||||
notifyDataSetChanged();
|
|
||||||
})
|
})
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
.show();
|
.show();
|
||||||
} else {
|
} else {
|
||||||
checkRecommended();
|
checkRecommended();
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else if (itemId == R.id.item_filter_system) {
|
} else if (itemId == R.id.item_filter_system) {
|
||||||
|
|
@ -363,6 +349,12 @@ public class ScopeAdapter extends SimpleStatefulAdaptor<ScopeAdapter.ViewHolder>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewRecycled(@NonNull ViewHolder holder) {
|
||||||
|
holder.checkbox.setOnCheckedChangeListener(null);
|
||||||
|
super.onViewRecycled(holder);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||||
AppInfo appInfo = showList.get(position);
|
AppInfo appInfo = showList.get(position);
|
||||||
|
|
@ -444,10 +436,10 @@ public class ScopeAdapter extends SimpleStatefulAdaptor<ScopeAdapter.ViewHolder>
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
holder.checkbox.setOnCheckedChangeListener(null);
|
|
||||||
holder.checkbox.setChecked(checkedList.contains(appInfo.application));
|
holder.checkbox.setChecked(checkedList.contains(appInfo.application));
|
||||||
|
|
||||||
holder.checkbox.setOnCheckedChangeListener((v, isChecked) -> onCheckedChange(v, isChecked, appInfo));
|
holder.checkbox.setOnCheckedChangeListener((v, isChecked) -> onCheckedChange(v, isChecked, appInfo));
|
||||||
|
|
||||||
holder.itemView.setOnClickListener(v -> {
|
holder.itemView.setOnClickListener(v -> {
|
||||||
if (enabled) holder.checkbox.toggle();
|
if (enabled) holder.checkbox.toggle();
|
||||||
});
|
});
|
||||||
|
|
@ -495,82 +487,85 @@ public class ScopeAdapter extends SimpleStatefulAdaptor<ScopeAdapter.ViewHolder>
|
||||||
fragment.binding.masterSwitch.setOnCheckedChangeListener(switchBarOnCheckedChangeListener);
|
fragment.binding.masterSwitch.setOnCheckedChangeListener(switchBarOnCheckedChangeListener);
|
||||||
loadAppListHandler.post(() -> {
|
loadAppListHandler.post(() -> {
|
||||||
List<PackageInfo> appList = AppHelper.getAppList(force);
|
List<PackageInfo> appList = AppHelper.getAppList(force);
|
||||||
checkedList.clear();
|
denyList = AppHelper.getDenyList(force);
|
||||||
recommendedList.clear();
|
var tmpRecList = new HashSet<ApplicationWithEquals>();
|
||||||
denyList.clear();
|
var tmpChkList = new HashSet<>(ConfigManager.getModuleScope(module.packageName));
|
||||||
denyList.addAll(AppHelper.getDenyList(force));
|
final var tmpList = new ArrayList<AppInfo>();
|
||||||
var tmpList = new ArrayList<AppInfo>();
|
final HashSet<ApplicationWithEquals> installedList = new HashSet<>();
|
||||||
checkedList.addAll(ConfigManager.getModuleScope(module.packageName));
|
|
||||||
HashSet<ApplicationWithEquals> installedList = new HashSet<>();
|
|
||||||
List<String> scopeList = module.getScopeList();
|
List<String> scopeList = module.getScopeList();
|
||||||
boolean emptyCheckedList = checkedList.isEmpty();
|
boolean emptyCheckedList = tmpChkList.isEmpty();
|
||||||
for (PackageInfo info : appList) {
|
appList.parallelStream().forEach(info -> {
|
||||||
int userId = info.applicationInfo.uid / 100000;
|
int userId = info.applicationInfo.uid / 100000;
|
||||||
String packageName = info.packageName;
|
String packageName = info.packageName;
|
||||||
if (packageName.equals("android") && userId != 0 ||
|
if (packageName.equals("android") && userId != 0 ||
|
||||||
packageName.equals(module.packageName) ||
|
packageName.equals(module.packageName) ||
|
||||||
packageName.equals(BuildConfig.APPLICATION_ID)) {
|
packageName.equals(BuildConfig.APPLICATION_ID)) {
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplicationWithEquals application = new ApplicationWithEquals(packageName, userId);
|
ApplicationWithEquals application = new ApplicationWithEquals(packageName, userId);
|
||||||
|
|
||||||
installedList.add(application);
|
synchronized (installedList) {
|
||||||
|
installedList.add(application);
|
||||||
|
}
|
||||||
|
|
||||||
if (userId != module.userId) {
|
if (userId != module.userId) {
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scopeList != null && scopeList.contains(packageName)) {
|
if (scopeList != null && scopeList.contains(packageName)) {
|
||||||
recommendedList.add(application);
|
synchronized (tmpRecList) {
|
||||||
|
tmpRecList.add(application);
|
||||||
|
}
|
||||||
if (emptyCheckedList) {
|
if (emptyCheckedList) {
|
||||||
checkedList.add(application);
|
synchronized (tmpChkList) {
|
||||||
|
tmpChkList.add(application);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (shouldHideApp(info, application)) {
|
} else if (shouldHideApp(info, application)) {
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AppInfo appInfo = new AppInfo();
|
AppInfo appInfo = new AppInfo();
|
||||||
appInfo.packageInfo = info;
|
appInfo.packageInfo = info;
|
||||||
appInfo.label = info.applicationInfo.loadLabel(pm);
|
appInfo.label = AppHelper.getAppLabel(info, pm);
|
||||||
appInfo.application = application;
|
appInfo.application = application;
|
||||||
appInfo.packageName = info.packageName;
|
appInfo.packageName = info.packageName;
|
||||||
appInfo.applicationInfo = info.applicationInfo;
|
appInfo.applicationInfo = info.applicationInfo;
|
||||||
tmpList.add(appInfo);
|
synchronized (tmpList) {
|
||||||
}
|
tmpList.add(appInfo);
|
||||||
checkedList.retainAll(installedList);
|
}
|
||||||
|
});
|
||||||
|
tmpChkList.retainAll(installedList);
|
||||||
if (emptyCheckedList) {
|
if (emptyCheckedList) {
|
||||||
ConfigManager.setModuleScope(module.packageName, checkedList);
|
ConfigManager.setModuleScope(module.packageName, tmpChkList);
|
||||||
}
|
|
||||||
sortApps(tmpList);
|
|
||||||
searchList.clear();
|
|
||||||
searchList.addAll(tmpList);
|
|
||||||
synchronized (dataReadyRunnable) {
|
|
||||||
synchronized (this) {
|
|
||||||
refreshing = false;
|
|
||||||
}
|
|
||||||
activity.runOnUiThread(dataReadyRunnable);
|
|
||||||
try {
|
|
||||||
dataReadyRunnable.wait();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
checkedList = tmpChkList;
|
||||||
|
recommendedList = tmpRecList;
|
||||||
|
searchList = tmpList.parallelStream().sorted(this::sortApps).collect(Collectors.toList());
|
||||||
|
|
||||||
|
String queryStr = fragment.searchView != null ? fragment.searchView.getQuery().toString() : "";
|
||||||
|
|
||||||
|
getFilter().filter(queryStr, count -> {
|
||||||
|
refreshing = false;
|
||||||
|
fragment.runOnUiThread((this::notifyDataSetChanged));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onCheckedChange(CompoundButton buttonView, boolean isChecked, AppInfo appInfo) {
|
protected void onCheckedChange(CompoundButton buttonView, boolean isChecked, AppInfo appInfo) {
|
||||||
|
var tmpChkList = new HashSet<>(checkedList);
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
checkedList.add(appInfo.application);
|
tmpChkList.add(appInfo.application);
|
||||||
} else {
|
} else {
|
||||||
checkedList.remove(appInfo.application);
|
tmpChkList.remove(appInfo.application);
|
||||||
}
|
}
|
||||||
if (!ConfigManager.setModuleScope(module.packageName, checkedList)) {
|
if (!ConfigManager.setModuleScope(module.packageName, tmpChkList)) {
|
||||||
Snackbar.make(fragment.binding.snackbar, R.string.failed_to_save_scope_list, Snackbar.LENGTH_SHORT).show();
|
Snackbar.make(fragment.binding.snackbar, R.string.failed_to_save_scope_list, Snackbar.LENGTH_SHORT).show();
|
||||||
if (!isChecked) {
|
if (!isChecked) {
|
||||||
checkedList.add(appInfo.application);
|
tmpChkList.add(appInfo.application);
|
||||||
} else {
|
} else {
|
||||||
checkedList.remove(appInfo.application);
|
tmpChkList.remove(appInfo.application);
|
||||||
}
|
}
|
||||||
buttonView.setChecked(!isChecked);
|
buttonView.setChecked(!isChecked);
|
||||||
} else if (appInfo.packageName.equals("android")) {
|
} else if (appInfo.packageName.equals("android")) {
|
||||||
|
|
@ -581,6 +576,12 @@ public class ScopeAdapter extends SimpleStatefulAdaptor<ScopeAdapter.ViewHolder>
|
||||||
Snackbar.make(fragment.binding.snackbar, activity.getString(R.string.deny_list, appInfo.label), Snackbar.LENGTH_SHORT)
|
Snackbar.make(fragment.binding.snackbar, activity.getString(R.string.deny_list, appInfo.label), Snackbar.LENGTH_SHORT)
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
checkedList = tmpChkList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLoaded() {
|
||||||
|
return !refreshing;
|
||||||
}
|
}
|
||||||
|
|
||||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
@ -631,10 +632,8 @@ public class ScopeAdapter extends SimpleStatefulAdaptor<ScopeAdapter.ViewHolder>
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void publishResults(CharSequence constraint, FilterResults results) {
|
protected void publishResults(CharSequence constraint, FilterResults results) {
|
||||||
showList.clear();
|
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
showList.addAll((Collection<AppInfo>) results.values);
|
showList = (List<AppInfo>) results.values;
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -662,7 +661,6 @@ public class ScopeAdapter extends SimpleStatefulAdaptor<ScopeAdapter.ViewHolder>
|
||||||
if (!recommendedList.isEmpty()) {
|
if (!recommendedList.isEmpty()) {
|
||||||
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||||
checkRecommended();
|
checkRecommended();
|
||||||
notifyDataSetChanged();
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
builder.setPositiveButton(android.R.string.cancel, null);
|
builder.setPositiveButton(android.R.string.cancel, null);
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.widget.SearchView;
|
import androidx.appcompat.widget.SearchView;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.google.android.material.snackbar.Snackbar;
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
|
|
||||||
|
|
@ -78,6 +79,15 @@ public class AppListFragment extends BaseFragment {
|
||||||
|
|
||||||
scopeAdapter = new ScopeAdapter(this, module);
|
scopeAdapter = new ScopeAdapter(this, module);
|
||||||
scopeAdapter.setHasStableIds(true);
|
scopeAdapter.setHasStableIds(true);
|
||||||
|
scopeAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
|
||||||
|
@Override
|
||||||
|
public void onChanged() {
|
||||||
|
if (binding != null && scopeAdapter != null) {
|
||||||
|
binding.progress.setVisibility(scopeAdapter.isLoaded() ? View.GONE : View.VISIBLE);
|
||||||
|
binding.swipeRefreshLayout.setRefreshing(!scopeAdapter.isLoaded());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
binding.recyclerView.setAdapter(scopeAdapter);
|
binding.recyclerView.setAdapter(scopeAdapter);
|
||||||
binding.recyclerView.setHasFixedSize(true);
|
binding.recyclerView.setHasFixedSize(true);
|
||||||
binding.recyclerView.setLayoutManager(new LinearLayoutManager(requireActivity()));
|
binding.recyclerView.setLayoutManager(new LinearLayoutManager(requireActivity()));
|
||||||
|
|
|
||||||
|
|
@ -56,11 +56,12 @@ import java.util.Locale;
|
||||||
|
|
||||||
import rikka.core.util.ResourceUtils;
|
import rikka.core.util.ResourceUtils;
|
||||||
|
|
||||||
public class HomeFragment extends BaseFragment implements RepoLoader.Listener {
|
public class HomeFragment extends BaseFragment implements RepoLoader.Listener, ModuleUtil.ModuleListener {
|
||||||
|
|
||||||
private FragmentHomeBinding binding;
|
private FragmentHomeBinding binding;
|
||||||
|
|
||||||
private static final RepoLoader repoLoader = RepoLoader.getInstance();
|
private static final RepoLoader repoLoader = RepoLoader.getInstance();
|
||||||
|
private static final ModuleUtil moduleUtil = ModuleUtil.getInstance();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
|
@ -109,6 +110,7 @@ public class HomeFragment extends BaseFragment implements RepoLoader.Listener {
|
||||||
updateStates(requireActivity(), ConfigManager.isBinderAlive(), UpdateUtil.needUpdate());
|
updateStates(requireActivity(), ConfigManager.isBinderAlive(), UpdateUtil.needUpdate());
|
||||||
|
|
||||||
repoLoader.addListener(this);
|
repoLoader.addListener(this);
|
||||||
|
moduleUtil.addListener(this);
|
||||||
if (repoLoader.isRepoLoaded()) {
|
if (repoLoader.isRepoLoaded()) {
|
||||||
repoLoaded();
|
repoLoaded();
|
||||||
}
|
}
|
||||||
|
|
@ -198,7 +200,9 @@ public class HomeFragment extends BaseFragment implements RepoLoader.Listener {
|
||||||
public void repoLoaded() {
|
public void repoLoaded() {
|
||||||
final int[] count = new int[]{0};
|
final int[] count = new int[]{0};
|
||||||
HashSet<String> processedModules = new HashSet<>();
|
HashSet<String> processedModules = new HashSet<>();
|
||||||
ModuleUtil.getInstance().getModules().forEach((k, v) -> {
|
var modules = moduleUtil.getModules();
|
||||||
|
if (modules == null) return;
|
||||||
|
modules.forEach((k, v) -> {
|
||||||
if (!processedModules.contains(k.first)) {
|
if (!processedModules.contains(k.first)) {
|
||||||
var ver = repoLoader.getModuleLatestVersion(k.first);
|
var ver = repoLoader.getModuleLatestVersion(k.first);
|
||||||
if (ver != null && ver.upgradable(v.versionCode, v.versionName)) {
|
if (ver != null && ver.upgradable(v.versionCode, v.versionName)) {
|
||||||
|
|
@ -222,6 +226,11 @@ public class HomeFragment extends BaseFragment implements RepoLoader.Listener {
|
||||||
runOnUiThread(() -> binding.downloadSummary.setText(getResources().getString(R.string.module_repo_up_to_date)));
|
runOnUiThread(() -> binding.downloadSummary.setText(getResources().getString(R.string.module_repo_up_to_date)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onModulesReloaded() {
|
||||||
|
setModulesSummary(moduleUtil.getEnabledModulesCount());
|
||||||
|
}
|
||||||
|
|
||||||
private class StartFragmentListener implements View.OnClickListener {
|
private class StartFragmentListener implements View.OnClickListener {
|
||||||
boolean requireInstalled;
|
boolean requireInstalled;
|
||||||
int fragment;
|
int fragment;
|
||||||
|
|
@ -244,19 +253,20 @@ public class HomeFragment extends BaseFragment implements RepoLoader.Listener {
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
int moduleCount;
|
|
||||||
if (ConfigManager.isBinderAlive()) {
|
if (ConfigManager.isBinderAlive()) {
|
||||||
moduleCount = ModuleUtil.getInstance().getEnabledModulesCount();
|
setModulesSummary(moduleUtil.getEnabledModulesCount());
|
||||||
} else {
|
} else setModulesSummary(0);
|
||||||
moduleCount = 0;
|
}
|
||||||
}
|
|
||||||
binding.modulesSummary.setText(getResources().getQuantityString(R.plurals.modules_enabled_count, moduleCount, moduleCount));
|
private void setModulesSummary(int moduleCount) {
|
||||||
|
runOnUiThread(() -> binding.modulesSummary.setText(getResources().getQuantityString(R.plurals.modules_enabled_count, moduleCount, moduleCount)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
repoLoader.removeListener(this);
|
repoLoader.removeListener(this);
|
||||||
|
moduleUtil.removeListener(this);
|
||||||
binding = null;
|
binding = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
package org.lsposed.manager.ui.fragment;
|
package org.lsposed.manager.ui.fragment;
|
||||||
|
|
||||||
import static android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS;
|
import static android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS;
|
||||||
|
import static androidx.recyclerview.widget.RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
|
@ -82,26 +83,31 @@ import org.lsposed.manager.util.ModuleUtil;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Predicate;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
import rikka.core.util.ResourceUtils;
|
import rikka.core.util.ResourceUtils;
|
||||||
import rikka.recyclerview.RecyclerViewKt;
|
import rikka.recyclerview.RecyclerViewKt;
|
||||||
import rikka.widget.borderview.BorderRecyclerView;
|
|
||||||
|
|
||||||
public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleListener {
|
public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleListener {
|
||||||
private static final PackageManager pm = App.getInstance().getPackageManager();
|
private static final PackageManager pm = App.getInstance().getPackageManager();
|
||||||
private static final ModuleUtil moduleUtil = ModuleUtil.getInstance();
|
private static final ModuleUtil moduleUtil = ModuleUtil.getInstance();
|
||||||
private static final RepoLoader repoLoader = RepoLoader.getInstance();
|
private static final RepoLoader repoLoader = RepoLoader.getInstance();
|
||||||
|
private static final List<UserInfo> users = ConfigManager.getUsers();
|
||||||
protected FragmentPagerBinding binding;
|
protected FragmentPagerBinding binding;
|
||||||
protected SearchView searchView;
|
protected SearchView searchView;
|
||||||
private SearchView.OnQueryTextListener searchListener;
|
private SearchView.OnQueryTextListener searchListener;
|
||||||
|
|
||||||
private final ArrayList<ModuleAdapter> adapters = new ArrayList<>();
|
private final ArrayList<ModuleAdapter> adapters = new ArrayList<>();
|
||||||
private final ArrayList<String> tabTitles = new ArrayList<>();
|
|
||||||
|
private final RecyclerView.AdapterDataObserver observer = new RecyclerView.AdapterDataObserver() {
|
||||||
|
@Override
|
||||||
|
public void onChanged() {
|
||||||
|
updateProgress();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private ModuleUtil.InstalledModule selectedModule;
|
private ModuleUtil.InstalledModule selectedModule;
|
||||||
|
|
||||||
|
|
@ -121,6 +127,23 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (users != null) {
|
||||||
|
for (var user : users) {
|
||||||
|
var adapter = new ModuleAdapter(user);
|
||||||
|
adapter.setHasStableIds(true);
|
||||||
|
adapter.setStateRestorationPolicy(PREVENT_WHEN_EMPTY);
|
||||||
|
adapters.add(adapter);
|
||||||
|
adapter.registerAdapterDataObserver(observer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateProgress() {
|
||||||
|
if (binding != null) {
|
||||||
|
var position = binding.viewPager.getCurrentItem();
|
||||||
|
binding.progress.setVisibility(adapters.get(position).isLoaded ? View.GONE : View.VISIBLE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -133,71 +156,54 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
binding = FragmentPagerBinding.inflate(inflater, container, false);
|
binding = FragmentPagerBinding.inflate(inflater, container, false);
|
||||||
|
|
||||||
setupToolbar(binding.toolbar, R.string.Modules, R.menu.menu_modules);
|
setupToolbar(binding.toolbar, R.string.Modules, R.menu.menu_modules);
|
||||||
binding.viewPager.setAdapter(new PagerAdapter(this));
|
binding.viewPager.setAdapter(new PagerAdapter(this));
|
||||||
binding.viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
|
binding.viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void onPageSelected(int position) {
|
public void onPageSelected(int position) {
|
||||||
BorderRecyclerView recyclerView = binding.viewPager.findViewWithTag(position);
|
updateProgress();
|
||||||
|
|
||||||
if (position > 0) {
|
|
||||||
binding.fab.show();
|
|
||||||
} else {
|
|
||||||
binding.fab.hide();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var users = ConfigManager.getUsers();
|
new TabLayoutMediator(binding.tabLayout, binding.viewPager, (tab, position) -> {
|
||||||
if (users != null) {
|
if (position < adapters.size()) {
|
||||||
adapters.clear();
|
tab.setText(adapters.get(position).getUser().name);
|
||||||
if (users.size() != 1) {
|
|
||||||
tabTitles.clear();
|
|
||||||
for (var user : users) {
|
|
||||||
var adapter = new ModuleAdapter(user);
|
|
||||||
adapter.setHasStableIds(true);
|
|
||||||
adapters.add(adapter);
|
|
||||||
tabTitles.add(user.name);
|
|
||||||
}
|
|
||||||
new TabLayoutMediator(binding.tabLayout, binding.viewPager, (tab, position) -> {
|
|
||||||
if (position < tabTitles.size()) {
|
|
||||||
tab.setText(tabTitles.get(position));
|
|
||||||
}
|
|
||||||
}).attach();
|
|
||||||
binding.viewPager.setUserInputEnabled(true);
|
|
||||||
binding.tabLayout.setVisibility(View.VISIBLE);
|
|
||||||
} else {
|
|
||||||
var adapter = new ModuleAdapter(null);
|
|
||||||
adapter.setHasStableIds(true);
|
|
||||||
adapters.add(adapter);
|
|
||||||
binding.viewPager.setUserInputEnabled(false);
|
|
||||||
binding.tabLayout.setVisibility(View.GONE);
|
|
||||||
}
|
}
|
||||||
|
}).attach();
|
||||||
|
|
||||||
|
if (users != null && users.size() != 1) {
|
||||||
|
binding.viewPager.setUserInputEnabled(true);
|
||||||
|
binding.tabLayout.setVisibility(View.VISIBLE);
|
||||||
|
binding.tabLayout.addOnLayoutChangeListener((view, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
|
||||||
|
ViewGroup vg = (ViewGroup) binding.tabLayout.getChildAt(0);
|
||||||
|
int tabLayoutWidth = IntStream.range(0, binding.tabLayout.getTabCount()).map(i -> vg.getChildAt(i).getWidth()).sum();
|
||||||
|
if (tabLayoutWidth <= binding.getRoot().getWidth()) {
|
||||||
|
binding.tabLayout.setTabMode(TabLayout.MODE_FIXED);
|
||||||
|
binding.tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
binding.fab.show();
|
||||||
|
} else {
|
||||||
|
binding.viewPager.setUserInputEnabled(false);
|
||||||
|
binding.tabLayout.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.tabLayout.addOnLayoutChangeListener((view, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
|
|
||||||
ViewGroup vg = (ViewGroup) binding.tabLayout.getChildAt(0);
|
|
||||||
int tabLayoutWidth = IntStream.range(0, binding.tabLayout.getTabCount()).map(i -> vg.getChildAt(i).getWidth()).sum();
|
|
||||||
if (tabLayoutWidth <= binding.getRoot().getWidth()) {
|
|
||||||
binding.tabLayout.setTabMode(TabLayout.MODE_FIXED);
|
|
||||||
binding.tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
binding.fab.setOnClickListener(v -> {
|
binding.fab.setOnClickListener(v -> {
|
||||||
var pickAdaptor = new ModuleAdapter(null, true);
|
var pickAdaptor = new ModuleAdapter(adapters.get(binding.viewPager.getCurrentItem()).getUser(), true);
|
||||||
var position = binding.viewPager.getCurrentItem();
|
var position = binding.viewPager.getCurrentItem();
|
||||||
var snapshot = adapters.get(position).snapshot().stream().map(m -> m.packageName).collect(Collectors.toSet());
|
|
||||||
var user = adapters.get(position).getUser();
|
var user = adapters.get(position).getUser();
|
||||||
pickAdaptor.setFilter(m -> !snapshot.contains(m.packageName));
|
var binding = DialogRecyclerviewBinding.inflate(getLayoutInflater());
|
||||||
|
binding.list.setAdapter(pickAdaptor);
|
||||||
|
binding.list.setLayoutManager(new LinearLayoutManager(requireActivity()));
|
||||||
|
pickAdaptor.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
|
||||||
|
@Override
|
||||||
|
public void onChanged() {
|
||||||
|
binding.progress.setVisibility(pickAdaptor.isLoaded() ? View.GONE : View.VISIBLE);
|
||||||
|
}
|
||||||
|
});
|
||||||
pickAdaptor.refresh();
|
pickAdaptor.refresh();
|
||||||
var rv = DialogRecyclerviewBinding.inflate(getLayoutInflater()).getRoot();
|
|
||||||
rv.setAdapter(pickAdaptor);
|
|
||||||
rv.setLayoutManager(new LinearLayoutManager(requireActivity()));
|
|
||||||
var dialog = new MaterialAlertDialogBuilder(requireActivity())
|
var dialog = new MaterialAlertDialogBuilder(requireActivity())
|
||||||
.setTitle(getString(R.string.install_to_user, user.name))
|
.setTitle(getString(R.string.install_to_user, user.name))
|
||||||
.setView(rv)
|
.setView(binding.getRoot())
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
.show();
|
.show();
|
||||||
pickAdaptor.setOnPickListener(picked -> {
|
pickAdaptor.setOnPickListener(picked -> {
|
||||||
|
|
@ -229,8 +235,13 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSingleInstalledModuleReloaded() {
|
public void onSingleInstalledModuleReloaded(ModuleUtil.InstalledModule module) {
|
||||||
adapters.forEach(adapter -> adapter.refresh(true));
|
adapters.forEach(ModuleAdapter::refresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onModulesReloaded() {
|
||||||
|
adapters.forEach(ModuleAdapter::refresh);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -341,16 +352,18 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
||||||
binding.recyclerView.setAdapter(fragment.adapters.get(position));
|
binding.recyclerView.setAdapter(fragment.adapters.get(position));
|
||||||
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(requireActivity());
|
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(requireActivity());
|
||||||
binding.recyclerView.setLayoutManager(layoutManager);
|
binding.recyclerView.setLayoutManager(layoutManager);
|
||||||
binding.recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
if (users != null && users.size() != 1) {
|
||||||
@Override
|
binding.recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||||
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
|
@Override
|
||||||
if (newState == RecyclerView.SCROLL_STATE_IDLE && position > 0) {
|
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
|
||||||
fragment.binding.fab.show();
|
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
|
||||||
} else {
|
fragment.binding.fab.show();
|
||||||
fragment.binding.fab.hide();
|
} else {
|
||||||
|
fragment.binding.fab.hide();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
RecyclerViewKt.fixEdgeEffect(binding.recyclerView, false, true);
|
RecyclerViewKt.fixEdgeEffect(binding.recyclerView, false, true);
|
||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
@ -384,15 +397,13 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ModuleAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<ModuleAdapter.ViewHolder> implements Filterable {
|
private class ModuleAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<ModuleAdapter.ViewHolder> implements Filterable {
|
||||||
private final ConcurrentLinkedQueue<ModuleUtil.InstalledModule> searchList = new ConcurrentLinkedQueue<>();
|
private List<ModuleUtil.InstalledModule> searchList = new ArrayList<>();
|
||||||
private final List<ModuleUtil.InstalledModule> showList = new ArrayList<>();
|
private List<ModuleUtil.InstalledModule> showList = new ArrayList<>();
|
||||||
private final UserInfo user;
|
private final UserInfo user;
|
||||||
private final boolean isPick;
|
private final boolean isPick;
|
||||||
private boolean isLoaded;
|
private boolean isLoaded;
|
||||||
private View.OnClickListener onPickListener;
|
private View.OnClickListener onPickListener;
|
||||||
|
|
||||||
private Predicate<ModuleUtil.InstalledModule> customFilter = m -> true;
|
|
||||||
|
|
||||||
ModuleAdapter(UserInfo user) {
|
ModuleAdapter(UserInfo user) {
|
||||||
this(user, false);
|
this(user, false);
|
||||||
}
|
}
|
||||||
|
|
@ -412,6 +423,10 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
||||||
return new ModuleAdapter.ViewHolder(ItemModuleBinding.inflate(getLayoutInflater(), parent, false));
|
return new ModuleAdapter.ViewHolder(ItemModuleBinding.inflate(getLayoutInflater(), parent, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isPick() {
|
||||||
|
return isPick;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull ModuleAdapter.ViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull ModuleAdapter.ViewHolder holder, int position) {
|
||||||
ModuleUtil.InstalledModule item = showList.get(position);
|
ModuleUtil.InstalledModule item = showList.get(position);
|
||||||
|
|
@ -539,6 +554,12 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewRecycled(@NonNull ViewHolder holder) {
|
||||||
|
holder.itemView.setTag(null);
|
||||||
|
super.onViewRecycled(holder);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() {
|
public int getItemCount() {
|
||||||
return showList.size();
|
return showList.size();
|
||||||
|
|
@ -555,49 +576,71 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
||||||
return new ModuleAdapter.ApplicationFilter();
|
return new ModuleAdapter.ApplicationFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFilter(@NonNull Predicate<ModuleUtil.InstalledModule> filter) {
|
|
||||||
this.customFilter = filter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOnPickListener(View.OnClickListener onPickListener) {
|
public void setOnPickListener(View.OnClickListener onPickListener) {
|
||||||
this.onPickListener = onPickListener;
|
this.onPickListener = onPickListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ModuleUtil.InstalledModule> snapshot() {
|
|
||||||
return new ArrayList<>(searchList);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void refresh() {
|
public void refresh() {
|
||||||
refresh(false);
|
refresh(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void refresh(boolean force) {
|
public void refresh(boolean force) {
|
||||||
if (force) moduleUtil.reloadInstalledModules();
|
if (force) runAsync(moduleUtil::reloadInstalledModules);
|
||||||
runOnUiThread(reloadModules);
|
runAsync(reloadModules);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Runnable reloadModules = new Runnable() {
|
private final Runnable reloadModules = () -> {
|
||||||
public void run() {
|
var modules = moduleUtil.getModules();
|
||||||
var tmpList = moduleUtil.getModules().values().stream().filter(module -> user == null ? module.userId == 0 : module.userId == user.id).filter(customFilter).collect(Collectors.toCollection(ArrayList::new));
|
if (modules == null) return;
|
||||||
Comparator<PackageInfo> cmp = AppHelper.getAppListComparator(0, pm);
|
Comparator<PackageInfo> cmp = AppHelper.getAppListComparator(0, pm);
|
||||||
tmpList.sort((a, b) -> {
|
setLoaded(false);
|
||||||
boolean aChecked = moduleUtil.isModuleEnabled(a.packageName);
|
var tmpList = new ArrayList<ModuleUtil.InstalledModule>();
|
||||||
boolean bChecked = moduleUtil.isModuleEnabled(b.packageName);
|
modules.values().parallelStream()
|
||||||
if (aChecked == bChecked) {
|
.sorted((a, b) -> {
|
||||||
return cmp.compare(a.pkg, b.pkg);
|
boolean aChecked = moduleUtil.isModuleEnabled(a.packageName);
|
||||||
} else if (aChecked) {
|
boolean bChecked = moduleUtil.isModuleEnabled(b.packageName);
|
||||||
return -1;
|
if (aChecked == bChecked) {
|
||||||
} else {
|
var c = cmp.compare(a.pkg, b.pkg);
|
||||||
return 1;
|
if (c == 0) {
|
||||||
|
if (a.userId == getUser().id) return -1;
|
||||||
|
if (b.userId == getUser().id) return 1;
|
||||||
|
else return Integer.compare(a.userId, b.userId);
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
} else if (aChecked) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}).forEachOrdered(new Consumer<>() {
|
||||||
|
private final HashSet<String> uniquer = new HashSet<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void accept(ModuleUtil.InstalledModule module) {
|
||||||
|
if (isPick()) {
|
||||||
|
if (!uniquer.contains(module.packageName)) {
|
||||||
|
uniquer.add(module.packageName);
|
||||||
|
if (module.userId != getUser().id)
|
||||||
|
tmpList.add(module);
|
||||||
|
}
|
||||||
|
} else if (module.userId == getUser().id) {
|
||||||
|
tmpList.add(module);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
searchList.clear();
|
});
|
||||||
searchList.addAll(tmpList);
|
String queryStr = searchView != null ? searchView.getQuery().toString() : "";
|
||||||
String queryStr = searchView != null ? searchView.getQuery().toString() : "";
|
searchList = tmpList;
|
||||||
runOnUiThread(() -> getFilter().filter(queryStr));
|
runOnUiThread(() -> getFilter().filter(queryStr, count -> setLoaded(true)));
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
private void setLoaded(boolean loaded) {
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
isLoaded = loaded;
|
||||||
|
notifyDataSetChanged();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isLoaded() {
|
public boolean isLoaded() {
|
||||||
return isLoaded;
|
return isLoaded;
|
||||||
|
|
@ -651,14 +694,10 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
||||||
return filterResults;
|
return filterResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
|
||||||
@Override
|
@Override
|
||||||
protected void publishResults(CharSequence constraint, FilterResults results) {
|
protected void publishResults(CharSequence constraint, FilterResults results) {
|
||||||
showList.clear();
|
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
showList.addAll((List<ModuleUtil.InstalledModule>) results.values);
|
showList = (List<ModuleUtil.InstalledModule>) results.values;
|
||||||
isLoaded = true;
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@ import org.lsposed.manager.repo.model.Collaborator;
|
||||||
import org.lsposed.manager.repo.model.OnlineModule;
|
import org.lsposed.manager.repo.model.OnlineModule;
|
||||||
import org.lsposed.manager.repo.model.Release;
|
import org.lsposed.manager.repo.model.Release;
|
||||||
import org.lsposed.manager.repo.model.ReleaseAsset;
|
import org.lsposed.manager.repo.model.ReleaseAsset;
|
||||||
|
import org.lsposed.manager.ui.widget.EmptyStateRecyclerView;
|
||||||
import org.lsposed.manager.ui.widget.LinkifyTextView;
|
import org.lsposed.manager.ui.widget.LinkifyTextView;
|
||||||
import org.lsposed.manager.util.NavUtil;
|
import org.lsposed.manager.util.NavUtil;
|
||||||
import org.lsposed.manager.util.SimpleStatefulAdaptor;
|
import org.lsposed.manager.util.SimpleStatefulAdaptor;
|
||||||
|
|
@ -210,7 +211,7 @@ public class RepoItemFragment extends BaseFragment implements RepoLoader.Listene
|
||||||
public void moduleReleasesLoaded(OnlineModule module) {
|
public void moduleReleasesLoaded(OnlineModule module) {
|
||||||
this.module = module;
|
this.module = module;
|
||||||
if (releaseAdapter != null) {
|
if (releaseAdapter != null) {
|
||||||
runOnUiThread(() -> releaseAdapter.loadItems());
|
runAsync(releaseAdapter::loadItems);
|
||||||
if (isResumed() && module.getReleases().size() == 1) {
|
if (isResumed() && module.getReleases().size() == 1) {
|
||||||
Snackbar.make(binding.snackbar, R.string.module_release_no_more, Snackbar.LENGTH_SHORT).show();
|
Snackbar.make(binding.snackbar, R.string.module_release_no_more, Snackbar.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
|
|
@ -220,7 +221,7 @@ public class RepoItemFragment extends BaseFragment implements RepoLoader.Listene
|
||||||
@Override
|
@Override
|
||||||
public void onThrowable(Throwable t) {
|
public void onThrowable(Throwable t) {
|
||||||
if (releaseAdapter != null) {
|
if (releaseAdapter != null) {
|
||||||
runOnUiThread(() -> releaseAdapter.loadItems());
|
runAsync(releaseAdapter::loadItems);
|
||||||
if (isResumed()) {
|
if (isResumed()) {
|
||||||
Snackbar.make(binding.snackbar, getString(R.string.repo_load_failed, t.getLocalizedMessage()), Snackbar.LENGTH_SHORT).show();
|
Snackbar.make(binding.snackbar, getString(R.string.repo_load_failed, t.getLocalizedMessage()), Snackbar.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
|
|
@ -321,12 +322,12 @@ public class RepoItemFragment extends BaseFragment implements RepoLoader.Listene
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ReleaseAdapter extends SimpleStatefulAdaptor<ReleaseAdapter.ViewHolder> {
|
private class ReleaseAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<ReleaseAdapter.ViewHolder> {
|
||||||
private List<Release> items;
|
private List<Release> items = new ArrayList<>();
|
||||||
private final Resources resources = App.getInstance().getResources();
|
private final Resources resources = App.getInstance().getResources();
|
||||||
|
|
||||||
public ReleaseAdapter() {
|
public ReleaseAdapter() {
|
||||||
loadItems();
|
runAsync(this::loadItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadItems() {
|
public void loadItems() {
|
||||||
|
|
@ -345,7 +346,7 @@ public class RepoItemFragment extends BaseFragment implements RepoLoader.Listene
|
||||||
return !name.startsWith("snapshot") && !name.startsWith("nightly");
|
return !name.startsWith("snapshot") && !name.startsWith("nightly");
|
||||||
}).collect(Collectors.toList());
|
}).collect(Collectors.toList());
|
||||||
} else this.items = releases;
|
} else this.items = releases;
|
||||||
notifyDataSetChanged();
|
runOnUiThread(this::notifyDataSetChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
|
@ -400,6 +401,11 @@ public class RepoItemFragment extends BaseFragment implements RepoLoader.Listene
|
||||||
return !module.releasesLoaded && position == getItemCount() - 1 ? 1 : 0;
|
return !module.releasesLoaded && position == getItemCount() - 1 ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLoaded() {
|
||||||
|
return module.releasesLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
class ViewHolder extends RecyclerView.ViewHolder {
|
class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
TextView title;
|
TextView title;
|
||||||
WebView description;
|
WebView description;
|
||||||
|
|
|
||||||
|
|
@ -38,23 +38,6 @@ import rikka.core.util.ResourceUtils;
|
||||||
public class EmptyStateRecyclerView extends StatefulRecyclerView {
|
public class EmptyStateRecyclerView extends StatefulRecyclerView {
|
||||||
private final TextPaint paint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
|
private final TextPaint paint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
|
||||||
private final String emptyText;
|
private final String emptyText;
|
||||||
private final AdapterDataObserver emptyObserver = new AdapterDataObserver() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onChanged() {
|
|
||||||
Adapter<?> adapter = getAdapter();
|
|
||||||
if (adapter != null) {
|
|
||||||
boolean newEmpty = adapter.getItemCount() == 0;
|
|
||||||
if (empty != newEmpty) {
|
|
||||||
empty = newEmpty;
|
|
||||||
invalidate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private boolean empty = false;
|
|
||||||
|
|
||||||
|
|
||||||
public EmptyStateRecyclerView(Context context) {
|
public EmptyStateRecyclerView(Context context) {
|
||||||
this(context, null);
|
this(context, null);
|
||||||
|
|
@ -74,26 +57,11 @@ public class EmptyStateRecyclerView extends StatefulRecyclerView {
|
||||||
emptyText = context.getString(R.string.list_empty);
|
emptyText = context.getString(R.string.list_empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setAdapter(Adapter adapter) {
|
|
||||||
var oldAdapter = getAdapter();
|
|
||||||
if (oldAdapter != null) {
|
|
||||||
oldAdapter.unregisterAdapterDataObserver(emptyObserver);
|
|
||||||
}
|
|
||||||
super.setAdapter(adapter);
|
|
||||||
if (adapter != null) {
|
|
||||||
adapter.registerAdapterDataObserver(emptyObserver);
|
|
||||||
if (adapter instanceof EmptyStateAdapter && ((EmptyStateAdapter<?>) adapter).isLoaded()) {
|
|
||||||
emptyObserver.onChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void dispatchDraw(Canvas canvas) {
|
protected void dispatchDraw(Canvas canvas) {
|
||||||
super.dispatchDraw(canvas);
|
super.dispatchDraw(canvas);
|
||||||
|
var adapter = getAdapter();
|
||||||
if (empty) {
|
if (adapter instanceof EmptyStateAdapter && ((EmptyStateAdapter<?>) adapter).isLoaded() && adapter.getItemCount() == 0) {
|
||||||
final int width = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
|
final int width = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
|
||||||
final int height = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
|
final int height = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
|
||||||
|
|
||||||
|
|
@ -108,7 +76,6 @@ public class EmptyStateRecyclerView extends StatefulRecyclerView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public abstract static class EmptyStateAdapter<T extends ViewHolder> extends SimpleStatefulAdaptor<T> {
|
public abstract static class EmptyStateAdapter<T extends ViewHolder> extends SimpleStatefulAdaptor<T> {
|
||||||
abstract public boolean isLoaded();
|
abstract public boolean isLoaded();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import android.content.pm.PackageManager.NameNotFoundException;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.util.Pair;
|
import androidx.core.util.Pair;
|
||||||
|
|
||||||
import org.lsposed.manager.App;
|
import org.lsposed.manager.App;
|
||||||
|
|
@ -58,7 +59,7 @@ public final class ModuleUtil {
|
||||||
public static synchronized ModuleUtil getInstance() {
|
public static synchronized ModuleUtil getInstance() {
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
instance = new ModuleUtil();
|
instance = new ModuleUtil();
|
||||||
instance.reloadInstalledModules();
|
App.getExecutorService().submit(instance::reloadInstalledModules);
|
||||||
}
|
}
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
@ -101,10 +102,13 @@ public final class ModuleUtil {
|
||||||
installedModules = modules;
|
installedModules = modules;
|
||||||
|
|
||||||
enabledModules = new HashSet<>(Arrays.asList(ConfigManager.getEnabledModules()));
|
enabledModules = new HashSet<>(Arrays.asList(ConfigManager.getEnabledModules()));
|
||||||
|
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
isReloading = false;
|
isReloading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (var listener: listeners) {
|
||||||
|
listener.onModulesReloaded();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public InstalledModule reloadSingleModule(String packageName, int userId) {
|
public InstalledModule reloadSingleModule(String packageName, int userId) {
|
||||||
|
|
@ -122,7 +126,7 @@ public final class ModuleUtil {
|
||||||
InstalledModule old = installedModules.remove(Pair.create(packageName, userId));
|
InstalledModule old = installedModules.remove(Pair.create(packageName, userId));
|
||||||
if (old != null) {
|
if (old != null) {
|
||||||
for (ModuleListener listener : listeners) {
|
for (ModuleListener listener : listeners) {
|
||||||
listener.onSingleInstalledModuleReloaded();
|
listener.onSingleInstalledModuleReloaded(old);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -133,14 +137,14 @@ public final class ModuleUtil {
|
||||||
InstalledModule module = new InstalledModule(pkg);
|
InstalledModule module = new InstalledModule(pkg);
|
||||||
installedModules.put(Pair.create(packageName, userId), module);
|
installedModules.put(Pair.create(packageName, userId), module);
|
||||||
for (ModuleListener listener : listeners) {
|
for (ModuleListener listener : listeners) {
|
||||||
listener.onSingleInstalledModuleReloaded();
|
listener.onSingleInstalledModuleReloaded(module);
|
||||||
}
|
}
|
||||||
return module;
|
return module;
|
||||||
} else {
|
} else {
|
||||||
InstalledModule old = installedModules.remove(Pair.create(packageName, userId));
|
InstalledModule old = installedModules.remove(Pair.create(packageName, userId));
|
||||||
if (old != null) {
|
if (old != null) {
|
||||||
for (ModuleListener listener : listeners) {
|
for (ModuleListener listener : listeners) {
|
||||||
listener.onSingleInstalledModuleReloaded();
|
listener.onSingleInstalledModuleReloaded(old);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -155,8 +159,9 @@ public final class ModuleUtil {
|
||||||
return getModule(packageName, 0);
|
return getModule(packageName, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<Pair<String, Integer>, InstalledModule> getModules() {
|
@Nullable
|
||||||
return installedModules;
|
synchronized public Map<Pair<String, Integer>, InstalledModule> getModules() {
|
||||||
|
return isReloading ? null : installedModules;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean setModuleEnabled(String packageName, boolean enabled) {
|
public boolean setModuleEnabled(String packageName, boolean enabled) {
|
||||||
|
|
@ -193,7 +198,13 @@ public final class ModuleUtil {
|
||||||
* Called whenever one (previously or now) installed module has been
|
* Called whenever one (previously or now) installed module has been
|
||||||
* reloaded
|
* reloaded
|
||||||
*/
|
*/
|
||||||
void onSingleInstalledModuleReloaded();
|
default void onSingleInstalledModuleReloaded(InstalledModule module) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
default void onModulesReloaded() {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class InstalledModule {
|
public class InstalledModule {
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ public abstract class SimpleStatefulAdaptor<T extends RecyclerView.ViewHolder> e
|
||||||
super.onViewRecycled(holder);
|
super.onViewRecycled(holder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
@Override
|
@Override
|
||||||
public final void onBindViewHolder(@NonNull T holder, int position, @NonNull List<Object> payloads) {
|
public final void onBindViewHolder(@NonNull T holder, int position, @NonNull List<Object> payloads) {
|
||||||
var state = states.remove(holder.getItemId());
|
var state = states.remove(holder.getItemId());
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,29 @@
|
||||||
~
|
~
|
||||||
~ Copyright (C) 2021 LSPosed Contributors
|
~ Copyright (C) 2021 LSPosed Contributors
|
||||||
-->
|
-->
|
||||||
<org.lsposed.manager.ui.widget.EmptyStateRecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:clipToPadding="false"
|
|
||||||
android:minHeight="?attr/listPreferredItemHeight"
|
|
||||||
android:fadeScrollbars="true"
|
|
||||||
android:scrollbarStyle="insideOverlay"
|
|
||||||
android:scrollbars="vertical" />
|
|
||||||
|
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||||
|
android:id="@+id/progress"
|
||||||
|
android:layout_width="36dp"
|
||||||
|
android:layout_height="36dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:indeterminate="true"
|
||||||
|
android:visibility="visible"
|
||||||
|
app:hideAnimationBehavior="inward"
|
||||||
|
app:showAnimationBehavior="outward" />
|
||||||
|
|
||||||
|
<org.lsposed.manager.ui.widget.EmptyStateRecyclerView
|
||||||
|
android:id="@+id/list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:fadeScrollbars="true"
|
||||||
|
android:minHeight="?attr/listPreferredItemHeight"
|
||||||
|
android:scrollbarStyle="insideOverlay"
|
||||||
|
android:scrollbars="vertical" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:id="@+id/swipeRefreshLayout">
|
android:id="@+id/swipeRefreshLayout">
|
||||||
|
|
||||||
<org.lsposed.manager.ui.widget.StatefulRecyclerView
|
<org.lsposed.manager.ui.widget.EmptyStateRecyclerView
|
||||||
android:id="@+id/recyclerView"
|
android:id="@+id/recyclerView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
|
|
||||||
|
|
@ -38,19 +38,19 @@
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:elevation="0dp"
|
||||||
android:minHeight="?attr/actionBarSize"
|
android:minHeight="?attr/actionBarSize"
|
||||||
app:logo="@drawable/ic_launcher"
|
|
||||||
app:contentInsetStart="24dp"
|
app:contentInsetStart="24dp"
|
||||||
app:titleMarginStart="48dp"
|
app:logo="@drawable/ic_launcher"
|
||||||
android:elevation="0dp" />
|
app:titleMarginStart="48dp" />
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:clipToPadding="false"
|
|
||||||
android:clipChildren="false"
|
android:clipChildren="false"
|
||||||
|
android:clipToPadding="false"
|
||||||
android:paddingTop="?actionBarSize"
|
android:paddingTop="?actionBarSize"
|
||||||
app:fitsSystemWindowsInsets="top|bottom"
|
app:fitsSystemWindowsInsets="top|bottom"
|
||||||
tools:ignore="MissingPrefix">
|
tools:ignore="MissingPrefix">
|
||||||
|
|
@ -59,9 +59,9 @@
|
||||||
android:id="@+id/nestedScrollView"
|
android:id="@+id/nestedScrollView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
app:borderTopVisibility="whenTop"
|
app:borderBottomVisibility="never"
|
||||||
app:borderTopDrawable="@null"
|
app:borderTopDrawable="@null"
|
||||||
app:borderBottomVisibility="never">
|
app:borderTopVisibility="whenTop">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
@ -72,9 +72,9 @@
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
<com.google.android.material.card.MaterialCardView
|
||||||
android:id="@+id/status"
|
android:id="@+id/status"
|
||||||
|
style="@style/HomeCard.Primary"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content">
|
||||||
style="@style/HomeCard.Primary">
|
|
||||||
|
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
@ -97,9 +97,9 @@
|
||||||
android:layout_marginStart="24dp"
|
android:layout_marginStart="24dp"
|
||||||
android:layout_toEndOf="@id/status_icon"
|
android:layout_toEndOf="@id/status_icon"
|
||||||
android:fontFamily="sans-serif-medium"
|
android:fontFamily="sans-serif-medium"
|
||||||
android:textSize="16sp"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||||
android:textColor="@color/primary_text_material_inverse" />
|
android:textColor="@color/primary_text_material_inverse"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/status_summary"
|
android:id="@+id/status_summary"
|
||||||
|
|
@ -114,9 +114,9 @@
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
<com.google.android.material.card.MaterialCardView
|
||||||
android:id="@+id/modules"
|
android:id="@+id/modules"
|
||||||
|
style="@style/HomeCard.Secondary"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content">
|
||||||
style="@style/HomeCard.Secondary">
|
|
||||||
|
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
@ -138,8 +138,8 @@
|
||||||
android:layout_marginStart="24dp"
|
android:layout_marginStart="24dp"
|
||||||
android:layout_toEndOf="@id/modules_icon"
|
android:layout_toEndOf="@id/modules_icon"
|
||||||
android:text="@string/Modules"
|
android:text="@string/Modules"
|
||||||
android:textSize="16sp"
|
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||||
android:textAppearance="?android:attr/textAppearanceListItem" />
|
android:textSize="16sp" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/modules_summary"
|
android:id="@+id/modules_summary"
|
||||||
|
|
@ -147,15 +147,16 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/modules_title"
|
android:layout_below="@id/modules_title"
|
||||||
android:layout_alignStart="@id/modules_title"
|
android:layout_alignStart="@id/modules_title"
|
||||||
|
android:text="@string/module_repo_loading"
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
</com.google.android.material.card.MaterialCardView>
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
<com.google.android.material.card.MaterialCardView
|
||||||
android:id="@+id/download"
|
android:id="@+id/download"
|
||||||
|
style="@style/HomeCard.Secondary"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content">
|
||||||
style="@style/HomeCard.Secondary">
|
|
||||||
|
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
@ -177,8 +178,8 @@
|
||||||
android:layout_marginStart="24dp"
|
android:layout_marginStart="24dp"
|
||||||
android:layout_toEndOf="@id/download_icon"
|
android:layout_toEndOf="@id/download_icon"
|
||||||
android:text="@string/module_repo"
|
android:text="@string/module_repo"
|
||||||
android:textSize="16sp"
|
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||||
android:textAppearance="?android:attr/textAppearanceListItem" />
|
android:textSize="16sp" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/download_summary"
|
android:id="@+id/download_summary"
|
||||||
|
|
@ -193,16 +194,16 @@
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
<com.google.android.material.card.MaterialCardView
|
||||||
android:id="@+id/logs"
|
android:id="@+id/logs"
|
||||||
|
style="@style/HomeCard.Tertiary"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content">
|
||||||
style="@style/HomeCard.Tertiary">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="16dp"
|
android:layout_gravity="center_vertical"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:layout_gravity="center_vertical">
|
android:padding="16dp">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="24dp"
|
android:layout_width="24dp"
|
||||||
|
|
@ -215,23 +216,23 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="24dp"
|
android:layout_marginStart="24dp"
|
||||||
android:text="@string/Logs"
|
android:text="@string/Logs"
|
||||||
android:textSize="16sp"
|
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||||
android:textAppearance="?android:attr/textAppearanceListItem" />
|
android:textSize="16sp" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</com.google.android.material.card.MaterialCardView>
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
<com.google.android.material.card.MaterialCardView
|
||||||
android:id="@+id/settings"
|
android:id="@+id/settings"
|
||||||
|
style="@style/HomeCard.Tertiary"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content">
|
||||||
style="@style/HomeCard.Tertiary">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="16dp"
|
android:layout_gravity="center_vertical"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:layout_gravity="center_vertical">
|
android:padding="16dp">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="24dp"
|
android:layout_width="24dp"
|
||||||
|
|
@ -250,16 +251,16 @@
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
<com.google.android.material.card.MaterialCardView
|
||||||
android:id="@+id/issue"
|
android:id="@+id/issue"
|
||||||
|
style="@style/HomeCard.Tertiary"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content">
|
||||||
style="@style/HomeCard.Tertiary">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="16dp"
|
android:layout_gravity="center_vertical"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:layout_gravity="center_vertical">
|
android:padding="16dp">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="24dp"
|
android:layout_width="24dp"
|
||||||
|
|
@ -272,8 +273,8 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="24dp"
|
android:layout_marginStart="24dp"
|
||||||
android:text="@string/report_issue"
|
android:text="@string/report_issue"
|
||||||
android:textSize="16sp"
|
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||||
android:textAppearance="?android:attr/textAppearanceListItem" />
|
android:textSize="16sp" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</com.google.android.material.card.MaterialCardView>
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,10 +38,10 @@
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:minHeight="?attr/actionBarSize"
|
|
||||||
android:elevation="0dp"
|
android:elevation="0dp"
|
||||||
app:layout_scrollFlags="scroll|enterAlways"
|
android:minHeight="?attr/actionBarSize"
|
||||||
app:layout_scrollEffect="none" />
|
app:layout_scrollEffect="none"
|
||||||
|
app:layout_scrollFlags="scroll|enterAlways" />
|
||||||
|
|
||||||
<com.google.android.material.tabs.TabLayout
|
<com.google.android.material.tabs.TabLayout
|
||||||
android:id="@+id/tab_layout"
|
android:id="@+id/tab_layout"
|
||||||
|
|
@ -54,11 +54,25 @@
|
||||||
app:tabMode="scrollable" />
|
app:tabMode="scrollable" />
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<androidx.viewpager2.widget.ViewPager2
|
<FrameLayout
|
||||||
android:id="@+id/view_pager"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||||
|
|
||||||
|
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||||
|
android:id="@+id/progress"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:indeterminate="true"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:hideAnimationBehavior="outward" />
|
||||||
|
|
||||||
|
<androidx.viewpager2.widget.ViewPager2
|
||||||
|
android:id="@+id/view_pager"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
android:id="@+id/fab"
|
android:id="@+id/fab"
|
||||||
|
|
@ -68,6 +82,6 @@
|
||||||
android:layout_margin="16dp"
|
android:layout_margin="16dp"
|
||||||
android:contentDescription="@string/add_module_to_user"
|
android:contentDescription="@string/add_module_to_user"
|
||||||
android:src="@drawable/ic_baseline_add_24"
|
android:src="@drawable/ic_baseline_add_24"
|
||||||
android:visibility="invisible"
|
android:visibility="gone"
|
||||||
app:layout_fitsSystemWindowsInsets="bottom" />
|
app:layout_fitsSystemWindowsInsets="bottom" />
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
~ Copyright (C) 2020 EdXposed Contributors
|
~ Copyright (C) 2020 EdXposed Contributors
|
||||||
~ Copyright (C) 2021 LSPosed Contributors
|
~ Copyright (C) 2021 LSPosed Contributors
|
||||||
-->
|
-->
|
||||||
<org.lsposed.manager.ui.widget.StatefulRecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
<org.lsposed.manager.ui.widget.EmptyStateRecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:id="@+id/recyclerView"
|
android:id="@+id/recyclerView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
|
||||||
|
|
@ -173,7 +173,7 @@ dependencies {
|
||||||
implementation("com.android.tools.build:apksig:$agpVersion")
|
implementation("com.android.tools.build:apksig:$agpVersion")
|
||||||
implementation("org.apache.commons:commons-lang3:3.12.0")
|
implementation("org.apache.commons:commons-lang3:3.12.0")
|
||||||
implementation("de.upb.cs.swt:axml:2.1.1")
|
implementation("de.upb.cs.swt:axml:2.1.1")
|
||||||
compileOnly("androidx.annotation:annotation:1.2.0")
|
compileOnly("androidx.annotation:annotation:1.3.0")
|
||||||
compileOnly(project(":hiddenapi-stubs"))
|
compileOnly(project(":hiddenapi-stubs"))
|
||||||
implementation(project(":hiddenapi-bridge"))
|
implementation(project(":hiddenapi-bridge"))
|
||||||
implementation(project(":manager-service"))
|
implementation(project(":manager-service"))
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ package org.lsposed.lspd.service;
|
||||||
|
|
||||||
import static android.content.Context.BIND_AUTO_CREATE;
|
import static android.content.Context.BIND_AUTO_CREATE;
|
||||||
import static org.lsposed.lspd.service.ServiceManager.TAG;
|
import static org.lsposed.lspd.service.ServiceManager.TAG;
|
||||||
|
import static org.lsposed.lspd.service.ServiceManager.getExecutorService;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.INotificationManager;
|
import android.app.INotificationManager;
|
||||||
|
|
@ -406,10 +407,10 @@ public class LSPManagerService extends ILSPManagerService.Stub {
|
||||||
// we do it by cancelling the launch (return false)
|
// we do it by cancelling the launch (return false)
|
||||||
// and start activity in a new thread
|
// and start activity in a new thread
|
||||||
pendingManager = true;
|
pendingManager = true;
|
||||||
new Thread(() -> {
|
getExecutorService().submit(() -> {
|
||||||
ensureWebViewPermission();
|
ensureWebViewPermission();
|
||||||
stopAndStartActivity(pkgName, intent, true);
|
stopAndStartActivity(pkgName, intent, true);
|
||||||
}).start();
|
});
|
||||||
Log.d(TAG, "requested to launch manager");
|
Log.d(TAG, "requested to launch manager");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ package org.lsposed.lspd.service;
|
||||||
|
|
||||||
import static org.lsposed.lspd.service.PackageService.PER_USER_RANGE;
|
import static org.lsposed.lspd.service.PackageService.PER_USER_RANGE;
|
||||||
import static org.lsposed.lspd.service.ServiceManager.TAG;
|
import static org.lsposed.lspd.service.ServiceManager.TAG;
|
||||||
|
import static org.lsposed.lspd.service.ServiceManager.getExecutorService;
|
||||||
|
|
||||||
import android.app.IApplicationThread;
|
import android.app.IApplicationThread;
|
||||||
import android.content.IIntentReceiver;
|
import android.content.IIntentReceiver;
|
||||||
|
|
@ -216,7 +217,7 @@ public class LSPosedService extends ILSPosedService.Stub {
|
||||||
var receiver = new IIntentReceiver.Stub() {
|
var receiver = new IIntentReceiver.Stub() {
|
||||||
@Override
|
@Override
|
||||||
public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
|
public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
|
||||||
new Thread(() -> dispatchPackageChanged(intent)).start();
|
getExecutorService().submit(() -> dispatchPackageChanged(intent));
|
||||||
try {
|
try {
|
||||||
ActivityManagerService.finishReceiver(this, resultCode, data, extras, false, intent.getFlags());
|
ActivityManagerService.finishReceiver(this, resultCode, data, extras, false, intent.getFlags());
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
|
@ -241,7 +242,7 @@ public class LSPosedService extends ILSPosedService.Stub {
|
||||||
ActivityManagerService.registerReceiver("android", null, new IIntentReceiver.Stub() {
|
ActivityManagerService.registerReceiver("android", null, new IIntentReceiver.Stub() {
|
||||||
@Override
|
@Override
|
||||||
public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
|
public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
|
||||||
new Thread(() -> dispatchUserUnlocked(intent)).start();
|
getExecutorService().submit(() -> dispatchUserUnlocked(intent));
|
||||||
try {
|
try {
|
||||||
ActivityManagerService.finishReceiver(this, resultCode, data, extras, false, intent.getFlags());
|
ActivityManagerService.finishReceiver(this, resultCode, data, extras, false, intent.getFlags());
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
|
@ -263,7 +264,7 @@ public class LSPosedService extends ILSPosedService.Stub {
|
||||||
ActivityManagerService.registerReceiver("android", null, new IIntentReceiver.Stub() {
|
ActivityManagerService.registerReceiver("android", null, new IIntentReceiver.Stub() {
|
||||||
@Override
|
@Override
|
||||||
public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
|
public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
|
||||||
new Thread(() -> dispatchConfigurationChanged(intent)).start();
|
getExecutorService().submit(() -> dispatchConfigurationChanged(intent));
|
||||||
try {
|
try {
|
||||||
ActivityManagerService.finishReceiver(this, resultCode, data, extras, false, intent.getFlags());
|
ActivityManagerService.finishReceiver(this, resultCode, data, extras, false, intent.getFlags());
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
|
@ -287,7 +288,7 @@ public class LSPosedService extends ILSPosedService.Stub {
|
||||||
ActivityManagerService.registerReceiver("android", null, new IIntentReceiver.Stub() {
|
ActivityManagerService.registerReceiver("android", null, new IIntentReceiver.Stub() {
|
||||||
@Override
|
@Override
|
||||||
public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
|
public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
|
||||||
new Thread(() -> dispatchSecretCodeReceive()).start();
|
getExecutorService().submit(() -> dispatchSecretCodeReceive());
|
||||||
try {
|
try {
|
||||||
ActivityManagerService.finishReceiver(this, resultCode, data, extras, false, intent.getFlags());
|
ActivityManagerService.finishReceiver(this, resultCode, data, extras, false, intent.getFlags());
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
|
@ -309,14 +310,14 @@ public class LSPosedService extends ILSPosedService.Stub {
|
||||||
ActivityManagerService.registerReceiver("android", null, new IIntentReceiver.Stub() {
|
ActivityManagerService.registerReceiver("android", null, new IIntentReceiver.Stub() {
|
||||||
@Override
|
@Override
|
||||||
public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
|
public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
|
||||||
new Thread(() -> {
|
getExecutorService().submit(() -> {
|
||||||
try {
|
try {
|
||||||
var am = ActivityManagerService.getActivityManager();
|
var am = ActivityManagerService.getActivityManager();
|
||||||
if (am != null) am.setActivityController(null, false);
|
if (am != null) am.setActivityController(null, false);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
Log.e(TAG, "setActivityController", e);
|
Log.e(TAG, "setActivityController", e);
|
||||||
}
|
}
|
||||||
}).start();
|
});
|
||||||
try {
|
try {
|
||||||
ActivityManagerService.finishReceiver(this, resultCode, data, extras, false, intent.getFlags());
|
ActivityManagerService.finishReceiver(this, resultCode, data, extras, false, intent.getFlags());
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import io.github.xposed.xposedservice.utils.ParceledListSlice;
|
import io.github.xposed.xposedservice.utils.ParceledListSlice;
|
||||||
|
|
||||||
|
|
@ -134,14 +135,15 @@ public class PackageService {
|
||||||
res.addAll(pm.getInstalledPackages(flags, user.id).getList());
|
res.addAll(pm.getInstalledPackages(flags, user.id).getList());
|
||||||
}
|
}
|
||||||
if (filterNoProcess) {
|
if (filterNoProcess) {
|
||||||
res.removeIf(packageInfo -> {
|
return new ParceledListSlice<>(res.parallelStream().filter(packageInfo -> {
|
||||||
try {
|
try {
|
||||||
PackageInfo pkgInfo = getPackageInfoWithComponents(packageInfo.packageName, MATCH_ALL_FLAGS, packageInfo.applicationInfo.uid / PER_USER_RANGE);
|
PackageInfo pkgInfo = getPackageInfoWithComponents(packageInfo.packageName, MATCH_ALL_FLAGS, packageInfo.applicationInfo.uid / PER_USER_RANGE);
|
||||||
return fetchProcesses(pkgInfo).isEmpty();
|
return !fetchProcesses(pkgInfo).isEmpty();
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
return false;
|
Log.w(TAG, "filter failed", e);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
});
|
}).collect(Collectors.toList()));
|
||||||
}
|
}
|
||||||
return new ParceledListSlice<>(res);
|
return new ParceledListSlice<>(res);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,9 @@ import org.lsposed.lspd.BuildConfig;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
import hidden.HiddenApiBridge;
|
import hidden.HiddenApiBridge;
|
||||||
|
|
||||||
|
|
@ -47,6 +50,12 @@ public class ServiceManager {
|
||||||
private static LSPSystemServerService systemServerService = null;
|
private static LSPSystemServerService systemServerService = null;
|
||||||
private static LogcatService logcatService = null;
|
private static LogcatService logcatService = null;
|
||||||
|
|
||||||
|
private static final ExecutorService executorService = Executors.newCachedThreadPool();
|
||||||
|
|
||||||
|
public static ExecutorService getExecutorService() {
|
||||||
|
return executorService;
|
||||||
|
}
|
||||||
|
|
||||||
private static void waitSystemService(String name) {
|
private static void waitSystemService(String name) {
|
||||||
while (android.os.ServiceManager.getService(name) == null) {
|
while (android.os.ServiceManager.getService(name) == null) {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue