[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.constraintlayout:constraintlayout:2.1.1")
|
||||
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-ui:$navVersion")
|
||||
implementation("androidx.preference:preference:1.1.1")
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import android.content.Intent;
|
|||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.os.Looper;
|
||||
import android.os.Process;
|
||||
import android.system.Os;
|
||||
import android.text.TextUtils;
|
||||
|
|
@ -36,6 +37,7 @@ import androidx.annotation.NonNull;
|
|||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import org.lsposed.hiddenapibypass.HiddenApiBypass;
|
||||
import org.lsposed.manager.adapters.AppHelper;
|
||||
import org.lsposed.manager.repo.RepoLoader;
|
||||
import org.lsposed.manager.ui.activity.CrashReportActivity;
|
||||
import org.lsposed.manager.util.DoHDNS;
|
||||
|
|
@ -84,6 +86,33 @@ public class App extends Application {
|
|||
// TODO: set specific class name
|
||||
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";
|
||||
|
|
@ -91,7 +120,7 @@ public class App extends Application {
|
|||
private static OkHttpClient okHttpClient;
|
||||
private static Cache okHttpCache;
|
||||
private SharedPreferences pref;
|
||||
private ExecutorService executorService;
|
||||
private final ExecutorService executorService = Executors.newCachedThreadPool();
|
||||
|
||||
public static App getInstance() {
|
||||
return instance;
|
||||
|
|
@ -143,8 +172,6 @@ public class App extends Application {
|
|||
|
||||
instance = this;
|
||||
|
||||
executorService = Executors.newCachedThreadPool();
|
||||
|
||||
pref = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
if ("CN".equals(Locale.getDefault().getCountry())) {
|
||||
if (!pref.contains("doh")) {
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import java.util.HashMap;
|
|||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
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 {
|
||||
List<Application> list = new ArrayList<>();
|
||||
applications.forEach(application -> {
|
||||
|
|
|
|||
|
|
@ -26,14 +26,18 @@ import android.content.pm.ApplicationInfo;
|
|||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.os.Looper;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import org.lsposed.lspd.models.Application;
|
||||
import org.lsposed.manager.App;
|
||||
import org.lsposed.manager.ConfigManager;
|
||||
import org.lsposed.manager.R;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class AppHelper {
|
||||
|
||||
|
|
@ -41,6 +45,7 @@ public class AppHelper {
|
|||
public static final int FLAG_SHOW_FOR_ALL_USERS = 0x0400;
|
||||
private static List<String> denyList;
|
||||
private static List<PackageInfo> appList;
|
||||
private static final ConcurrentHashMap<PackageInfo, CharSequence> appLabel = new ConcurrentHashMap<>();
|
||||
|
||||
public static Intent getSettingsIntent(String packageName, int userId) {
|
||||
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) {
|
||||
appList = ConfigManager.getInstalledPackagesFromAllUsers(PackageManager.GET_META_DATA | PackageManager.MATCH_UNINSTALLED_PACKAGES, true);
|
||||
}
|
||||
return appList;
|
||||
}
|
||||
|
||||
public static List<String> getDenyList(boolean force) {
|
||||
synchronized public static List<String> getDenyList(boolean force) {
|
||||
if (denyList == null || force) {
|
||||
denyList = ConfigManager.getDenyListPackages();
|
||||
}
|
||||
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.ui.fragment.AppListFragment;
|
||||
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.ModuleUtil;
|
||||
import org.lsposed.manager.util.SimpleStatefulAdaptor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
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.widget.switchbar.SwitchBar;
|
||||
|
||||
@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 AppListFragment fragment;
|
||||
|
|
@ -102,11 +102,11 @@ public class ScopeAdapter extends SimpleStatefulAdaptor<ScopeAdapter.ViewHolder>
|
|||
|
||||
private final ModuleUtil.InstalledModule module;
|
||||
|
||||
private final HashSet<ApplicationWithEquals> recommendedList = new HashSet<>();
|
||||
private final HashSet<ApplicationWithEquals> checkedList = new HashSet<>();
|
||||
private final ConcurrentLinkedQueue<AppInfo> searchList = new ConcurrentLinkedQueue<>();
|
||||
private final List<AppInfo> showList = new ArrayList<>();
|
||||
private final List<String> denyList = new ArrayList<>();
|
||||
private Set<ApplicationWithEquals> recommendedList = new HashSet<>();
|
||||
private Set<ApplicationWithEquals> checkedList = new HashSet<>();
|
||||
private List<AppInfo> searchList = new ArrayList<>();
|
||||
private List<AppInfo> showList = new ArrayList<>();
|
||||
private List<String> denyList = new ArrayList<>();
|
||||
|
||||
private final SwitchBar.OnCheckedChangeListener switchBarOnCheckedChangeListener = new SwitchBar.OnCheckedChangeListener() {
|
||||
@Override
|
||||
|
|
@ -119,21 +119,6 @@ public class ScopeAdapter extends SimpleStatefulAdaptor<ScopeAdapter.ViewHolder>
|
|||
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 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;
|
||||
}
|
||||
|
||||
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<AppInfo> frameworkComparator = (a, b) -> {
|
||||
if (a.packageName.equals("android") == b.packageName.equals("android")) {
|
||||
|
|
@ -210,23 +195,26 @@ public class ScopeAdapter extends SimpleStatefulAdaptor<ScopeAdapter.ViewHolder>
|
|||
return 1;
|
||||
}
|
||||
};
|
||||
list.sort((a, b) -> {
|
||||
boolean aChecked = checkedList.contains(a.application);
|
||||
boolean bChecked = checkedList.contains(b.application);
|
||||
if (aChecked == bChecked) {
|
||||
return recommendedComparator.compare(a, b);
|
||||
} else if (aChecked) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
boolean aChecked = checkedList.contains(x.application);
|
||||
boolean bChecked = checkedList.contains(y.application);
|
||||
if (aChecked == bChecked) {
|
||||
return recommendedComparator.compare(x, y);
|
||||
} else if (aChecked) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
private void checkRecommended() {
|
||||
checkedList.removeIf(i -> i.userId == module.userId);
|
||||
checkedList.addAll(recommendedList);
|
||||
ConfigManager.setModuleScope(module.packageName, checkedList);
|
||||
fragment.runAsync(() -> {
|
||||
var tmpChkList = new HashSet<>(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) {
|
||||
|
|
@ -237,13 +225,11 @@ public class ScopeAdapter extends SimpleStatefulAdaptor<ScopeAdapter.ViewHolder>
|
|||
.setMessage(R.string.use_recommended_message)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
checkRecommended();
|
||||
notifyDataSetChanged();
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show();
|
||||
} else {
|
||||
checkRecommended();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
return true;
|
||||
} 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
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int 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.setOnCheckedChangeListener((v, isChecked) -> onCheckedChange(v, isChecked, appInfo));
|
||||
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
if (enabled) holder.checkbox.toggle();
|
||||
});
|
||||
|
|
@ -495,82 +487,85 @@ public class ScopeAdapter extends SimpleStatefulAdaptor<ScopeAdapter.ViewHolder>
|
|||
fragment.binding.masterSwitch.setOnCheckedChangeListener(switchBarOnCheckedChangeListener);
|
||||
loadAppListHandler.post(() -> {
|
||||
List<PackageInfo> appList = AppHelper.getAppList(force);
|
||||
checkedList.clear();
|
||||
recommendedList.clear();
|
||||
denyList.clear();
|
||||
denyList.addAll(AppHelper.getDenyList(force));
|
||||
var tmpList = new ArrayList<AppInfo>();
|
||||
checkedList.addAll(ConfigManager.getModuleScope(module.packageName));
|
||||
HashSet<ApplicationWithEquals> installedList = new HashSet<>();
|
||||
denyList = AppHelper.getDenyList(force);
|
||||
var tmpRecList = new HashSet<ApplicationWithEquals>();
|
||||
var tmpChkList = new HashSet<>(ConfigManager.getModuleScope(module.packageName));
|
||||
final var tmpList = new ArrayList<AppInfo>();
|
||||
final HashSet<ApplicationWithEquals> installedList = new HashSet<>();
|
||||
List<String> scopeList = module.getScopeList();
|
||||
boolean emptyCheckedList = checkedList.isEmpty();
|
||||
for (PackageInfo info : appList) {
|
||||
boolean emptyCheckedList = tmpChkList.isEmpty();
|
||||
appList.parallelStream().forEach(info -> {
|
||||
int userId = info.applicationInfo.uid / 100000;
|
||||
String packageName = info.packageName;
|
||||
if (packageName.equals("android") && userId != 0 ||
|
||||
packageName.equals(module.packageName) ||
|
||||
packageName.equals(BuildConfig.APPLICATION_ID)) {
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
ApplicationWithEquals application = new ApplicationWithEquals(packageName, userId);
|
||||
|
||||
installedList.add(application);
|
||||
synchronized (installedList) {
|
||||
installedList.add(application);
|
||||
}
|
||||
|
||||
if (userId != module.userId) {
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
if (scopeList != null && scopeList.contains(packageName)) {
|
||||
recommendedList.add(application);
|
||||
synchronized (tmpRecList) {
|
||||
tmpRecList.add(application);
|
||||
}
|
||||
if (emptyCheckedList) {
|
||||
checkedList.add(application);
|
||||
synchronized (tmpChkList) {
|
||||
tmpChkList.add(application);
|
||||
}
|
||||
}
|
||||
} else if (shouldHideApp(info, application)) {
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
AppInfo appInfo = new AppInfo();
|
||||
appInfo.packageInfo = info;
|
||||
appInfo.label = info.applicationInfo.loadLabel(pm);
|
||||
appInfo.label = AppHelper.getAppLabel(info, pm);
|
||||
appInfo.application = application;
|
||||
appInfo.packageName = info.packageName;
|
||||
appInfo.applicationInfo = info.applicationInfo;
|
||||
tmpList.add(appInfo);
|
||||
}
|
||||
checkedList.retainAll(installedList);
|
||||
synchronized (tmpList) {
|
||||
tmpList.add(appInfo);
|
||||
}
|
||||
});
|
||||
tmpChkList.retainAll(installedList);
|
||||
if (emptyCheckedList) {
|
||||
ConfigManager.setModuleScope(module.packageName, checkedList);
|
||||
}
|
||||
sortApps(tmpList);
|
||||
searchList.clear();
|
||||
searchList.addAll(tmpList);
|
||||
synchronized (dataReadyRunnable) {
|
||||
synchronized (this) {
|
||||
refreshing = false;
|
||||
}
|
||||
activity.runOnUiThread(dataReadyRunnable);
|
||||
try {
|
||||
dataReadyRunnable.wait();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
ConfigManager.setModuleScope(module.packageName, tmpChkList);
|
||||
}
|
||||
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) {
|
||||
var tmpChkList = new HashSet<>(checkedList);
|
||||
if (isChecked) {
|
||||
checkedList.add(appInfo.application);
|
||||
tmpChkList.add(appInfo.application);
|
||||
} 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();
|
||||
if (!isChecked) {
|
||||
checkedList.add(appInfo.application);
|
||||
tmpChkList.add(appInfo.application);
|
||||
} else {
|
||||
checkedList.remove(appInfo.application);
|
||||
tmpChkList.remove(appInfo.application);
|
||||
}
|
||||
buttonView.setChecked(!isChecked);
|
||||
} 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)
|
||||
.show();
|
||||
}
|
||||
checkedList = tmpChkList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLoaded() {
|
||||
return !refreshing;
|
||||
}
|
||||
|
||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
|
|
@ -631,10 +632,8 @@ public class ScopeAdapter extends SimpleStatefulAdaptor<ScopeAdapter.ViewHolder>
|
|||
|
||||
@Override
|
||||
protected void publishResults(CharSequence constraint, FilterResults results) {
|
||||
showList.clear();
|
||||
//noinspection unchecked
|
||||
showList.addAll((Collection<AppInfo>) results.values);
|
||||
notifyDataSetChanged();
|
||||
showList = (List<AppInfo>) results.values;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -662,7 +661,6 @@ public class ScopeAdapter extends SimpleStatefulAdaptor<ScopeAdapter.ViewHolder>
|
|||
if (!recommendedList.isEmpty()) {
|
||||
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
checkRecommended();
|
||||
notifyDataSetChanged();
|
||||
});
|
||||
} else {
|
||||
builder.setPositiveButton(android.R.string.cancel, null);
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import androidx.annotation.NonNull;
|
|||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
|
|
@ -78,6 +79,15 @@ public class AppListFragment extends BaseFragment {
|
|||
|
||||
scopeAdapter = new ScopeAdapter(this, module);
|
||||
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.setHasFixedSize(true);
|
||||
binding.recyclerView.setLayoutManager(new LinearLayoutManager(requireActivity()));
|
||||
|
|
|
|||
|
|
@ -56,11 +56,12 @@ import java.util.Locale;
|
|||
|
||||
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 static final RepoLoader repoLoader = RepoLoader.getInstance();
|
||||
private static final ModuleUtil moduleUtil = ModuleUtil.getInstance();
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
|
|
@ -109,6 +110,7 @@ public class HomeFragment extends BaseFragment implements RepoLoader.Listener {
|
|||
updateStates(requireActivity(), ConfigManager.isBinderAlive(), UpdateUtil.needUpdate());
|
||||
|
||||
repoLoader.addListener(this);
|
||||
moduleUtil.addListener(this);
|
||||
if (repoLoader.isRepoLoaded()) {
|
||||
repoLoaded();
|
||||
}
|
||||
|
|
@ -198,7 +200,9 @@ public class HomeFragment extends BaseFragment implements RepoLoader.Listener {
|
|||
public void repoLoaded() {
|
||||
final int[] count = new int[]{0};
|
||||
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)) {
|
||||
var ver = repoLoader.getModuleLatestVersion(k.first);
|
||||
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)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onModulesReloaded() {
|
||||
setModulesSummary(moduleUtil.getEnabledModulesCount());
|
||||
}
|
||||
|
||||
private class StartFragmentListener implements View.OnClickListener {
|
||||
boolean requireInstalled;
|
||||
int fragment;
|
||||
|
|
@ -244,19 +253,20 @@ public class HomeFragment extends BaseFragment implements RepoLoader.Listener {
|
|||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
int moduleCount;
|
||||
if (ConfigManager.isBinderAlive()) {
|
||||
moduleCount = ModuleUtil.getInstance().getEnabledModulesCount();
|
||||
} else {
|
||||
moduleCount = 0;
|
||||
}
|
||||
binding.modulesSummary.setText(getResources().getQuantityString(R.plurals.modules_enabled_count, moduleCount, moduleCount));
|
||||
setModulesSummary(moduleUtil.getEnabledModulesCount());
|
||||
} else setModulesSummary(0);
|
||||
}
|
||||
|
||||
private void setModulesSummary(int moduleCount) {
|
||||
runOnUiThread(() -> binding.modulesSummary.setText(getResources().getQuantityString(R.plurals.modules_enabled_count, moduleCount, moduleCount)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
repoLoader.removeListener(this);
|
||||
moduleUtil.removeListener(this);
|
||||
binding = null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
package org.lsposed.manager.ui.fragment;
|
||||
|
||||
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.content.Intent;
|
||||
|
|
@ -82,26 +83,31 @@ import org.lsposed.manager.util.ModuleUtil;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import rikka.core.util.ResourceUtils;
|
||||
import rikka.recyclerview.RecyclerViewKt;
|
||||
import rikka.widget.borderview.BorderRecyclerView;
|
||||
|
||||
public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleListener {
|
||||
private static final PackageManager pm = App.getInstance().getPackageManager();
|
||||
private static final ModuleUtil moduleUtil = ModuleUtil.getInstance();
|
||||
private static final RepoLoader repoLoader = RepoLoader.getInstance();
|
||||
|
||||
private static final List<UserInfo> users = ConfigManager.getUsers();
|
||||
protected FragmentPagerBinding binding;
|
||||
protected SearchView searchView;
|
||||
private SearchView.OnQueryTextListener searchListener;
|
||||
|
||||
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;
|
||||
|
||||
|
|
@ -121,6 +127,23 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
|||
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
|
||||
|
|
@ -133,71 +156,54 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
|||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
binding = FragmentPagerBinding.inflate(inflater, container, false);
|
||||
|
||||
setupToolbar(binding.toolbar, R.string.Modules, R.menu.menu_modules);
|
||||
binding.viewPager.setAdapter(new PagerAdapter(this));
|
||||
binding.viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
BorderRecyclerView recyclerView = binding.viewPager.findViewWithTag(position);
|
||||
|
||||
if (position > 0) {
|
||||
binding.fab.show();
|
||||
} else {
|
||||
binding.fab.hide();
|
||||
}
|
||||
updateProgress();
|
||||
}
|
||||
});
|
||||
|
||||
var users = ConfigManager.getUsers();
|
||||
if (users != null) {
|
||||
adapters.clear();
|
||||
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);
|
||||
new TabLayoutMediator(binding.tabLayout, binding.viewPager, (tab, position) -> {
|
||||
if (position < adapters.size()) {
|
||||
tab.setText(adapters.get(position).getUser().name);
|
||||
}
|
||||
}).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 -> {
|
||||
var pickAdaptor = new ModuleAdapter(null, true);
|
||||
var pickAdaptor = new ModuleAdapter(adapters.get(binding.viewPager.getCurrentItem()).getUser(), true);
|
||||
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();
|
||||
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();
|
||||
var rv = DialogRecyclerviewBinding.inflate(getLayoutInflater()).getRoot();
|
||||
rv.setAdapter(pickAdaptor);
|
||||
rv.setLayoutManager(new LinearLayoutManager(requireActivity()));
|
||||
var dialog = new MaterialAlertDialogBuilder(requireActivity())
|
||||
.setTitle(getString(R.string.install_to_user, user.name))
|
||||
.setView(rv)
|
||||
.setView(binding.getRoot())
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show();
|
||||
pickAdaptor.setOnPickListener(picked -> {
|
||||
|
|
@ -229,8 +235,13 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onSingleInstalledModuleReloaded() {
|
||||
adapters.forEach(adapter -> adapter.refresh(true));
|
||||
public void onSingleInstalledModuleReloaded(ModuleUtil.InstalledModule module) {
|
||||
adapters.forEach(ModuleAdapter::refresh);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onModulesReloaded() {
|
||||
adapters.forEach(ModuleAdapter::refresh);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -341,16 +352,18 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
|||
binding.recyclerView.setAdapter(fragment.adapters.get(position));
|
||||
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(requireActivity());
|
||||
binding.recyclerView.setLayoutManager(layoutManager);
|
||||
binding.recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
@Override
|
||||
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
|
||||
if (newState == RecyclerView.SCROLL_STATE_IDLE && position > 0) {
|
||||
fragment.binding.fab.show();
|
||||
} else {
|
||||
fragment.binding.fab.hide();
|
||||
if (users != null && users.size() != 1) {
|
||||
binding.recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
@Override
|
||||
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
|
||||
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
|
||||
fragment.binding.fab.show();
|
||||
} else {
|
||||
fragment.binding.fab.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
RecyclerViewKt.fixEdgeEffect(binding.recyclerView, false, true);
|
||||
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 final ConcurrentLinkedQueue<ModuleUtil.InstalledModule> searchList = new ConcurrentLinkedQueue<>();
|
||||
private final List<ModuleUtil.InstalledModule> showList = new ArrayList<>();
|
||||
private List<ModuleUtil.InstalledModule> searchList = new ArrayList<>();
|
||||
private List<ModuleUtil.InstalledModule> showList = new ArrayList<>();
|
||||
private final UserInfo user;
|
||||
private final boolean isPick;
|
||||
private boolean isLoaded;
|
||||
private View.OnClickListener onPickListener;
|
||||
|
||||
private Predicate<ModuleUtil.InstalledModule> customFilter = m -> true;
|
||||
|
||||
ModuleAdapter(UserInfo user) {
|
||||
this(user, false);
|
||||
}
|
||||
|
|
@ -412,6 +423,10 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
|||
return new ModuleAdapter.ViewHolder(ItemModuleBinding.inflate(getLayoutInflater(), parent, false));
|
||||
}
|
||||
|
||||
public boolean isPick() {
|
||||
return isPick;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ModuleAdapter.ViewHolder holder, int 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
|
||||
public int getItemCount() {
|
||||
return showList.size();
|
||||
|
|
@ -555,49 +576,71 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
|||
return new ModuleAdapter.ApplicationFilter();
|
||||
}
|
||||
|
||||
public void setFilter(@NonNull Predicate<ModuleUtil.InstalledModule> filter) {
|
||||
this.customFilter = filter;
|
||||
}
|
||||
|
||||
public void setOnPickListener(View.OnClickListener onPickListener) {
|
||||
this.onPickListener = onPickListener;
|
||||
}
|
||||
|
||||
public List<ModuleUtil.InstalledModule> snapshot() {
|
||||
return new ArrayList<>(searchList);
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
refresh(false);
|
||||
}
|
||||
|
||||
public void refresh(boolean force) {
|
||||
if (force) moduleUtil.reloadInstalledModules();
|
||||
runOnUiThread(reloadModules);
|
||||
if (force) runAsync(moduleUtil::reloadInstalledModules);
|
||||
runAsync(reloadModules);
|
||||
}
|
||||
|
||||
private final Runnable reloadModules = new Runnable() {
|
||||
public void run() {
|
||||
var tmpList = moduleUtil.getModules().values().stream().filter(module -> user == null ? module.userId == 0 : module.userId == user.id).filter(customFilter).collect(Collectors.toCollection(ArrayList::new));
|
||||
Comparator<PackageInfo> cmp = AppHelper.getAppListComparator(0, pm);
|
||||
tmpList.sort((a, b) -> {
|
||||
boolean aChecked = moduleUtil.isModuleEnabled(a.packageName);
|
||||
boolean bChecked = moduleUtil.isModuleEnabled(b.packageName);
|
||||
if (aChecked == bChecked) {
|
||||
return cmp.compare(a.pkg, b.pkg);
|
||||
} else if (aChecked) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
private final Runnable reloadModules = () -> {
|
||||
var modules = moduleUtil.getModules();
|
||||
if (modules == null) return;
|
||||
Comparator<PackageInfo> cmp = AppHelper.getAppListComparator(0, pm);
|
||||
setLoaded(false);
|
||||
var tmpList = new ArrayList<ModuleUtil.InstalledModule>();
|
||||
modules.values().parallelStream()
|
||||
.sorted((a, b) -> {
|
||||
boolean aChecked = moduleUtil.isModuleEnabled(a.packageName);
|
||||
boolean bChecked = moduleUtil.isModuleEnabled(b.packageName);
|
||||
if (aChecked == bChecked) {
|
||||
var c = cmp.compare(a.pkg, b.pkg);
|
||||
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() : "";
|
||||
runOnUiThread(() -> getFilter().filter(queryStr));
|
||||
}
|
||||
}
|
||||
});
|
||||
String queryStr = searchView != null ? searchView.getQuery().toString() : "";
|
||||
searchList = tmpList;
|
||||
runOnUiThread(() -> getFilter().filter(queryStr, count -> setLoaded(true)));
|
||||
};
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private void setLoaded(boolean loaded) {
|
||||
runOnUiThread(() -> {
|
||||
isLoaded = loaded;
|
||||
notifyDataSetChanged();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLoaded() {
|
||||
return isLoaded;
|
||||
|
|
@ -651,14 +694,10 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
|||
return filterResults;
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
@Override
|
||||
protected void publishResults(CharSequence constraint, FilterResults results) {
|
||||
showList.clear();
|
||||
//noinspection unchecked
|
||||
showList.addAll((List<ModuleUtil.InstalledModule>) results.values);
|
||||
isLoaded = true;
|
||||
notifyDataSetChanged();
|
||||
showList = (List<ModuleUtil.InstalledModule>) results.values;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ import org.lsposed.manager.repo.model.Collaborator;
|
|||
import org.lsposed.manager.repo.model.OnlineModule;
|
||||
import org.lsposed.manager.repo.model.Release;
|
||||
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.util.NavUtil;
|
||||
import org.lsposed.manager.util.SimpleStatefulAdaptor;
|
||||
|
|
@ -210,7 +211,7 @@ public class RepoItemFragment extends BaseFragment implements RepoLoader.Listene
|
|||
public void moduleReleasesLoaded(OnlineModule module) {
|
||||
this.module = module;
|
||||
if (releaseAdapter != null) {
|
||||
runOnUiThread(() -> releaseAdapter.loadItems());
|
||||
runAsync(releaseAdapter::loadItems);
|
||||
if (isResumed() && module.getReleases().size() == 1) {
|
||||
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
|
||||
public void onThrowable(Throwable t) {
|
||||
if (releaseAdapter != null) {
|
||||
runOnUiThread(() -> releaseAdapter.loadItems());
|
||||
runAsync(releaseAdapter::loadItems);
|
||||
if (isResumed()) {
|
||||
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 List<Release> items;
|
||||
private class ReleaseAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<ReleaseAdapter.ViewHolder> {
|
||||
private List<Release> items = new ArrayList<>();
|
||||
private final Resources resources = App.getInstance().getResources();
|
||||
|
||||
public ReleaseAdapter() {
|
||||
loadItems();
|
||||
runAsync(this::loadItems);
|
||||
}
|
||||
|
||||
public void loadItems() {
|
||||
|
|
@ -345,7 +346,7 @@ public class RepoItemFragment extends BaseFragment implements RepoLoader.Listene
|
|||
return !name.startsWith("snapshot") && !name.startsWith("nightly");
|
||||
}).collect(Collectors.toList());
|
||||
} else this.items = releases;
|
||||
notifyDataSetChanged();
|
||||
runOnUiThread(this::notifyDataSetChanged);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
|
@ -400,6 +401,11 @@ public class RepoItemFragment extends BaseFragment implements RepoLoader.Listene
|
|||
return !module.releasesLoaded && position == getItemCount() - 1 ? 1 : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLoaded() {
|
||||
return module.releasesLoaded;
|
||||
}
|
||||
|
||||
class ViewHolder extends RecyclerView.ViewHolder {
|
||||
TextView title;
|
||||
WebView description;
|
||||
|
|
|
|||
|
|
@ -38,23 +38,6 @@ import rikka.core.util.ResourceUtils;
|
|||
public class EmptyStateRecyclerView extends StatefulRecyclerView {
|
||||
private final TextPaint paint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
|
||||
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) {
|
||||
this(context, null);
|
||||
|
|
@ -74,26 +57,11 @@ public class EmptyStateRecyclerView extends StatefulRecyclerView {
|
|||
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
|
||||
protected void dispatchDraw(Canvas canvas) {
|
||||
super.dispatchDraw(canvas);
|
||||
|
||||
if (empty) {
|
||||
var adapter = getAdapter();
|
||||
if (adapter instanceof EmptyStateAdapter && ((EmptyStateAdapter<?>) adapter).isLoaded() && adapter.getItemCount() == 0) {
|
||||
final int width = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
|
||||
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> {
|
||||
abstract public boolean isLoaded();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import android.content.pm.PackageManager.NameNotFoundException;
|
|||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.util.Pair;
|
||||
|
||||
import org.lsposed.manager.App;
|
||||
|
|
@ -58,7 +59,7 @@ public final class ModuleUtil {
|
|||
public static synchronized ModuleUtil getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new ModuleUtil();
|
||||
instance.reloadInstalledModules();
|
||||
App.getExecutorService().submit(instance::reloadInstalledModules);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
|
@ -101,10 +102,13 @@ public final class ModuleUtil {
|
|||
installedModules = modules;
|
||||
|
||||
enabledModules = new HashSet<>(Arrays.asList(ConfigManager.getEnabledModules()));
|
||||
|
||||
synchronized (this) {
|
||||
isReloading = false;
|
||||
}
|
||||
|
||||
for (var listener: listeners) {
|
||||
listener.onModulesReloaded();
|
||||
}
|
||||
}
|
||||
|
||||
public InstalledModule reloadSingleModule(String packageName, int userId) {
|
||||
|
|
@ -122,7 +126,7 @@ public final class ModuleUtil {
|
|||
InstalledModule old = installedModules.remove(Pair.create(packageName, userId));
|
||||
if (old != null) {
|
||||
for (ModuleListener listener : listeners) {
|
||||
listener.onSingleInstalledModuleReloaded();
|
||||
listener.onSingleInstalledModuleReloaded(old);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
|
@ -133,14 +137,14 @@ public final class ModuleUtil {
|
|||
InstalledModule module = new InstalledModule(pkg);
|
||||
installedModules.put(Pair.create(packageName, userId), module);
|
||||
for (ModuleListener listener : listeners) {
|
||||
listener.onSingleInstalledModuleReloaded();
|
||||
listener.onSingleInstalledModuleReloaded(module);
|
||||
}
|
||||
return module;
|
||||
} else {
|
||||
InstalledModule old = installedModules.remove(Pair.create(packageName, userId));
|
||||
if (old != null) {
|
||||
for (ModuleListener listener : listeners) {
|
||||
listener.onSingleInstalledModuleReloaded();
|
||||
listener.onSingleInstalledModuleReloaded(old);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
|
@ -155,8 +159,9 @@ public final class ModuleUtil {
|
|||
return getModule(packageName, 0);
|
||||
}
|
||||
|
||||
public Map<Pair<String, Integer>, InstalledModule> getModules() {
|
||||
return installedModules;
|
||||
@Nullable
|
||||
synchronized public Map<Pair<String, Integer>, InstalledModule> getModules() {
|
||||
return isReloading ? null : installedModules;
|
||||
}
|
||||
|
||||
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
|
||||
* reloaded
|
||||
*/
|
||||
void onSingleInstalledModuleReloaded();
|
||||
default void onSingleInstalledModuleReloaded(InstalledModule module) {
|
||||
|
||||
}
|
||||
|
||||
default void onModulesReloaded() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public class InstalledModule {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ public abstract class SimpleStatefulAdaptor<T extends RecyclerView.ViewHolder> e
|
|||
super.onViewRecycled(holder);
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
@Override
|
||||
public final void onBindViewHolder(@NonNull T holder, int position, @NonNull List<Object> payloads) {
|
||||
var state = states.remove(holder.getItemId());
|
||||
|
|
|
|||
|
|
@ -16,12 +16,29 @@
|
|||
~
|
||||
~ 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:id="@+id/swipeRefreshLayout">
|
||||
|
||||
<org.lsposed.manager.ui.widget.StatefulRecyclerView
|
||||
<org.lsposed.manager.ui.widget.EmptyStateRecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
|
|
|||
|
|
@ -38,19 +38,19 @@
|
|||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="0dp"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
app:logo="@drawable/ic_launcher"
|
||||
app:contentInsetStart="24dp"
|
||||
app:titleMarginStart="48dp"
|
||||
android:elevation="0dp" />
|
||||
app:logo="@drawable/ic_launcher"
|
||||
app:titleMarginStart="48dp" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:paddingTop="?actionBarSize"
|
||||
app:fitsSystemWindowsInsets="top|bottom"
|
||||
tools:ignore="MissingPrefix">
|
||||
|
|
@ -59,9 +59,9 @@
|
|||
android:id="@+id/nestedScrollView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:borderTopVisibility="whenTop"
|
||||
app:borderBottomVisibility="never"
|
||||
app:borderTopDrawable="@null"
|
||||
app:borderBottomVisibility="never">
|
||||
app:borderTopVisibility="whenTop">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -72,9 +72,9 @@
|
|||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/status"
|
||||
style="@style/HomeCard.Primary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/HomeCard.Primary">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -97,9 +97,9 @@
|
|||
android:layout_marginStart="24dp"
|
||||
android:layout_toEndOf="@id/status_icon"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:textSize="16sp"
|
||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||
android:textColor="@color/primary_text_material_inverse" />
|
||||
android:textColor="@color/primary_text_material_inverse"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status_summary"
|
||||
|
|
@ -114,9 +114,9 @@
|
|||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/modules"
|
||||
style="@style/HomeCard.Secondary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/HomeCard.Secondary">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -138,8 +138,8 @@
|
|||
android:layout_marginStart="24dp"
|
||||
android:layout_toEndOf="@id/modules_icon"
|
||||
android:text="@string/Modules"
|
||||
android:textSize="16sp"
|
||||
android:textAppearance="?android:attr/textAppearanceListItem" />
|
||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/modules_summary"
|
||||
|
|
@ -147,15 +147,16 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/modules_title"
|
||||
android:layout_alignStart="@id/modules_title"
|
||||
android:text="@string/module_repo_loading"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||
</RelativeLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/download"
|
||||
style="@style/HomeCard.Secondary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/HomeCard.Secondary">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -177,8 +178,8 @@
|
|||
android:layout_marginStart="24dp"
|
||||
android:layout_toEndOf="@id/download_icon"
|
||||
android:text="@string/module_repo"
|
||||
android:textSize="16sp"
|
||||
android:textAppearance="?android:attr/textAppearanceListItem" />
|
||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/download_summary"
|
||||
|
|
@ -193,16 +194,16 @@
|
|||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/logs"
|
||||
style="@style/HomeCard.Tertiary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/HomeCard.Tertiary">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:layout_gravity="center_vertical">
|
||||
android:padding="16dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
|
|
@ -215,23 +216,23 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:text="@string/Logs"
|
||||
android:textSize="16sp"
|
||||
android:textAppearance="?android:attr/textAppearanceListItem" />
|
||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||
android:textSize="16sp" />
|
||||
</LinearLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/settings"
|
||||
style="@style/HomeCard.Tertiary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/HomeCard.Tertiary">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:layout_gravity="center_vertical">
|
||||
android:padding="16dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
|
|
@ -250,16 +251,16 @@
|
|||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/issue"
|
||||
style="@style/HomeCard.Tertiary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/HomeCard.Tertiary">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:layout_gravity="center_vertical">
|
||||
android:padding="16dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
|
|
@ -272,8 +273,8 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:text="@string/report_issue"
|
||||
android:textSize="16sp"
|
||||
android:textAppearance="?android:attr/textAppearanceListItem" />
|
||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||
android:textSize="16sp" />
|
||||
</LinearLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
|
|
|
|||
|
|
@ -38,10 +38,10 @@
|
|||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
android:elevation="0dp"
|
||||
app:layout_scrollFlags="scroll|enterAlways"
|
||||
app:layout_scrollEffect="none" />
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
app:layout_scrollEffect="none"
|
||||
app:layout_scrollFlags="scroll|enterAlways" />
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tab_layout"
|
||||
|
|
@ -54,11 +54,25 @@
|
|||
app:tabMode="scrollable" />
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/view_pager"
|
||||
<FrameLayout
|
||||
android:layout_width="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
|
||||
android:id="@+id/fab"
|
||||
|
|
@ -68,6 +82,6 @@
|
|||
android:layout_margin="16dp"
|
||||
android:contentDescription="@string/add_module_to_user"
|
||||
android:src="@drawable/ic_baseline_add_24"
|
||||
android:visibility="invisible"
|
||||
android:visibility="gone"
|
||||
app:layout_fitsSystemWindowsInsets="bottom" />
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
~ Copyright (C) 2020 EdXposed 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"
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@ dependencies {
|
|||
implementation("com.android.tools.build:apksig:$agpVersion")
|
||||
implementation("org.apache.commons:commons-lang3:3.12.0")
|
||||
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"))
|
||||
implementation(project(":hiddenapi-bridge"))
|
||||
implementation(project(":manager-service"))
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ package org.lsposed.lspd.service;
|
|||
|
||||
import static android.content.Context.BIND_AUTO_CREATE;
|
||||
import static org.lsposed.lspd.service.ServiceManager.TAG;
|
||||
import static org.lsposed.lspd.service.ServiceManager.getExecutorService;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.INotificationManager;
|
||||
|
|
@ -406,10 +407,10 @@ public class LSPManagerService extends ILSPManagerService.Stub {
|
|||
// we do it by cancelling the launch (return false)
|
||||
// and start activity in a new thread
|
||||
pendingManager = true;
|
||||
new Thread(() -> {
|
||||
getExecutorService().submit(() -> {
|
||||
ensureWebViewPermission();
|
||||
stopAndStartActivity(pkgName, intent, true);
|
||||
}).start();
|
||||
});
|
||||
Log.d(TAG, "requested to launch manager");
|
||||
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.ServiceManager.TAG;
|
||||
import static org.lsposed.lspd.service.ServiceManager.getExecutorService;
|
||||
|
||||
import android.app.IApplicationThread;
|
||||
import android.content.IIntentReceiver;
|
||||
|
|
@ -216,7 +217,7 @@ public class LSPosedService extends ILSPosedService.Stub {
|
|||
var receiver = new IIntentReceiver.Stub() {
|
||||
@Override
|
||||
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 {
|
||||
ActivityManagerService.finishReceiver(this, resultCode, data, extras, false, intent.getFlags());
|
||||
} catch (Throwable e) {
|
||||
|
|
@ -241,7 +242,7 @@ public class LSPosedService extends ILSPosedService.Stub {
|
|||
ActivityManagerService.registerReceiver("android", null, new IIntentReceiver.Stub() {
|
||||
@Override
|
||||
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 {
|
||||
ActivityManagerService.finishReceiver(this, resultCode, data, extras, false, intent.getFlags());
|
||||
} catch (Throwable e) {
|
||||
|
|
@ -263,7 +264,7 @@ public class LSPosedService extends ILSPosedService.Stub {
|
|||
ActivityManagerService.registerReceiver("android", null, new IIntentReceiver.Stub() {
|
||||
@Override
|
||||
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 {
|
||||
ActivityManagerService.finishReceiver(this, resultCode, data, extras, false, intent.getFlags());
|
||||
} catch (Throwable e) {
|
||||
|
|
@ -287,7 +288,7 @@ public class LSPosedService extends ILSPosedService.Stub {
|
|||
ActivityManagerService.registerReceiver("android", null, new IIntentReceiver.Stub() {
|
||||
@Override
|
||||
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 {
|
||||
ActivityManagerService.finishReceiver(this, resultCode, data, extras, false, intent.getFlags());
|
||||
} catch (Throwable e) {
|
||||
|
|
@ -309,14 +310,14 @@ public class LSPosedService extends ILSPosedService.Stub {
|
|||
ActivityManagerService.registerReceiver("android", null, new IIntentReceiver.Stub() {
|
||||
@Override
|
||||
public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
|
||||
new Thread(() -> {
|
||||
getExecutorService().submit(() -> {
|
||||
try {
|
||||
var am = ActivityManagerService.getActivityManager();
|
||||
if (am != null) am.setActivityController(null, false);
|
||||
} catch (Throwable e) {
|
||||
Log.e(TAG, "setActivityController", e);
|
||||
}
|
||||
}).start();
|
||||
});
|
||||
try {
|
||||
ActivityManagerService.finishReceiver(this, resultCode, data, extras, false, intent.getFlags());
|
||||
} catch (Throwable e) {
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.github.xposed.xposedservice.utils.ParceledListSlice;
|
||||
|
||||
|
|
@ -134,14 +135,15 @@ public class PackageService {
|
|||
res.addAll(pm.getInstalledPackages(flags, user.id).getList());
|
||||
}
|
||||
if (filterNoProcess) {
|
||||
res.removeIf(packageInfo -> {
|
||||
return new ParceledListSlice<>(res.parallelStream().filter(packageInfo -> {
|
||||
try {
|
||||
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) {
|
||||
return false;
|
||||
Log.w(TAG, "filter failed", e);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}).collect(Collectors.toList()));
|
||||
}
|
||||
return new ParceledListSlice<>(res);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,9 @@ import org.lsposed.lspd.BuildConfig;
|
|||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import hidden.HiddenApiBridge;
|
||||
|
||||
|
|
@ -47,6 +50,12 @@ public class ServiceManager {
|
|||
private static LSPSystemServerService systemServerService = 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) {
|
||||
while (android.os.ServiceManager.getService(name) == null) {
|
||||
try {
|
||||
|
|
|
|||
Loading…
Reference in New Issue