[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;
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.
// 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<Application> 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) {

View File

@ -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;
}
}
}

View File

@ -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.
* <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
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();

View File

@ -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);

View File

@ -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) {