Reload ModulesFragment after user removed or added (#1465)

Co-authored-by: LoveSy <shana@zju.edu.cn>
This commit is contained in:
Howard Wu 2021-12-04 21:44:40 +08:00 committed by GitHub
parent 61c447091f
commit 6a735d2064
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 185 additions and 75 deletions

View File

@ -20,6 +20,7 @@
package org.lsposed.manager; package org.lsposed.manager;
import android.annotation.SuppressLint;
import android.app.Application; import android.app.Application;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
@ -116,6 +117,10 @@ public class App extends Application {
} }
public static final String TAG = "LSPosedManager"; public static final String TAG = "LSPosedManager";
private static final String ACTION_USER_ADDED = "android.intent.action.USER_ADDED";
private static final String ACTION_USER_REMOVED = "android.intent.action.USER_REMOVED";
private static final String ACTION_USER_INFO_CHANGED = "android.intent.action.USER_INFO_CHANGED";
private static final String EXTRA_REMOVED_FOR_ALL_USERS = "android.intent.extra.REMOVED_FOR_ALL_USERS";
private static App instance = null; private static App instance = null;
private static OkHttpClient okHttpClient; private static OkHttpClient okHttpClient;
private static Cache okHttpCache; private static Cache okHttpCache;
@ -138,6 +143,7 @@ public class App extends Application {
return !Process.isApplicationUid(Process.myUid()); return !Process.isApplicationUid(Process.myUid());
} }
@SuppressLint("WrongConstant")
private void setCrashReport() { private void setCrashReport() {
Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> { Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
@ -182,17 +188,39 @@ public class App extends Application {
DayNightDelegate.setDefaultNightMode(ThemeUtil.getDarkTheme()); DayNightDelegate.setDefaultNightMode(ThemeUtil.getDarkTheme());
LocaleDelegate.setDefaultLocale(getLocale()); LocaleDelegate.setDefaultLocale(getLocale());
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("org.lsposed.manager.NOTIFICATION");
registerReceiver(new BroadcastReceiver() { registerReceiver(new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent inIntent) {
int userId = intent.getIntExtra(Intent.EXTRA_USER, 0); var intent = (Intent) inIntent.getParcelableExtra(Intent.EXTRA_INTENT);
String packageName = intent.getStringExtra("android.intent.extra.PACKAGES"); Log.d(TAG, "onReceive: " + intent);
boolean packageFullyRemoved = intent.getBooleanExtra(Intent.ACTION_PACKAGE_FULLY_REMOVED, false); switch (intent.getAction()) {
if (packageName != null) { case Intent.ACTION_PACKAGE_ADDED:
ModuleUtil.getInstance().reloadSingleModule(packageName, userId, packageFullyRemoved); case Intent.ACTION_PACKAGE_CHANGED:
case Intent.ACTION_PACKAGE_FULLY_REMOVED:
case Intent.ACTION_UID_REMOVED: {
var userId = intent.getIntExtra(Intent.EXTRA_USER, 0);
var packageName = intent.getStringExtra("android.intent.extra.PACKAGES");
var packageRemovedForAllUsers = intent.getBooleanExtra(EXTRA_REMOVED_FOR_ALL_USERS, false);
var isXposedModule = intent.getBooleanExtra("isXposedModule", false);
if (packageName != null) {
if (isXposedModule)
ModuleUtil.getInstance().reloadSingleModule(packageName, userId, packageRemovedForAllUsers);
else
App.getExecutorService().submit(() -> AppHelper.getAppList(true));
}
break;
}
case ACTION_USER_ADDED:
case ACTION_USER_REMOVED:
case ACTION_USER_INFO_CHANGED: {
App.getExecutorService().submit(() -> ModuleUtil.getInstance().reloadInstalledModules());
break;
}
} }
} }
}, new IntentFilter(Intent.ACTION_PACKAGE_CHANGED)); }, intentFilter);
UpdateUtil.loadRemoteVersion(); UpdateUtil.loadRemoteVersion();

View File

@ -20,6 +20,7 @@
package org.lsposed.manager.adapters; package org.lsposed.manager.adapters;
import android.annotation.SuppressLint;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
@ -44,6 +45,7 @@ public class AppHelper {
private static List<PackageInfo> appList; private static List<PackageInfo> appList;
private static final ConcurrentHashMap<PackageInfo, CharSequence> appLabel = new ConcurrentHashMap<>(); private static final ConcurrentHashMap<PackageInfo, CharSequence> appLabel = new ConcurrentHashMap<>();
@SuppressLint("WrongConstant")
public static Intent getSettingsIntent(String packageName, int userId) { public static Intent getSettingsIntent(String packageName, int userId) {
Intent intentToResolve = new Intent(Intent.ACTION_MAIN); Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
intentToResolve.addCategory(SETTINGS_CATEGORY); intentToResolve.addCategory(SETTINGS_CATEGORY);
@ -63,6 +65,7 @@ public class AppHelper {
return intent; return intent;
} }
@SuppressLint("WrongConstant")
public static Intent getLaunchIntentForPackage(String packageName, int userId) { public static Intent getLaunchIntentForPackage(String packageName, int userId) {
Intent intentToResolve = new Intent(Intent.ACTION_MAIN); Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
intentToResolve.addCategory(Intent.CATEGORY_INFO); intentToResolve.addCategory(Intent.CATEGORY_INFO);

View File

@ -266,6 +266,7 @@ public class ScopeAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<Scope
return true; return true;
} }
@SuppressLint("WrongConstant")
public boolean onContextItemSelected(@NonNull MenuItem item) { public boolean onContextItemSelected(@NonNull MenuItem item) {
ApplicationInfo info = selectedInfo; ApplicationInfo info = selectedInfo;
if (info == null) { if (info == null) {

View File

@ -20,7 +20,6 @@
package org.lsposed.manager.ui.fragment; package org.lsposed.manager.ui.fragment;
import static android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS; import static android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS;
import static androidx.recyclerview.widget.RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Intent; import android.content.Intent;
@ -37,6 +36,7 @@ import android.text.TextUtils;
import android.text.style.ForegroundColorSpan; import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan; import android.text.style.StyleSpan;
import android.text.style.TypefaceSpan; import android.text.style.TypefaceSpan;
import android.util.SparseArray;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
@ -72,9 +72,9 @@ import org.lsposed.manager.App;
import org.lsposed.manager.ConfigManager; import org.lsposed.manager.ConfigManager;
import org.lsposed.manager.R; import org.lsposed.manager.R;
import org.lsposed.manager.adapters.AppHelper; import org.lsposed.manager.adapters.AppHelper;
import org.lsposed.manager.databinding.SwiperefreshRecyclerviewBinding;
import org.lsposed.manager.databinding.FragmentPagerBinding; import org.lsposed.manager.databinding.FragmentPagerBinding;
import org.lsposed.manager.databinding.ItemModuleBinding; import org.lsposed.manager.databinding.ItemModuleBinding;
import org.lsposed.manager.databinding.SwiperefreshRecyclerviewBinding;
import org.lsposed.manager.repo.RepoLoader; import org.lsposed.manager.repo.RepoLoader;
import org.lsposed.manager.ui.dialog.BlurBehindDialogBuilder; import org.lsposed.manager.ui.dialog.BlurBehindDialogBuilder;
import org.lsposed.manager.ui.widget.EmptyStateRecyclerView; import org.lsposed.manager.ui.widget.EmptyStateRecyclerView;
@ -95,12 +95,12 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
private static final PackageManager pm = App.getInstance().getPackageManager(); private static final PackageManager pm = App.getInstance().getPackageManager();
private static final ModuleUtil moduleUtil = ModuleUtil.getInstance(); private static final ModuleUtil moduleUtil = ModuleUtil.getInstance();
private static final RepoLoader repoLoader = RepoLoader.getInstance(); private static final RepoLoader repoLoader = RepoLoader.getInstance();
private static final List<UserInfo> users = ConfigManager.getUsers();
protected FragmentPagerBinding binding; protected FragmentPagerBinding binding;
protected SearchView searchView; protected SearchView searchView;
private SearchView.OnQueryTextListener searchListener; private SearchView.OnQueryTextListener searchListener;
final ArrayList<ModuleAdapter> adapters = new ArrayList<>(); SparseArray<ModuleAdapter> adapters = new SparseArray<>();
PagerAdapter pagerAdapter = null;
private ModuleUtil.InstalledModule selectedModule; private ModuleUtil.InstalledModule selectedModule;
@ -110,24 +110,22 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
searchListener = new SearchView.OnQueryTextListener() { searchListener = new SearchView.OnQueryTextListener() {
@Override @Override
public boolean onQueryTextSubmit(String query) { public boolean onQueryTextSubmit(String query) {
adapters.forEach(adapter -> adapter.getFilter().filter(query)); forEachAdaptor(adapter -> adapter.getFilter().filter(query));
return false; return false;
} }
@Override @Override
public boolean onQueryTextChange(String query) { public boolean onQueryTextChange(String query) {
adapters.forEach(adapter -> adapter.getFilter().filter(query)); forEachAdaptor(adapter -> adapter.getFilter().filter(query));
return false; return false;
} }
}; };
}
if (users != null) { private void forEachAdaptor(Consumer<? super ModuleAdapter> action) {
for (var user : users) { var snapshot = adapters;
var adapter = new ModuleAdapter(user); for (var i = 0; i < snapshot.size(); ++i) {
adapter.setHasStableIds(true); action.accept(snapshot.valueAt(i));
adapter.setStateRestorationPolicy(PREVENT_WHEN_EMPTY);
adapters.add(adapter);
}
} }
} }
@ -149,7 +147,8 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
binding = FragmentPagerBinding.inflate(inflater, container, false); binding = FragmentPagerBinding.inflate(inflater, container, false);
binding.appBar.setLiftable(true); binding.appBar.setLiftable(true);
setupToolbar(binding.toolbar, binding.clickView, R.string.Modules, R.menu.menu_modules); setupToolbar(binding.toolbar, binding.clickView, R.string.Modules, R.menu.menu_modules);
binding.viewPager.setAdapter(new PagerAdapter(this)); pagerAdapter = new PagerAdapter(this);
binding.viewPager.setAdapter(pagerAdapter);
binding.viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { binding.viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override @Override
public void onPageSelected(int position) { public void onPageSelected(int position) {
@ -159,29 +158,22 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
new TabLayoutMediator(binding.tabLayout, binding.viewPager, (tab, position) -> { new TabLayoutMediator(binding.tabLayout, binding.viewPager, (tab, position) -> {
if (position < adapters.size()) { if (position < adapters.size()) {
tab.setText(adapters.get(position).getUser().name); tab.setText(adapters.valueAt(position).getUser().name);
} }
}).attach(); }).attach();
if (users != null && users.size() != 1) { binding.tabLayout.addOnLayoutChangeListener((view, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
binding.viewPager.setUserInputEnabled(true); ViewGroup vg = (ViewGroup) binding.tabLayout.getChildAt(0);
binding.tabLayout.setVisibility(View.VISIBLE); int tabLayoutWidth = IntStream.range(0, binding.tabLayout.getTabCount()).map(i -> vg.getChildAt(i).getWidth()).sum();
binding.tabLayout.addOnLayoutChangeListener((view, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { if (tabLayoutWidth <= binding.getRoot().getWidth()) {
ViewGroup vg = (ViewGroup) binding.tabLayout.getChildAt(0); binding.tabLayout.setTabMode(TabLayout.MODE_FIXED);
int tabLayoutWidth = IntStream.range(0, binding.tabLayout.getTabCount()).map(i -> vg.getChildAt(i).getWidth()).sum(); binding.tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
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.fab.setOnClickListener(v -> { binding.fab.setOnClickListener(v -> {
var bundle = new Bundle(); var bundle = new Bundle();
var user = adapters.get(binding.viewPager.getCurrentItem()).getUser(); var user = adapters.valueAt(binding.viewPager.getCurrentItem()).getUser();
bundle.putParcelable("userInfo", user); bundle.putParcelable("userInfo", user);
var f = new RecyclerViewDialogFragment(); var f = new RecyclerViewDialogFragment();
f.setArguments(bundle); f.setArguments(bundle);
@ -190,7 +182,7 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
moduleUtil.addListener(this); moduleUtil.addListener(this);
repoLoader.addListener(this); repoLoader.addListener(this);
updateModuleSummary(); onModulesReloaded();
return binding.getRoot(); return binding.getRoot();
} }
@ -214,23 +206,48 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
adapters.forEach(ModuleAdapter::refresh); forEachAdaptor(ModuleAdapter::refresh);
} }
@Override @Override
public void onSingleModuleReloaded(ModuleUtil.InstalledModule module) { public void onSingleModuleReloaded(ModuleUtil.InstalledModule module) {
adapters.forEach(ModuleAdapter::refresh); forEachAdaptor(ModuleAdapter::refresh);
} }
@Override @Override
public void onModulesReloaded() { public void onModulesReloaded() {
adapters.forEach(ModuleAdapter::refresh); var users = moduleUtil.getUsers();
if (users == null) return;
if (users.size() != 1) {
binding.viewPager.setUserInputEnabled(true);
binding.tabLayout.setVisibility(View.VISIBLE);
binding.fab.show();
} else {
binding.viewPager.setUserInputEnabled(false);
binding.tabLayout.setVisibility(View.GONE);
}
var tmp = new SparseArray<ModuleAdapter>(users.size());
var snapshot = adapters;
for (var user : users) {
if (snapshot.indexOfKey(user.id) >= 0) {
tmp.put(user.id, snapshot.get(user.id));
} else {
var adapter = new ModuleAdapter(user);
adapter.setHasStableIds(true);
tmp.put(user.id, adapter);
}
}
adapters = tmp;
forEachAdaptor(ModuleAdapter::refresh);
runOnUiThread(pagerAdapter::notifyDataSetChanged);
updateModuleSummary(); updateModuleSummary();
} }
@Override @Override
public void onRepoLoaded() { public void onRepoLoaded() {
adapters.forEach(ModuleAdapter::refresh); forEachAdaptor(ModuleAdapter::refresh);
} }
private void updateModuleSummary() { private void updateModuleSummary() {
@ -261,6 +278,7 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
.show(); .show();
} }
@SuppressLint("WrongConstant")
@Override @Override
public boolean onContextItemSelected(@NonNull MenuItem item) { public boolean onContextItemSelected(@NonNull MenuItem item) {
if (selectedModule == null) { if (selectedModule == null) {
@ -348,9 +366,9 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
if (fragment == null || arguments == null) { if (fragment == null || arguments == null) {
return null; return null;
} }
int position = arguments.getInt("position"); int userId = arguments.getInt("user_id");
binding = SwiperefreshRecyclerviewBinding.inflate(getLayoutInflater(), container, false); binding = SwiperefreshRecyclerviewBinding.inflate(getLayoutInflater(), container, false);
adapter = fragment.adapters.get(position); adapter = fragment.adapters.get(userId);
binding.recyclerView.setAdapter(adapter); binding.recyclerView.setAdapter(adapter);
binding.recyclerView.setLayoutManager(new LinearLayoutManager(requireActivity())); binding.recyclerView.setLayoutManager(new LinearLayoutManager(requireActivity()));
binding.swipeRefreshLayout.setOnRefreshListener(adapter::fullRefresh); binding.swipeRefreshLayout.setOnRefreshListener(adapter::fullRefresh);
@ -430,7 +448,7 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
@Override @Override
public Fragment createFragment(int position) { public Fragment createFragment(int position) {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putInt("position", position); bundle.putInt("user_id", adapters.keyAt(position));
Fragment fragment = new ModuleListFragment(); Fragment fragment = new ModuleListFragment();
fragment.setArguments(bundle); fragment.setArguments(bundle);
return fragment; return fragment;
@ -443,7 +461,12 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
@Override @Override
public long getItemId(int position) { public long getItemId(int position) {
return position; return adapters.keyAt(position);
}
@Override
public boolean containsItem(long itemId) {
return adapters.indexOfKey((int) itemId) >= 0;
} }
} }

View File

@ -30,11 +30,13 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.util.Pair; import androidx.core.util.Pair;
import org.lsposed.lspd.models.UserInfo;
import org.lsposed.manager.App; import org.lsposed.manager.App;
import org.lsposed.manager.ConfigManager; import org.lsposed.manager.ConfigManager;
import org.lsposed.manager.repo.RepoLoader; import org.lsposed.manager.repo.RepoLoader;
import org.lsposed.manager.repo.model.OnlineModule; import org.lsposed.manager.repo.model.OnlineModule;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@ -50,6 +52,7 @@ public final class ModuleUtil {
private final PackageManager pm; private final PackageManager pm;
private final Set<ModuleListener> listeners = ConcurrentHashMap.newKeySet(); private final Set<ModuleListener> listeners = ConcurrentHashMap.newKeySet();
private HashSet<String> enabledModules = new HashSet<>(); private HashSet<String> enabledModules = new HashSet<>();
private List<UserInfo> users = new ArrayList<>();
private Map<Pair<String, Integer>, InstalledModule> installedModules = new HashMap<>(); private Map<Pair<String, Integer>, InstalledModule> installedModules = new HashMap<>();
private boolean modulesLoaded = false; private boolean modulesLoaded = false;
@ -89,6 +92,7 @@ public final class ModuleUtil {
} }
Map<Pair<String, Integer>, InstalledModule> modules = new HashMap<>(); Map<Pair<String, Integer>, InstalledModule> modules = new HashMap<>();
var users = ConfigManager.getUsers();
for (PackageInfo pkg : ConfigManager.getInstalledPackagesFromAllUsers(PackageManager.GET_META_DATA, false)) { for (PackageInfo pkg : ConfigManager.getInstalledPackagesFromAllUsers(PackageManager.GET_META_DATA, false)) {
ApplicationInfo app = pkg.applicationInfo; ApplicationInfo app = pkg.applicationInfo;
@ -100,11 +104,18 @@ public final class ModuleUtil {
installedModules = modules; installedModules = modules;
this.users = users;
enabledModules = new HashSet<>(Arrays.asList(ConfigManager.getEnabledModules())); enabledModules = new HashSet<>(Arrays.asList(ConfigManager.getEnabledModules()));
modulesLoaded = true; modulesLoaded = true;
listeners.forEach(ModuleListener::onModulesReloaded); listeners.forEach(ModuleListener::onModulesReloaded);
} }
@Nullable
public List<UserInfo> getUsers() {
return modulesLoaded ? users : null;
}
public InstalledModule reloadSingleModule(String packageName, int userId) { public InstalledModule reloadSingleModule(String packageName, int userId) {
return reloadSingleModule(packageName, userId, false); return reloadSingleModule(packageName, userId, false);
} }

View File

@ -21,6 +21,7 @@ package org.lsposed.lspd.service;
import static org.lsposed.lspd.service.ServiceManager.TAG; import static org.lsposed.lspd.service.ServiceManager.TAG;
import android.annotation.SuppressLint;
import android.app.IActivityManager; import android.app.IActivityManager;
import android.app.IApplicationThread; import android.app.IApplicationThread;
import android.app.IServiceConnection; import android.app.IServiceConnection;
@ -101,17 +102,16 @@ public class ActivityManagerService {
return am.startUserInBackground(userId); return am.startUserInBackground(userId);
} }
@SuppressLint("NewApi")
public static Intent registerReceiver(String callerPackage, public static Intent registerReceiver(String callerPackage,
String callingFeatureId, IIntentReceiver receiver, IntentFilter filter, String callingFeatureId, IIntentReceiver receiver, IntentFilter filter,
String requiredPermission, int userId, int flags) throws RemoteException { String requiredPermission, int userId, int flags) throws RemoteException {
IActivityManager am = getActivityManager(); IActivityManager am = getActivityManager();
if (am == null || thread == null) return null; if (am == null || thread == null) return null;
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S || (Build.VERSION.SDK_INT == Build.VERSION_CODES.R && Build.VERSION.PREVIEW_SDK_INT != 0)) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && Build.VERSION.PREVIEW_SDK_INT != 0))
return am.registerReceiverWithFeature(thread, callerPackage, callingFeatureId, "null", receiver, filter, requiredPermission, userId, flags); return am.registerReceiverWithFeature(thread, callerPackage, callingFeatureId, "null", receiver, filter, requiredPermission, userId, flags);
} catch (Throwable ignored) { else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
return am.registerReceiverWithFeature(thread, callerPackage, callingFeatureId, receiver, filter, requiredPermission, userId, flags); return am.registerReceiverWithFeature(thread, callerPackage, callingFeatureId, receiver, filter, requiredPermission, userId, flags);
} else { } else {
return am.registerReceiver(thread, callerPackage, receiver, filter, requiredPermission, userId, flags); return am.registerReceiver(thread, callerPackage, receiver, filter, requiredPermission, userId, flags);

View File

@ -252,13 +252,11 @@ public class LSPManagerService extends ILSPManagerService.Stub {
} }
@SuppressLint("WrongConstant") @SuppressLint("WrongConstant")
public static void broadcastIntent(String modulePackageName, int moduleUserId, boolean packageFullyRemoved) { public static void broadcastIntent(Intent inIntent) {
Intent intent = new Intent(Intent.ACTION_PACKAGE_CHANGED); var intent = new Intent("org.lsposed.manager.NOTIFICATION");
intent.putExtra(Intent.EXTRA_INTENT, inIntent);
intent.addFlags(0x01000000); //Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND intent.addFlags(0x01000000); //Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
intent.addFlags(0x00400000); //Intent.FLAG_RECEIVER_FROM_SHELL intent.addFlags(0x00400000); //Intent.FLAG_RECEIVER_FROM_SHELL
intent.putExtra("android.intent.extra.PACKAGES", modulePackageName);
intent.putExtra(Intent.EXTRA_USER, moduleUserId);
intent.putExtra(Intent.ACTION_PACKAGE_FULLY_REMOVED, packageFullyRemoved);
intent.setPackage(BuildConfig.MANAGER_INJECTED_PKG_NAME); intent.setPackage(BuildConfig.MANAGER_INJECTED_PKG_NAME);
try { try {
ActivityManagerService.broadcastIntentWithFeature(null, intent, ActivityManagerService.broadcastIntentWithFeature(null, intent,

View File

@ -42,6 +42,10 @@ import java.util.Arrays;
public class LSPosedService extends ILSPosedService.Stub { public class LSPosedService extends ILSPosedService.Stub {
private static final int AID_NOBODY = 9999; private static final int AID_NOBODY = 9999;
private static final int USER_NULL = -10000; private static final int USER_NULL = -10000;
private static final String ACTION_USER_ADDED = "android.intent.action.USER_ADDED";
public static final String ACTION_USER_REMOVED = "android.intent.action.USER_REMOVED";
private static final String EXTRA_USER_HANDLE = "android.intent.extra.user_handle";
private static final String EXTRA_REMOVED_FOR_ALL_USERS = "android.intent.extra.REMOVED_FOR_ALL_USERS";
@Override @Override
public ILSPApplicationService requestApplicationService(int uid, int pid, String processName, IBinder heartBeat) { public ILSPApplicationService requestApplicationService(int uid, int pid, String processName, IBinder heartBeat) {
@ -79,7 +83,7 @@ public class LSPosedService extends ILSPosedService.Stub {
if (uid == AID_NOBODY || uid <= 0) return; if (uid == AID_NOBODY || uid <= 0) return;
int userId = intent.getIntExtra("android.intent.extra.user_handle", USER_NULL); int userId = intent.getIntExtra("android.intent.extra.user_handle", USER_NULL);
var intentAction = intent.getAction(); var intentAction = intent.getAction();
var allUsers = intent.getBooleanExtra("android.intent.extra.REMOVED_FOR_ALL_USERS", false); var allUsers = intent.getBooleanExtra(EXTRA_REMOVED_FOR_ALL_USERS, false);
if (userId == USER_NULL) userId = uid % PER_USER_RANGE; if (userId == USER_NULL) userId = uid % PER_USER_RANGE;
Uri uri = intent.getData(); Uri uri = intent.getData();
String moduleName = (uri != null) ? uri.getSchemeSpecificPart() : ConfigManager.getInstance().getModule(uid); String moduleName = (uri != null) ? uri.getSchemeSpecificPart() : ConfigManager.getInstance().getModule(uid);
@ -102,8 +106,8 @@ public class LSPosedService extends ILSPosedService.Stub {
// because we only care about when the apk is gone // because we only care about when the apk is gone
if (moduleName != null && allUsers) if (moduleName != null && allUsers)
if (ConfigManager.getInstance().removeModule(moduleName)) { if (ConfigManager.getInstance().removeModule(moduleName)) {
broadcastOrShowNotification(moduleName, userId, intent);
isXposedModule = true; isXposedModule = true;
broadcastAndShowNotification(moduleName, userId, intent, true);
} }
break; break;
} }
@ -115,8 +119,8 @@ public class LSPosedService extends ILSPosedService.Stub {
if (components != null && !Arrays.stream(components).reduce(false, (p, c) -> p || c.equals(moduleName), Boolean::logicalOr)) { if (components != null && !Arrays.stream(components).reduce(false, (p, c) -> p || c.equals(moduleName), Boolean::logicalOr)) {
return; return;
} }
broadcastAndShowNotification(moduleName, userId, intent, isXposedModule);
if (isXposedModule) { if (isXposedModule) {
broadcastOrShowNotification(moduleName, userId, intent);
// When installing a new Xposed module, we update the apk path to mark it as a // When installing a new Xposed module, we update the apk path to mark it as a
// module to send a broadcast when modules that have not been activated are // module to send a broadcast when modules that have not been activated are
// uninstalled. // uninstalled.
@ -132,8 +136,8 @@ public class LSPosedService extends ILSPosedService.Stub {
case Intent.ACTION_UID_REMOVED: { case Intent.ACTION_UID_REMOVED: {
// when a package is removed (rather than hide) for a single user // when a package is removed (rather than hide) for a single user
// (apk may still be there because of multi-user) // (apk may still be there because of multi-user)
broadcastAndShowNotification(moduleName, userId, intent, isXposedModule);
if (isXposedModule) { if (isXposedModule) {
broadcastOrShowNotification(moduleName, userId, intent);
// it will automatically remove obsolete scope from database // it will automatically remove obsolete scope from database
ConfigManager.getInstance().updateCache(); ConfigManager.getInstance().updateCache();
} else if (ConfigManager.getInstance().isUidHooked(uid)) { } else if (ConfigManager.getInstance().isUidHooked(uid)) {
@ -159,18 +163,34 @@ public class LSPosedService extends ILSPosedService.Stub {
} }
} }
private void broadcastOrShowNotification(String moduleName, int userId, Intent intent) { private void broadcastAndShowNotification(String packageName, int userId, Intent intent, boolean isXposedModule) {
Log.d(TAG, "module " + moduleName + " changed, dispatching to manager"); Log.d(TAG, "package " + packageName + " changed, dispatching to manager");
var internAction = intent.getAction(); var action = intent.getAction();
var allUsers = intent.getBooleanExtra("android.intent.extra.REMOVED_FOR_ALL_USERS", false); var allUsers = intent.getBooleanExtra(EXTRA_REMOVED_FOR_ALL_USERS, false);
LSPManagerService.broadcastIntent(moduleName, userId, allUsers); intent.putExtra("android.intent.extra.PACKAGES", packageName);
var enabledModules = ConfigManager.getInstance().enabledModules(); intent.putExtra(Intent.EXTRA_USER, userId);
var scope = ConfigManager.getInstance().getModuleScope(moduleName); intent.putExtra("isXposedModule", isXposedModule);
boolean systemModule = scope != null && LSPManagerService.broadcastIntent(intent);
scope.parallelStream().anyMatch(app -> app.packageName.equals("android")); if (isXposedModule) {
boolean enabled = Arrays.asList(enabledModules).contains(moduleName); var enabledModules = ConfigManager.getInstance().enabledModules();
if (!(Intent.ACTION_UID_REMOVED.equals(internAction) || Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(internAction) || allUsers)) var scope = ConfigManager.getInstance().getModuleScope(packageName);
LSPManagerService.showNotification(moduleName, userId, enabled, systemModule); boolean systemModule = scope != null &&
scope.parallelStream().anyMatch(app -> app.packageName.equals("android"));
boolean enabled = Arrays.asList(enabledModules).contains(packageName);
if (!(Intent.ACTION_UID_REMOVED.equals(action) || Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action) || allUsers))
LSPManagerService.showNotification(packageName, userId, enabled, systemModule);
}
}
synchronized public void dispatchUserChanged(Intent intent) {
if (intent == null) return;
int uid = intent.getIntExtra(EXTRA_USER_HANDLE, AID_NOBODY);
if (uid == AID_NOBODY || uid <= 0) return;
try {
LSPManagerService.broadcastIntent(intent);
} catch (Throwable e) {
Log.e(TAG, "dispatch user info changed", e);
}
} }
synchronized public void dispatchUserUnlocked(Intent intent) { synchronized public void dispatchUserUnlocked(Intent intent) {
@ -335,6 +355,31 @@ public class LSPosedService extends ILSPosedService.Stub {
Log.d(TAG, "registered boot receiver"); Log.d(TAG, "registered boot receiver");
} }
private void registerUserChangeReceiver() {
try {
IntentFilter userFilter = new IntentFilter();
userFilter.addAction(ACTION_USER_ADDED);
userFilter.addAction(ACTION_USER_REMOVED);
var receiver = new IIntentReceiver.Stub() {
@Override
public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
getExecutorService().submit(() -> dispatchUserChanged(intent));
try {
ActivityManagerService.finishReceiver(this, resultCode, data, extras, false, intent.getFlags());
} catch (Throwable e) {
Log.e(TAG, "finish receiver", e);
}
}
};
ActivityManagerService.registerReceiver("android", null, receiver, userFilter, null, -1, 0);
} catch (Throwable e) {
Log.e(TAG, "register user info change receiver", e);
}
Log.d(TAG, "registered user info change receiver");
}
@Override @Override
public void dispatchSystemServerContext(IBinder activityThread, IBinder activityToken, String api) { public void dispatchSystemServerContext(IBinder activityThread, IBinder activityToken, String api) {
Log.d(TAG, "received system context"); Log.d(TAG, "received system context");
@ -345,6 +390,7 @@ public class LSPosedService extends ILSPosedService.Stub {
registerConfigurationReceiver(); registerConfigurationReceiver();
registerSecretCodeReceiver(); registerSecretCodeReceiver();
registerBootCompleteReceiver(); registerBootCompleteReceiver();
registerUserChangeReceiver();
} }
@Override @Override