diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6467c42e..0ceadef9 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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") diff --git a/app/src/main/java/org/lsposed/manager/App.java b/app/src/main/java/org/lsposed/manager/App.java index 5c23be86..624cda23 100644 --- a/app/src/main/java/org/lsposed/manager/App.java +++ b/app/src/main/java/org/lsposed/manager/App.java @@ -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")) { diff --git a/app/src/main/java/org/lsposed/manager/ConfigManager.java b/app/src/main/java/org/lsposed/manager/ConfigManager.java index 596f0ea0..11dc189e 100644 --- a/app/src/main/java/org/lsposed/manager/ConfigManager.java +++ b/app/src/main/java/org/lsposed/manager/ConfigManager.java @@ -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 applications) { + public static boolean setModuleScope(String packageName, Set applications) { try { List list = new ArrayList<>(); applications.forEach(application -> { diff --git a/app/src/main/java/org/lsposed/manager/adapters/AppHelper.java b/app/src/main/java/org/lsposed/manager/adapters/AppHelper.java index b39e5959..dea21987 100644 --- a/app/src/main/java/org/lsposed/manager/adapters/AppHelper.java +++ b/app/src/main/java/org/lsposed/manager/adapters/AppHelper.java @@ -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 denyList; private static List appList; + private static final ConcurrentHashMap 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 getAppList(boolean force) { + synchronized public static List getAppList(boolean force) { if (appList == null || force) { appList = ConfigManager.getInstalledPackagesFromAllUsers(PackageManager.GET_META_DATA | PackageManager.MATCH_UNINSTALLED_PACKAGES, true); } return appList; } - public static List getDenyList(boolean force) { + synchronized public static List 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)); + } } diff --git a/app/src/main/java/org/lsposed/manager/adapters/ScopeAdapter.java b/app/src/main/java/org/lsposed/manager/adapters/ScopeAdapter.java index ae545d45..a66194ae 100644 --- a/app/src/main/java/org/lsposed/manager/adapters/ScopeAdapter.java +++ b/app/src/main/java/org/lsposed/manager/adapters/ScopeAdapter.java @@ -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 implements Filterable { +public class ScopeAdapter extends EmptyStateRecyclerView.EmptyStateAdapter implements Filterable { private final Activity activity; private final AppListFragment fragment; @@ -102,11 +102,11 @@ public class ScopeAdapter extends SimpleStatefulAdaptor private final ModuleUtil.InstalledModule module; - private final HashSet recommendedList = new HashSet<>(); - private final HashSet checkedList = new HashSet<>(); - private final ConcurrentLinkedQueue searchList = new ConcurrentLinkedQueue<>(); - private final List showList = new ArrayList<>(); - private final List denyList = new ArrayList<>(); + private Set recommendedList = new HashSet<>(); + private Set checkedList = new HashSet<>(); + private List searchList = new ArrayList<>(); + private List showList = new ArrayList<>(); + private List denyList = new ArrayList<>(); private final SwitchBar.OnCheckedChangeListener switchBarOnCheckedChangeListener = new SwitchBar.OnCheckedChangeListener() { @Override @@ -119,21 +119,6 @@ public class ScopeAdapter extends SimpleStatefulAdaptor 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 return preferences.getBoolean("filter_system_apps", true) && (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; } - private void sortApps(List list) { + private int sortApps(AppInfo x, AppInfo y) { Comparator comparator = AppHelper.getAppListComparator(preferences.getInt("list_sort", 0), pm); Comparator frameworkComparator = (a, b) -> { if (a.packageName.equals("android") == b.packageName.equals("android")) { @@ -210,23 +195,26 @@ public class ScopeAdapter extends SimpleStatefulAdaptor 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 .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 } } + @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 } }); - 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 fragment.binding.masterSwitch.setOnCheckedChangeListener(switchBarOnCheckedChangeListener); loadAppListHandler.post(() -> { List appList = AppHelper.getAppList(force); - checkedList.clear(); - recommendedList.clear(); - denyList.clear(); - denyList.addAll(AppHelper.getDenyList(force)); - var tmpList = new ArrayList(); - checkedList.addAll(ConfigManager.getModuleScope(module.packageName)); - HashSet installedList = new HashSet<>(); + denyList = AppHelper.getDenyList(force); + var tmpRecList = new HashSet(); + var tmpChkList = new HashSet<>(ConfigManager.getModuleScope(module.packageName)); + final var tmpList = new ArrayList(); + final HashSet installedList = new HashSet<>(); List 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 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 @Override protected void publishResults(CharSequence constraint, FilterResults results) { - showList.clear(); //noinspection unchecked - showList.addAll((Collection) results.values); - notifyDataSetChanged(); + showList = (List) results.values; } } @@ -662,7 +661,6 @@ public class ScopeAdapter extends SimpleStatefulAdaptor if (!recommendedList.isEmpty()) { builder.setPositiveButton(android.R.string.ok, (dialog, which) -> { checkRecommended(); - notifyDataSetChanged(); }); } else { builder.setPositiveButton(android.R.string.cancel, null); diff --git a/app/src/main/java/org/lsposed/manager/ui/fragment/AppListFragment.java b/app/src/main/java/org/lsposed/manager/ui/fragment/AppListFragment.java index ec4f0b66..ad2da238 100644 --- a/app/src/main/java/org/lsposed/manager/ui/fragment/AppListFragment.java +++ b/app/src/main/java/org/lsposed/manager/ui/fragment/AppListFragment.java @@ -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())); diff --git a/app/src/main/java/org/lsposed/manager/ui/fragment/HomeFragment.java b/app/src/main/java/org/lsposed/manager/ui/fragment/HomeFragment.java index 842f05fb..80d3032a 100644 --- a/app/src/main/java/org/lsposed/manager/ui/fragment/HomeFragment.java +++ b/app/src/main/java/org/lsposed/manager/ui/fragment/HomeFragment.java @@ -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 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; } } diff --git a/app/src/main/java/org/lsposed/manager/ui/fragment/ModulesFragment.java b/app/src/main/java/org/lsposed/manager/ui/fragment/ModulesFragment.java index ab881c7c..a6e2607e 100644 --- a/app/src/main/java/org/lsposed/manager/ui/fragment/ModulesFragment.java +++ b/app/src/main/java/org/lsposed/manager/ui/fragment/ModulesFragment.java @@ -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 users = ConfigManager.getUsers(); protected FragmentPagerBinding binding; protected SearchView searchView; private SearchView.OnQueryTextListener searchListener; + private final ArrayList adapters = new ArrayList<>(); - private final ArrayList 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 implements Filterable { - private final ConcurrentLinkedQueue searchList = new ConcurrentLinkedQueue<>(); - private final List showList = new ArrayList<>(); + private List searchList = new ArrayList<>(); + private List showList = new ArrayList<>(); private final UserInfo user; private final boolean isPick; private boolean isLoaded; private View.OnClickListener onPickListener; - private Predicate 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 filter) { - this.customFilter = filter; - } - public void setOnPickListener(View.OnClickListener onPickListener) { this.onPickListener = onPickListener; } - public List 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 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 cmp = AppHelper.getAppListComparator(0, pm); + setLoaded(false); + var tmpList = new ArrayList(); + 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 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) results.values); - isLoaded = true; - notifyDataSetChanged(); + showList = (List) results.values; } } } diff --git a/app/src/main/java/org/lsposed/manager/ui/fragment/RepoItemFragment.java b/app/src/main/java/org/lsposed/manager/ui/fragment/RepoItemFragment.java index c7265dad..03531755 100644 --- a/app/src/main/java/org/lsposed/manager/ui/fragment/RepoItemFragment.java +++ b/app/src/main/java/org/lsposed/manager/ui/fragment/RepoItemFragment.java @@ -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 { - private List items; + private class ReleaseAdapter extends EmptyStateRecyclerView.EmptyStateAdapter { + private List 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; diff --git a/app/src/main/java/org/lsposed/manager/ui/widget/EmptyStateRecyclerView.java b/app/src/main/java/org/lsposed/manager/ui/widget/EmptyStateRecyclerView.java index 102010a2..5f1d89f5 100644 --- a/app/src/main/java/org/lsposed/manager/ui/widget/EmptyStateRecyclerView.java +++ b/app/src/main/java/org/lsposed/manager/ui/widget/EmptyStateRecyclerView.java @@ -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 extends SimpleStatefulAdaptor { abstract public boolean isLoaded(); } diff --git a/app/src/main/java/org/lsposed/manager/util/ModuleUtil.java b/app/src/main/java/org/lsposed/manager/util/ModuleUtil.java index 77479c42..de9f56b1 100644 --- a/app/src/main/java/org/lsposed/manager/util/ModuleUtil.java +++ b/app/src/main/java/org/lsposed/manager/util/ModuleUtil.java @@ -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, InstalledModule> getModules() { - return installedModules; + @Nullable + synchronized public Map, 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 { diff --git a/app/src/main/java/org/lsposed/manager/util/SimpleStatefulAdaptor.java b/app/src/main/java/org/lsposed/manager/util/SimpleStatefulAdaptor.java index d027c827..3d989ef5 100644 --- a/app/src/main/java/org/lsposed/manager/util/SimpleStatefulAdaptor.java +++ b/app/src/main/java/org/lsposed/manager/util/SimpleStatefulAdaptor.java @@ -33,6 +33,7 @@ public abstract class SimpleStatefulAdaptor e super.onViewRecycled(holder); } + @CallSuper @Override public final void onBindViewHolder(@NonNull T holder, int position, @NonNull List payloads) { var state = states.remove(holder.getItemId()); diff --git a/app/src/main/res/layout/dialog_recyclerview.xml b/app/src/main/res/layout/dialog_recyclerview.xml index 18571042..9fc055c7 100644 --- a/app/src/main/res/layout/dialog_recyclerview.xml +++ b/app/src/main/res/layout/dialog_recyclerview.xml @@ -16,12 +16,29 @@ ~ ~ Copyright (C) 2021 LSPosed Contributors --> - + + + + + + diff --git a/app/src/main/res/layout/fragment_app_list.xml b/app/src/main/res/layout/fragment_app_list.xml index 57451313..30788a1c 100644 --- a/app/src/main/res/layout/fragment_app_list.xml +++ b/app/src/main/res/layout/fragment_app_list.xml @@ -73,7 +73,7 @@ android:layout_height="match_parent" android:id="@+id/swipeRefreshLayout"> - + app:logo="@drawable/ic_launcher" + app:titleMarginStart="48dp" /> @@ -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"> + android:layout_height="wrap_content"> + android:textColor="@color/primary_text_material_inverse" + android:textSize="16sp" /> + android:layout_height="wrap_content"> + android:textAppearance="?android:attr/textAppearanceListItem" + android:textSize="16sp" /> + android:layout_height="wrap_content"> + android:textAppearance="?android:attr/textAppearanceListItem" + android:textSize="16sp" /> + android:layout_height="wrap_content"> + android:padding="16dp"> + android:textAppearance="?android:attr/textAppearanceListItem" + android:textSize="16sp" /> + android:layout_height="wrap_content"> + android:padding="16dp"> + android:layout_height="wrap_content"> + android:padding="16dp"> + android:textAppearance="?android:attr/textAppearanceListItem" + android:textSize="16sp" /> diff --git a/app/src/main/res/layout/fragment_pager.xml b/app/src/main/res/layout/fragment_pager.xml index f74d1cf6..6365ebf0 100644 --- a/app/src/main/res/layout/fragment_pager.xml +++ b/app/src/main/res/layout/fragment_pager.xml @@ -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" /> - + app:layout_behavior="@string/appbar_scrolling_view_behavior"> + + + + + diff --git a/app/src/main/res/layout/item_repo_recyclerview.xml b/app/src/main/res/layout/item_repo_recyclerview.xml index d034cf79..16edf94d 100644 --- a/app/src/main/res/layout/item_repo_recyclerview.xml +++ b/app/src/main/res/layout/item_repo_recyclerview.xml @@ -17,7 +17,7 @@ ~ Copyright (C) 2020 EdXposed Contributors ~ Copyright (C) 2021 LSPosed Contributors --> - { + getExecutorService().submit(() -> { ensureWebViewPermission(); stopAndStartActivity(pkgName, intent, true); - }).start(); + }); Log.d(TAG, "requested to launch manager"); return false; } diff --git a/core/src/main/java/org/lsposed/lspd/service/LSPosedService.java b/core/src/main/java/org/lsposed/lspd/service/LSPosedService.java index 9624f716..2d828458 100644 --- a/core/src/main/java/org/lsposed/lspd/service/LSPosedService.java +++ b/core/src/main/java/org/lsposed/lspd/service/LSPosedService.java @@ -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) { diff --git a/core/src/main/java/org/lsposed/lspd/service/PackageService.java b/core/src/main/java/org/lsposed/lspd/service/PackageService.java index b88bea78..8777a873 100644 --- a/core/src/main/java/org/lsposed/lspd/service/PackageService.java +++ b/core/src/main/java/org/lsposed/lspd/service/PackageService.java @@ -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); } diff --git a/core/src/main/java/org/lsposed/lspd/service/ServiceManager.java b/core/src/main/java/org/lsposed/lspd/service/ServiceManager.java index 5e0f7280..64c07576 100644 --- a/core/src/main/java/org/lsposed/lspd/service/ServiceManager.java +++ b/core/src/main/java/org/lsposed/lspd/service/ServiceManager.java @@ -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 {