From 9c1bbd56066ff1126ec0f64d12fd1b506747eca9 Mon Sep 17 00:00:00 2001 From: LoveSy Date: Mon, 17 May 2021 05:21:51 +0800 Subject: [PATCH] [core] Fix package listener (#600) --- .../lspd/service/ActivityManagerService.java | 6 ++ .../lsposed/lspd/service/ConfigManager.java | 38 ++++--- .../lspd/service/LSPManagerService.java | 15 ++- .../lsposed/lspd/service/LSPosedService.java | 99 +++++++++++-------- .../lsposed/lspd/service/PackageReceiver.java | 16 +-- .../java/android/app/IActivityManager.java | 2 + 6 files changed, 112 insertions(+), 64 deletions(-) diff --git a/core/src/main/java/org/lsposed/lspd/service/ActivityManagerService.java b/core/src/main/java/org/lsposed/lspd/service/ActivityManagerService.java index 091370d4..c94df7a0 100644 --- a/core/src/main/java/org/lsposed/lspd/service/ActivityManagerService.java +++ b/core/src/main/java/org/lsposed/lspd/service/ActivityManagerService.java @@ -79,4 +79,10 @@ public class ActivityManagerService { if (am == null) return; am.forceStopPackage(packageName, userId); } + + public static boolean startUserInBackground(int userId) throws RemoteException { + IActivityManager am = getActivityManager(); + if (am == null) return false; + return am.startUserInBackground(userId); + } } diff --git a/core/src/main/java/org/lsposed/lspd/service/ConfigManager.java b/core/src/main/java/org/lsposed/lspd/service/ConfigManager.java index 047e8d28..ace0a3ef 100644 --- a/core/src/main/java/org/lsposed/lspd/service/ConfigManager.java +++ b/core/src/main/java/org/lsposed/lspd/service/ConfigManager.java @@ -62,13 +62,13 @@ import java.util.concurrent.ConcurrentHashMap; // This config manager assume uid won't change when our service is off. // Otherwise, user should maintain it manually. public class ConfigManager { + public static final int PER_USER_RANGE = 100000; + private static final String[] MANAGER_PERMISSIONS_TO_GRANT = new String[]{ "android.permission.INTERACT_ACROSS_USERS", "android.permission.WRITE_SECURE_SETTINGS" }; - private static final int PER_USER_RANGE = 100000; - static ConfigManager instance = null; private static final File basePath = new File("/data/adb/lspd"); @@ -393,6 +393,10 @@ public class ConfigManager { return !cachedScope.containsKey(scope) && !isManager(scope.uid); } + public boolean isUidHooked(int uid) { + return cachedScope.keySet().stream().reduce(false, (p, scope) -> p || scope.uid == uid, Boolean::logicalOr); + } + // This should only be called by manager, so we don't need to cache it public List getModuleScope(String packageName) { int mid = getModuleId(packageName); @@ -427,8 +431,11 @@ public class ConfigManager { if (count < 0) { count = db.updateWithOnConflict("modules", values, "module_pkg_name=?", new String[]{packageName}, SQLiteDatabase.CONFLICT_IGNORE); } - // Called by oneway binder - updateCaches(true); + if (count > 0) { + // Called by oneway binder + updateCaches(true); + return true; + } return count >= 0; } @@ -489,11 +496,11 @@ public class ConfigManager { } } - public boolean removeModule(String packageName) { - boolean res = removeModuleWithoutCache(packageName); - // called by oneway binder - updateCaches(true); - return res; + public void removeModule(String packageName) { + if (removeModuleWithoutCache(packageName)) { + // called by oneway binder + updateCaches(true); + } } private boolean removeModuleWithoutCache(String packageName) { @@ -545,11 +552,16 @@ public class ConfigManager { return true; } - public boolean removeApp(Application app) { - boolean res = removeAppWithoutCache(app); + public void uninstalledApp(Application app) { + if (removeAppWithoutCache(app)) { + // Called by oneway binder + cacheScopes(); + } + } + + public void updateAppCache() { // Called by oneway binder - updateCaches(true); - return res; + cacheScopes(); } private boolean removeAppWithoutCache(Application app) { diff --git a/core/src/main/java/org/lsposed/lspd/service/LSPManagerService.java b/core/src/main/java/org/lsposed/lspd/service/LSPManagerService.java index ed6cbae1..4fe6b2c7 100644 --- a/core/src/main/java/org/lsposed/lspd/service/LSPManagerService.java +++ b/core/src/main/java/org/lsposed/lspd/service/LSPManagerService.java @@ -150,7 +150,9 @@ public class LSPManagerService extends ILSPManagerService.Stub { @Override public boolean uninstallPackage(String packageName, int userId) throws RemoteException { try { - return PackageService.uninstallPackage(new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST), userId); + if (ActivityManagerService.startUserInBackground(userId)) + return PackageService.uninstallPackage(new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST), userId); + else return false; } catch (InterruptedException | InvocationTargetException | NoSuchMethodException | InstantiationException | IllegalAccessException e) { Log.e(TAG, e.getMessage(), e); return false; @@ -168,7 +170,14 @@ public class LSPManagerService extends ILSPManagerService.Stub { } @Override - public int installExistingPackageAsUser(String packageName, int userid) { - return PackageService.installExistingPackageAsUser(packageName, userid); + public int installExistingPackageAsUser(String packageName, int userId) { + try { + if (ActivityManagerService.startUserInBackground(userId)) + return PackageService.installExistingPackageAsUser(packageName, userId); + else return PackageService.INSTALL_FAILED_INTERNAL_ERROR; + } catch (Throwable e) { + Log.w(TAG, "install existing package as user: ", e); + return PackageService.INSTALL_FAILED_INTERNAL_ERROR; + } } } 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 02c7e586..c6538724 100644 --- a/core/src/main/java/org/lsposed/lspd/service/LSPosedService.java +++ b/core/src/main/java/org/lsposed/lspd/service/LSPosedService.java @@ -31,11 +31,13 @@ import android.util.Log; import java.util.Arrays; -import org.lsposed.lspd.Application; - +import static org.lsposed.lspd.service.ConfigManager.PER_USER_RANGE; import static org.lsposed.lspd.service.ServiceManager.TAG; public class LSPosedService extends ILSPosedService.Stub { + private static final int AID_NOBODY = 9999; + private static final int USER_NULL = -10000; + @Override public ILSPApplicationService requestApplicationService(int uid, int pid, String processName, IBinder heartBeat) { if (Binder.getCallingUid() != 1000) { @@ -54,62 +56,74 @@ public class LSPosedService extends ILSPosedService.Stub { return ServiceManager.requestApplicationService(uid, pid, heartBeat); } + /** + * This part is quite complex. + * For modules, we never care about its user id, we only care about its apk path. + * So we will only process module's removal when it's removed from all users. + * And FULLY_REMOVE is exactly the one. + *

+ * For applications, we care about its user id. + * So we will process application's removal when it's removed from every single user. + * However, PACKAGE_REMOVED will be triggered by `pm hide`, so we use UID_REMOVED instead. + */ + @Override public void dispatchPackageChanged(Intent intent) throws RemoteException { if (Binder.getCallingUid() != 1000 || intent == null) return; + int uid = intent.getIntExtra(Intent.EXTRA_UID, AID_NOBODY); + if (uid == AID_NOBODY || uid <= 0) return; + int userId = intent.getIntExtra("android.intent.extra.user_handle", USER_NULL); + if (userId == USER_NULL) userId = uid % PER_USER_RANGE; + Uri uri = intent.getData(); - String packageName = (uri != null) ? uri.getSchemeSpecificPart() : null; - if (packageName == null) { - Log.e(TAG, "Package name is null"); - return; - } - Log.d(TAG, "Package changed: " + packageName); - int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); - int userId = intent.getIntExtra(Intent.EXTRA_USER, -1); - if (intent.getAction().equals(Intent.ACTION_PACKAGE_FULLY_REMOVED) && uid > 0) { - if (userId == 0 || userId == -1) { - ConfigManager.getInstance().removeModule(packageName); - } - Application app = new Application(); - app.packageName = packageName; - app.userId = userId; - ConfigManager.getInstance().removeApp(app); - return; - } + String moduleName = (uri != null) ? uri.getSchemeSpecificPart() : null; - if (intent.getAction().equals(Intent.ACTION_PACKAGE_CHANGED)) { - // make sure that the change is for the complete package, not only a - // component - String[] components = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST); - if (components != null) { - boolean isForPackage = false; - for (String component : components) { - if (packageName.equals(component)) { - isForPackage = true; - break; - } - } - if (!isForPackage) - return; - } - } + ApplicationInfo applicationInfo = moduleName != null ? PackageService.getApplicationInfo(moduleName, PackageManager.GET_META_DATA, 0) : null; - ApplicationInfo applicationInfo = PackageService.getApplicationInfo(packageName, PackageManager.GET_META_DATA, 0); boolean isXposedModule = applicationInfo != null && applicationInfo.metaData != null && applicationInfo.metaData.containsKey("xposedminversion"); - if (isXposedModule) { - ConfigManager.getInstance().updateModuleApkPath(packageName, applicationInfo.sourceDir); - Log.d(TAG, "Updated module apk path: " + packageName); + Log.d(TAG, "Package changed: uid=" + uid + " userId=" + userId + " action=" + intent.getAction() + " isXposedModule=" + isXposedModule); - boolean enabled = Arrays.asList(ConfigManager.getInstance().enabledModules()).contains(packageName); + switch (intent.getAction()) { + case Intent.ACTION_PACKAGE_FULLY_REMOVED: { + // for module, remove module + // because we only care about when the apk is gone + if (moduleName != null) + ConfigManager.getInstance().removeModule(moduleName); + break; + } + case Intent.ACTION_PACKAGE_CHANGED: { + // when package is changed, we may need to update cache (module cache or process cache) + if (isXposedModule) { + ConfigManager.getInstance().updateModuleApkPath(moduleName, applicationInfo.sourceDir); + Log.d(TAG, "Updated module apk path: " + moduleName); + } else if (ConfigManager.getInstance().isUidHooked(uid)) { + // it will automatically remove obsolete app from database + ConfigManager.getInstance().updateAppCache(); + } + break; + } + case Intent.ACTION_UID_REMOVED: { + // when a package is removed (rather than hide) for a single user + // (apk may still be there because of multi-user) + if (ConfigManager.getInstance().isUidHooked(uid)) { + // it will automatically remove obsolete app from database + ConfigManager.getInstance().updateAppCache(); + } + break; + } + } + if (isXposedModule) { + boolean enabled = Arrays.asList(ConfigManager.getInstance().enabledModules()).contains(moduleName); Intent broadcastIntent = new Intent(enabled ? "org.lsposed.action.MODULE_UPDATED" : "org.lsposed.action.MODULE_NOT_ACTIVATAED"); broadcastIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); broadcastIntent.addFlags(0x01000000); broadcastIntent.addFlags(0x00400000); broadcastIntent.setData(intent.getData()); broadcastIntent.putExtras(intent.getExtras()); + broadcastIntent.putExtra(Intent.EXTRA_USER, userId); broadcastIntent.setComponent(ComponentName.unflattenFromString(ConfigManager.getInstance().getManagerPackageName() + "/.receivers.ServiceReceiver")); try { @@ -121,7 +135,8 @@ public class LSPosedService extends ILSPosedService.Stub { Log.e(TAG, "Broadcast to manager failed: ", t); } } - if (!intent.getAction().equals(Intent.ACTION_PACKAGE_REMOVED) && uid > 0 && ConfigManager.getInstance().isManager(packageName)) { + + if (moduleName != null && ConfigManager.getInstance().isManager(moduleName) && userId == 0) { Log.d(TAG, "Manager updated"); try { ConfigManager.getInstance().updateManager(); diff --git a/core/src/main/java/org/lsposed/lspd/service/PackageReceiver.java b/core/src/main/java/org/lsposed/lspd/service/PackageReceiver.java index f62a0f06..b19fdebe 100644 --- a/core/src/main/java/org/lsposed/lspd/service/PackageReceiver.java +++ b/core/src/main/java/org/lsposed/lspd/service/PackageReceiver.java @@ -58,11 +58,14 @@ public class PackageReceiver { return; } - IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); - intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); - intentFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED); - intentFilter.addDataScheme("package"); + IntentFilter packageFilter = new IntentFilter(); + packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); + packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); + packageFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED); + packageFilter.addDataScheme("package"); + + IntentFilter uidFilter = new IntentFilter(); + uidFilter.addAction(Intent.ACTION_UID_REMOVED); HandlerThread thread = new HandlerThread("lspd-PackageReceiver"); thread.start(); @@ -71,7 +74,8 @@ public class PackageReceiver { try { @SuppressLint("DiscouragedPrivateApi") Method method = Context.class.getDeclaredMethod("registerReceiverAsUser", BroadcastReceiver.class, UserHandle.class, IntentFilter.class, String.class, Handler.class); - method.invoke(context, receiver, userHandleAll, intentFilter, null, handler); + method.invoke(context, receiver, userHandleAll, packageFilter, null, handler); + method.invoke(context, receiver, userHandleAll, uidFilter, null, handler); Utils.logI("registered package receiver"); } catch (Throwable e) { Utils.logW("registerReceiver failed", e); diff --git a/hiddenapi-stubs/src/main/java/android/app/IActivityManager.java b/hiddenapi-stubs/src/main/java/android/app/IActivityManager.java index 57336f08..bf7cf074 100644 --- a/hiddenapi-stubs/src/main/java/android/app/IActivityManager.java +++ b/hiddenapi-stubs/src/main/java/android/app/IActivityManager.java @@ -43,6 +43,8 @@ public interface IActivityManager extends IInterface { void forceStopPackage(String packageName, int userId); + boolean startUserInBackground(int userid); + abstract class Stub extends Binder implements IActivityManager { public static IActivityManager asInterface(IBinder obj) {