[core] Fix package listener (#600)

This commit is contained in:
LoveSy 2021-05-17 05:21:51 +08:00 committed by GitHub
parent 626a015dc9
commit 9c1bbd5606
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 112 additions and 64 deletions

View File

@ -79,4 +79,10 @@ public class ActivityManagerService {
if (am == null) return; if (am == null) return;
am.forceStopPackage(packageName, userId); am.forceStopPackage(packageName, userId);
} }
public static boolean startUserInBackground(int userId) throws RemoteException {
IActivityManager am = getActivityManager();
if (am == null) return false;
return am.startUserInBackground(userId);
}
} }

View File

@ -62,13 +62,13 @@ import java.util.concurrent.ConcurrentHashMap;
// This config manager assume uid won't change when our service is off. // This config manager assume uid won't change when our service is off.
// Otherwise, user should maintain it manually. // Otherwise, user should maintain it manually.
public class ConfigManager { public class ConfigManager {
public static final int PER_USER_RANGE = 100000;
private static final String[] MANAGER_PERMISSIONS_TO_GRANT = new String[]{ private static final String[] MANAGER_PERMISSIONS_TO_GRANT = new String[]{
"android.permission.INTERACT_ACROSS_USERS", "android.permission.INTERACT_ACROSS_USERS",
"android.permission.WRITE_SECURE_SETTINGS" "android.permission.WRITE_SECURE_SETTINGS"
}; };
private static final int PER_USER_RANGE = 100000;
static ConfigManager instance = null; static ConfigManager instance = null;
private static final File basePath = new File("/data/adb/lspd"); private static final File basePath = new File("/data/adb/lspd");
@ -393,6 +393,10 @@ public class ConfigManager {
return !cachedScope.containsKey(scope) && !isManager(scope.uid); 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 // This should only be called by manager, so we don't need to cache it
public List<Application> getModuleScope(String packageName) { public List<Application> getModuleScope(String packageName) {
int mid = getModuleId(packageName); int mid = getModuleId(packageName);
@ -427,8 +431,11 @@ public class ConfigManager {
if (count < 0) { if (count < 0) {
count = db.updateWithOnConflict("modules", values, "module_pkg_name=?", new String[]{packageName}, SQLiteDatabase.CONFLICT_IGNORE); count = db.updateWithOnConflict("modules", values, "module_pkg_name=?", new String[]{packageName}, SQLiteDatabase.CONFLICT_IGNORE);
} }
if (count > 0) {
// Called by oneway binder // Called by oneway binder
updateCaches(true); updateCaches(true);
return true;
}
return count >= 0; return count >= 0;
} }
@ -489,11 +496,11 @@ public class ConfigManager {
} }
} }
public boolean removeModule(String packageName) { public void removeModule(String packageName) {
boolean res = removeModuleWithoutCache(packageName); if (removeModuleWithoutCache(packageName)) {
// called by oneway binder // called by oneway binder
updateCaches(true); updateCaches(true);
return res; }
} }
private boolean removeModuleWithoutCache(String packageName) { private boolean removeModuleWithoutCache(String packageName) {
@ -545,11 +552,16 @@ public class ConfigManager {
return true; return true;
} }
public boolean removeApp(Application app) { public void uninstalledApp(Application app) {
boolean res = removeAppWithoutCache(app); if (removeAppWithoutCache(app)) {
// Called by oneway binder // Called by oneway binder
updateCaches(true); cacheScopes();
return res; }
}
public void updateAppCache() {
// Called by oneway binder
cacheScopes();
} }
private boolean removeAppWithoutCache(Application app) { private boolean removeAppWithoutCache(Application app) {

View File

@ -150,7 +150,9 @@ public class LSPManagerService extends ILSPManagerService.Stub {
@Override @Override
public boolean uninstallPackage(String packageName, int userId) throws RemoteException { public boolean uninstallPackage(String packageName, int userId) throws RemoteException {
try { try {
if (ActivityManagerService.startUserInBackground(userId))
return PackageService.uninstallPackage(new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST), userId); return PackageService.uninstallPackage(new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST), userId);
else return false;
} catch (InterruptedException | InvocationTargetException | NoSuchMethodException | InstantiationException | IllegalAccessException e) { } catch (InterruptedException | InvocationTargetException | NoSuchMethodException | InstantiationException | IllegalAccessException e) {
Log.e(TAG, e.getMessage(), e); Log.e(TAG, e.getMessage(), e);
return false; return false;
@ -168,7 +170,14 @@ public class LSPManagerService extends ILSPManagerService.Stub {
} }
@Override @Override
public int installExistingPackageAsUser(String packageName, int userid) { public int installExistingPackageAsUser(String packageName, int userId) {
return PackageService.installExistingPackageAsUser(packageName, 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;
}
} }
} }

View File

@ -31,11 +31,13 @@ import android.util.Log;
import java.util.Arrays; 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; import static org.lsposed.lspd.service.ServiceManager.TAG;
public class LSPosedService extends ILSPosedService.Stub { public class LSPosedService extends ILSPosedService.Stub {
private static final int AID_NOBODY = 9999;
private static final int USER_NULL = -10000;
@Override @Override
public ILSPApplicationService requestApplicationService(int uid, int pid, String processName, IBinder heartBeat) { public ILSPApplicationService requestApplicationService(int uid, int pid, String processName, IBinder heartBeat) {
if (Binder.getCallingUid() != 1000) { if (Binder.getCallingUid() != 1000) {
@ -54,62 +56,74 @@ public class LSPosedService extends ILSPosedService.Stub {
return ServiceManager.requestApplicationService(uid, pid, heartBeat); 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.
* <p>
* 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 @Override
public void dispatchPackageChanged(Intent intent) throws RemoteException { public void dispatchPackageChanged(Intent intent) throws RemoteException {
if (Binder.getCallingUid() != 1000 || intent == null) return; 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(); Uri uri = intent.getData();
String packageName = (uri != null) ? uri.getSchemeSpecificPart() : null; String moduleName = (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;
}
if (intent.getAction().equals(Intent.ACTION_PACKAGE_CHANGED)) { ApplicationInfo applicationInfo = moduleName != null ? PackageService.getApplicationInfo(moduleName, PackageManager.GET_META_DATA, 0) : null;
// 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 = PackageService.getApplicationInfo(packageName, PackageManager.GET_META_DATA, 0);
boolean isXposedModule = applicationInfo != null && boolean isXposedModule = applicationInfo != null &&
applicationInfo.metaData != null && applicationInfo.metaData != null &&
applicationInfo.metaData.containsKey("xposedminversion"); applicationInfo.metaData.containsKey("xposedminversion");
if (isXposedModule) { Log.d(TAG, "Package changed: uid=" + uid + " userId=" + userId + " action=" + intent.getAction() + " isXposedModule=" + isXposedModule);
ConfigManager.getInstance().updateModuleApkPath(packageName, applicationInfo.sourceDir);
Log.d(TAG, "Updated module apk path: " + packageName);
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"); 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(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
broadcastIntent.addFlags(0x01000000); broadcastIntent.addFlags(0x01000000);
broadcastIntent.addFlags(0x00400000); broadcastIntent.addFlags(0x00400000);
broadcastIntent.setData(intent.getData()); broadcastIntent.setData(intent.getData());
broadcastIntent.putExtras(intent.getExtras()); broadcastIntent.putExtras(intent.getExtras());
broadcastIntent.putExtra(Intent.EXTRA_USER, userId);
broadcastIntent.setComponent(ComponentName.unflattenFromString(ConfigManager.getInstance().getManagerPackageName() + "/.receivers.ServiceReceiver")); broadcastIntent.setComponent(ComponentName.unflattenFromString(ConfigManager.getInstance().getManagerPackageName() + "/.receivers.ServiceReceiver"));
try { try {
@ -121,7 +135,8 @@ public class LSPosedService extends ILSPosedService.Stub {
Log.e(TAG, "Broadcast to manager failed: ", t); 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"); Log.d(TAG, "Manager updated");
try { try {
ConfigManager.getInstance().updateManager(); ConfigManager.getInstance().updateManager();

View File

@ -58,11 +58,14 @@ public class PackageReceiver {
return; return;
} }
IntentFilter intentFilter = new IntentFilter(); IntentFilter packageFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
intentFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED); packageFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
intentFilter.addDataScheme("package"); packageFilter.addDataScheme("package");
IntentFilter uidFilter = new IntentFilter();
uidFilter.addAction(Intent.ACTION_UID_REMOVED);
HandlerThread thread = new HandlerThread("lspd-PackageReceiver"); HandlerThread thread = new HandlerThread("lspd-PackageReceiver");
thread.start(); thread.start();
@ -71,7 +74,8 @@ public class PackageReceiver {
try { try {
@SuppressLint("DiscouragedPrivateApi") @SuppressLint("DiscouragedPrivateApi")
Method method = Context.class.getDeclaredMethod("registerReceiverAsUser", BroadcastReceiver.class, UserHandle.class, IntentFilter.class, String.class, Handler.class); 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"); Utils.logI("registered package receiver");
} catch (Throwable e) { } catch (Throwable e) {
Utils.logW("registerReceiver failed", e); Utils.logW("registerReceiver failed", e);

View File

@ -43,6 +43,8 @@ public interface IActivityManager extends IInterface {
void forceStopPackage(String packageName, int userId); void forceStopPackage(String packageName, int userId);
boolean startUserInBackground(int userid);
abstract class Stub extends Binder implements IActivityManager { abstract class Stub extends Binder implements IActivityManager {
public static IActivityManager asInterface(IBinder obj) { public static IActivityManager asInterface(IBinder obj) {