();
-
- var updated = new NotificationChannel(UPDATED_CHANNEL_ID,
- context.getString(R.string.module_updated_channel_name),
- NotificationManager.IMPORTANCE_HIGH);
- updated.setShowBadge(false);
- if (hasNotificationChannelForSystem(nm, UPDATED_CHANNEL_ID)) {
- Log.d(TAG, "update notification channel: " + UPDATED_CHANNEL_ID);
- nm.updateNotificationChannelForPackage("android", 1000, updated);
- } else {
- list.add(updated);
- }
-
- var status = new NotificationChannel(STATUS_CHANNEL_ID,
- context.getString(R.string.status_channel_name),
- NotificationManager.IMPORTANCE_MIN);
- status.setShowBadge(false);
- if (hasNotificationChannelForSystem(nm, STATUS_CHANNEL_ID)) {
- Log.d(TAG, "update notification channel: " + STATUS_CHANNEL_ID);
- nm.updateNotificationChannelForPackage("android", 1000, status);
- } else {
- list.add(status);
- }
-
- var scope = new NotificationChannel(SCOPE_CHANNEL_ID,
- context.getString(R.string.scope_channel_name),
- NotificationManager.IMPORTANCE_HIGH);
- scope.setShowBadge(false);
- if (hasNotificationChannelForSystem(nm, SCOPE_CHANNEL_ID)) {
- Log.d(TAG, "update notification channel: " + SCOPE_CHANNEL_ID);
- nm.updateNotificationChannelForPackage("android", 1000, scope);
- } else {
- list.add(scope);
- }
-
- Log.d(TAG, "create notification channels for android: " + list);
- nm.createNotificationChannelsForPackage("android", 1000, new ParceledListSlice<>(list));
- }
-
- static void notifyStatusNotification() {
- var intent = new Intent(openManagerAction);
- intent.setPackage("android");
- var context = new FakeContext();
- int flags = PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE;
- var notification = new Notification.Builder(context, STATUS_CHANNEL_ID)
- .setContentTitle(context.getString(R.string.lsposed_running_notification_title))
- .setContentText(context.getString(R.string.lsposed_running_notification_content))
- .setSmallIcon(getNotificationIcon())
- .setContentIntent(PendingIntent.getBroadcast(context, 1, intent, flags))
- .setVisibility(Notification.VISIBILITY_SECRET)
- .setColor(0xFFF48FB1)
- .setOngoing(true)
- .setAutoCancel(false)
- .build();
- notification.extras.putString("android.substName", BuildConfig.FRAMEWORK_NAME);
- try {
- var nm = getNotificationManager();
- createNotificationChannel(nm);
- nm.enqueueNotificationWithTag("android", opPkg, null,
- STATUS_NOTIFICATION_ID, notification, 0);
- } catch (RemoteException e) {
- Log.e(TAG, "notifyStatusNotification: ", e);
- }
- }
-
- static void cancelStatusNotification() {
- try {
- var nm = getNotificationManager();
- createNotificationChannel(nm);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
- nm.cancelNotificationWithTag("android", "android", null, STATUS_NOTIFICATION_ID, 0);
- } else {
- nm.cancelNotificationWithTag("android", null, STATUS_NOTIFICATION_ID, 0);
- }
- } catch (RemoteException e) {
- Log.e(TAG, "cancelStatusNotification: ", e);
- }
- }
-
- private static PendingIntent getModuleIntent(String modulePackageName, int moduleUserId) {
- var intent = new Intent(openManagerAction);
- intent.setPackage("android");
- intent.setData(new Uri.Builder().scheme("module").encodedAuthority(modulePackageName + ":" + moduleUserId).build());
- int flags = PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE;
- return PendingIntent.getBroadcast(new FakeContext(), 3, intent, flags);
- }
-
- private static PendingIntent getModuleScopeIntent(String modulePackageName, int moduleUserId, String scopePackageName, String action, IXposedScopeCallback callback) {
- var intent = new Intent(moduleScope);
- intent.setPackage("android");
- intent.setData(new Uri.Builder().scheme("module").encodedAuthority(modulePackageName + ":" + moduleUserId).encodedPath(scopePackageName).appendQueryParameter("action", action).build());
- var extras = new Bundle();
- extras.putBinder("callback", callback.asBinder());
- intent.putExtras(extras);
- int flags = PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE;
- return PendingIntent.getBroadcast(new FakeContext(), 4, intent, flags);
- }
-
- private static String getNotificationIdKey(String channel, String modulePackageName, int moduleUserId) {
- return channel + "/" + modulePackageName + ":" + moduleUserId;
- }
-
- private static int pushAndGetNotificationId(String channel, String modulePackageName, int moduleUserId) {
- var idKey = getNotificationIdKey(channel, modulePackageName, moduleUserId);
- // previousNotificationId start with 2001
- // https://android.googlesource.com/platform/frameworks/base/+/master/proto/src/system_messages.proto
- // https://android.googlesource.com/platform/system/core/+/master/libcutils/include/private/android_filesystem_config.h
- // (AID_APP_END - AID_APP_START) x10 = 100000 < NOTE_NETWORK_AVAILABLE
- return notificationIds.computeIfAbsent(idKey, key -> previousNotificationId++);
- }
-
- static void notifyModuleUpdated(String modulePackageName,
- int moduleUserId,
- boolean enabled,
- boolean systemModule) {
- var context = new FakeContext();
- var userName = UserService.getUserName(moduleUserId);
- String title = context.getString(enabled ? systemModule ?
- R.string.xposed_module_updated_notification_title_system :
- R.string.xposed_module_updated_notification_title :
- R.string.module_is_not_activated_yet);
- String content = context.getString(enabled ? systemModule ?
- R.string.xposed_module_updated_notification_content_system :
- R.string.xposed_module_updated_notification_content :
- (moduleUserId == 0 ?
- R.string.module_is_not_activated_yet_main_user_detailed :
- R.string.module_is_not_activated_yet_multi_user_detailed), modulePackageName, userName);
-
- var style = new Notification.BigTextStyle();
- style.bigText(content);
-
- var notification = new Notification.Builder(context, UPDATED_CHANNEL_ID)
- .setContentTitle(title)
- .setContentText(content)
- .setSmallIcon(getNotificationIcon())
- .setContentIntent(getModuleIntent(modulePackageName, moduleUserId))
- .setVisibility(Notification.VISIBILITY_SECRET)
- .setColor(0xFFF48FB1)
- .setAutoCancel(true)
- .setStyle(style)
- .build();
- notification.extras.putString("android.substName", BuildConfig.FRAMEWORK_NAME);
- try {
- var nm = getNotificationManager();
- nm.enqueueNotificationWithTag("android", opPkg, modulePackageName,
- pushAndGetNotificationId(UPDATED_CHANNEL_ID, modulePackageName, moduleUserId),
- notification, 0);
- } catch (RemoteException e) {
- Log.e(TAG, "notify module updated", e);
- }
- }
-
- static void requestModuleScope(String modulePackageName, int moduleUserId, String scopePackageName, IXposedScopeCallback callback) {
- var context = new FakeContext();
- var userName = UserService.getUserName(moduleUserId);
- String title = context.getString(R.string.xposed_module_request_scope_title);
- String content = context.getString(R.string.xposed_module_request_scope_content, modulePackageName, userName, scopePackageName);
-
- var style = new Notification.BigTextStyle();
- style.bigText(content);
-
- var notification = new Notification.Builder(context, SCOPE_CHANNEL_ID)
- .setContentTitle(title)
- .setContentText(content)
- .setSmallIcon(getNotificationIcon())
- .setVisibility(Notification.VISIBILITY_SECRET)
- .setColor(0xFFF48FB1)
- .setAutoCancel(true)
- .setTimeoutAfter(1000 * 60 * 60)
- .setStyle(style)
- .setDeleteIntent(getModuleScopeIntent(modulePackageName, moduleUserId, scopePackageName, "delete", callback))
- .setActions(new Notification.Action.Builder(
- Icon.createWithResource(context, R.drawable.ic_baseline_check_24),
- context.getString(R.string.scope_approve),
- getModuleScopeIntent(modulePackageName, moduleUserId, scopePackageName, "approve", callback))
- .build(),
- new Notification.Action.Builder(
- Icon.createWithResource(context, R.drawable.ic_baseline_close_24),
- context.getString(R.string.scope_deny),
- getModuleScopeIntent(modulePackageName, moduleUserId, scopePackageName, "deny", callback))
- .build(),
- new Notification.Action.Builder(
- Icon.createWithResource(context, R.drawable.ic_baseline_block_24),
- context.getString(R.string.nerver_ask_again),
- getModuleScopeIntent(modulePackageName, moduleUserId, scopePackageName, "block", callback))
- .build()
- ).build();
- notification.extras.putString("android.substName", BuildConfig.FRAMEWORK_NAME);
- try {
- var nm = getNotificationManager();
- nm.enqueueNotificationWithTag("android", opPkg, modulePackageName,
- pushAndGetNotificationId(SCOPE_CHANNEL_ID, modulePackageName, moduleUserId),
- notification, 0);
- } catch (RemoteException e) {
- try {
- callback.onScopeRequestFailed(e.getMessage());
- } catch (RemoteException ignored) {
- }
- Log.e(TAG, "request module scope", e);
- }
- }
-
- static void cancelNotification(String channel, String modulePackageName, int moduleUserId) {
- try {
- var idKey = getNotificationIdKey(channel, modulePackageName, moduleUserId);
- var idValue = notificationIds.get(idKey);
- if (idValue == null) return;
- var nm = getNotificationManager();
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
- nm.cancelNotificationWithTag("android", "android", modulePackageName, idValue, 0);
- } else {
- nm.cancelNotificationWithTag("android", modulePackageName, idValue, 0);
- }
- notificationIds.remove(idKey);
- } catch (RemoteException e) {
- Log.e(TAG, "cancel notification", e);
- }
- }
-}
diff --git a/daemon/src/main/java/org/lsposed/lspd/service/LSPSystemServerService.java b/daemon/src/main/java/org/lsposed/lspd/service/LSPSystemServerService.java
deleted file mode 100644
index facf75417..000000000
--- a/daemon/src/main/java/org/lsposed/lspd/service/LSPSystemServerService.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * This file is part of LSPosed.
- *
- * LSPosed is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * LSPosed is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with LSPosed. If not, see .
- *
- * Copyright (C) 2021 - 2022 LSPosed Contributors
- */
-
-package org.lsposed.lspd.service;
-
-import static org.lsposed.lspd.service.ServiceManager.TAG;
-import static org.lsposed.lspd.service.ServiceManager.getSystemServiceManager;
-
-import android.os.Build;
-import android.os.IBinder;
-import android.os.IServiceCallback;
-import android.os.Parcel;
-import android.os.RemoteException;
-import android.os.SystemProperties;
-import android.util.Log;
-
-public class LSPSystemServerService extends ILSPSystemServerService.Stub implements IBinder.DeathRecipient {
-
- private final String proxyServiceName;
- private IBinder originService = null;
- private int requested;
-
- public boolean systemServerRequested() {
- return requested > 0;
- }
-
- public void putBinderForSystemServer() {
- android.os.ServiceManager.addService(proxyServiceName, this);
- binderDied();
- }
-
- public LSPSystemServerService(int maxRetry, String serviceName) {
- Log.d(TAG, "LSPSystemServerService::LSPSystemServerService with proxy " + serviceName);
- proxyServiceName = serviceName;
- requested = -maxRetry;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
- // Registers a callback when system is registering an authentic "serial" service
- // And we are proxying all requests to that system service
- var serviceCallback = new IServiceCallback.Stub() {
- @Override
- public void onRegistration(String name, IBinder binder) {
- Log.d(TAG, "LSPSystemServerService::LSPSystemServerService onRegistration: " + name + " " + binder);
- if (name.equals(proxyServiceName) && binder != null && binder != LSPSystemServerService.this) {
- Log.d(TAG, "Register " + name + " " + binder);
- originService = binder;
- LSPSystemServerService.this.linkToDeath();
- }
- }
-
- @Override
- public IBinder asBinder() {
- return this;
- }
- };
- try {
- getSystemServiceManager().registerForNotifications(proxyServiceName, serviceCallback);
- } catch (Throwable e) {
- Log.e(TAG, "unregister: ", e);
- }
- }
- }
-
- @Override
- public ILSPApplicationService requestApplicationService(int uid, int pid, String processName, IBinder heartBeat) {
- Log.d(TAG, "ILSPApplicationService.requestApplicationService: " + uid + " " + pid + " " + processName + " " + heartBeat);
- requested = 1;
- if (ConfigManager.getInstance().shouldSkipSystemServer() || uid != 1000 || heartBeat == null || !"system".equals(processName))
- return null;
- else
- return ServiceManager.requestApplicationService(uid, pid, processName, heartBeat);
- }
-
- @Override
- public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
- Log.d(TAG, "LSPSystemServerService.onTransact: code=" + code);
- if (originService != null) {
- return originService.transact(code, data, reply, flags);
- }
-
- switch (code) {
- case BridgeService.TRANSACTION_CODE -> {
- int uid = data.readInt();
- int pid = data.readInt();
- String processName = data.readString();
- IBinder heartBeat = data.readStrongBinder();
- var service = requestApplicationService(uid, pid, processName, heartBeat);
- if (service != null) {
- Log.d(TAG, "LSPSystemServerService.onTransact requestApplicationService granted: " + service);
- reply.writeNoException();
- reply.writeStrongBinder(service.asBinder());
- return true;
- } else {
- Log.d(TAG, "LSPSystemServerService.onTransact requestApplicationService rejected");
- return false;
- }
- }
- case LSPApplicationService.OBFUSCATION_MAP_TRANSACTION_CODE, LSPApplicationService.DEX_TRANSACTION_CODE -> {
- // Proxy LSP dex transaction to Application Binder
- return ServiceManager.getApplicationService().onTransact(code, data, reply, flags);
- }
- default -> {
- return super.onTransact(code, data, reply, flags);
- }
- }
- }
-
- public void linkToDeath() {
- try {
- originService.linkToDeath(this, 0);
- } catch (Throwable e) {
- Log.e(TAG, "system server service: link to death", e);
- }
- }
-
- @Override
- public void binderDied() {
- if (originService != null) {
- originService.unlinkToDeath(this, 0);
- originService = null;
- }
- }
-
- public void maybeRetryInject() {
- if (requested < 0) {
- Log.w(TAG, "System server injection fails, trying a restart");
- ++requested;
- if (Build.SUPPORTED_64_BIT_ABIS.length > 0 && Build.SUPPORTED_32_BIT_ABIS.length > 0) {
- // Only devices with both 32-bit and 64-bit support have zygote_secondary
- SystemProperties.set("ctl.restart", "zygote_secondary");
- } else {
- SystemProperties.set("ctl.restart", "zygote");
- }
- }
- }
-}
diff --git a/daemon/src/main/java/org/lsposed/lspd/service/LSPosedService.java b/daemon/src/main/java/org/lsposed/lspd/service/LSPosedService.java
deleted file mode 100644
index 4af2632a4..000000000
--- a/daemon/src/main/java/org/lsposed/lspd/service/LSPosedService.java
+++ /dev/null
@@ -1,499 +0,0 @@
-/*
- * This file is part of LSPosed.
- *
- * LSPosed is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * LSPosed is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with LSPosed. If not, see .
- *
- * Copyright (C) 2021 LSPosed Contributors
- */
-
-package org.lsposed.lspd.service;
-
-import static android.content.Intent.EXTRA_UID;
-import static org.lsposed.lspd.service.LSPNotificationManager.SCOPE_CHANNEL_ID;
-import static org.lsposed.lspd.service.LSPNotificationManager.UPDATED_CHANNEL_ID;
-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.app.IUidObserver;
-import android.content.Context;
-import android.content.IIntentReceiver;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.provider.Telephony;
-import android.telephony.TelephonyManager;
-import android.util.Log;
-
-import org.lsposed.daemon.BuildConfig;
-import org.lsposed.lspd.models.Application;
-
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-import java.util.function.Consumer;
-import java.util.zip.ZipFile;
-
-import hidden.HiddenApiBridge;
-import io.github.libxposed.service.IXposedScopeCallback;
-
-public class LSPosedService extends ILSPosedService.Stub {
- private static final int AID_NOBODY = 9999;
- 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";
- private static boolean bootCompleted = false;
- private IBinder appThread = null;
-
- private static boolean isModernModules(ApplicationInfo info) {
- String[] apks;
- if (info.splitSourceDirs != null) {
- apks = Arrays.copyOf(info.splitSourceDirs, info.splitSourceDirs.length + 1);
- apks[info.splitSourceDirs.length] = info.sourceDir;
- } else apks = new String[]{info.sourceDir};
- for (var apk : apks) {
- try (var zip = new ZipFile(apk)) {
- if (zip.getEntry("META-INF/xposed/java_init.list") != null) {
- return true;
- }
- } catch (IOException ignored) {
- }
- }
- return false;
- }
-
- @Override
- public ILSPApplicationService requestApplicationService(int uid, int pid, String processName, IBinder heartBeat) {
- if (Binder.getCallingUid() != 1000) {
- Log.w(TAG, "Someone else got my binder!?");
- return null;
- }
- if (ServiceManager.getApplicationService().hasRegister(uid, pid)) {
- Log.d(TAG, "Skipped duplicated request for uid " + uid + " pid " + pid);
- return null;
- }
- if (!ServiceManager.getManagerService().shouldStartManager(pid, uid, processName) && ConfigManager.getInstance().shouldSkipProcess(new ConfigManager.ProcessScope(processName, uid))) {
- Log.d(TAG, "Skipped " + processName + "/" + uid);
- return null;
- }
- Log.d(TAG, "returned service");
- return ServiceManager.requestApplicationService(uid, pid, processName, 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.
- */
-
- private void dispatchPackageChanged(Intent intent) {
- if (intent == null) return;
- int uid = intent.getIntExtra(EXTRA_UID, AID_NOBODY);
- if (uid == AID_NOBODY || uid <= 0) return;
- int userId = intent.getIntExtra("android.intent.extra.user_handle", USER_NULL);
- var intentAction = intent.getAction();
- if (intentAction == null) return;
- var allUsers = intent.getBooleanExtra(EXTRA_REMOVED_FOR_ALL_USERS, false);
- if (userId == USER_NULL) userId = uid % PER_USER_RANGE;
- Uri uri = intent.getData();
- var module = ConfigManager.getInstance().getModule(uid);
- String moduleName = (uri != null) ? uri.getSchemeSpecificPart() : (module != null) ? module.packageName : null;
-
- ApplicationInfo applicationInfo = null;
- if (moduleName != null) {
- try {
- applicationInfo = PackageService.getApplicationInfo(moduleName, PackageManager.GET_META_DATA | PackageService.MATCH_ALL_FLAGS, 0);
- } catch (Throwable ignored) {
- }
- }
-
- boolean isXposedModule = applicationInfo != null && ((applicationInfo.metaData != null && applicationInfo.metaData.containsKey("xposedminversion")) || isModernModules(applicationInfo));
-
- switch (intentAction) {
- case Intent.ACTION_PACKAGE_FULLY_REMOVED -> {
- // for module, remove module
- // because we only care about when the apk is gone
- if (moduleName != null) {
- if (allUsers && ConfigManager.getInstance().removeModule(moduleName)) {
- isXposedModule = true;
- broadcastAndShowNotification(moduleName, userId, intent, true);
- }
- LSPNotificationManager.cancelNotification(UPDATED_CHANNEL_ID, moduleName, userId);
- }
- }
- case Intent.ACTION_PACKAGE_REMOVED -> {
- if (moduleName != null) {
- LSPNotificationManager.cancelNotification(UPDATED_CHANNEL_ID, moduleName, userId);
- }
- break;
- }
- case Intent.ACTION_PACKAGE_ADDED, Intent.ACTION_PACKAGE_CHANGED -> {
- var configManager = ConfigManager.getInstance();
- // 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 && !Arrays.stream(components).reduce(false, (p, c) -> p || c.equals(moduleName), Boolean::logicalOr)) {
- return;
- }
- if (isXposedModule) {
- // 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
- // uninstalled.
- // If cache not updated, assume it's not xposed module
- isXposedModule = configManager.updateModuleApkPath(moduleName, ConfigManager.getInstance().getModuleApkPath(applicationInfo), false);
- } else {
- if (configManager.isUidHooked(uid)) {
- // it will automatically remove obsolete app from database
- configManager.updateAppCache();
- }
- if (intentAction.equals(Intent.ACTION_PACKAGE_ADDED) && !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
- for (String xposedModule : configManager.getAutoIncludeModules()) {
- // For Xposed modules with auto_include set, we always add new applications
- // to its scope
- var list = configManager.getModuleScope(xposedModule);
- if (list != null) {
- Application scope = new Application();
- scope.packageName = moduleName;
- scope.userId = userId;
- list.add(scope);
- try {
- if (!configManager.setModuleScope(xposedModule, list)) {
- Log.e(TAG, "failed to set scope for " + xposedModule);
- }
- } catch(RemoteException re) {
- Log.e(TAG, "failed to set scope for " + xposedModule, re);
- }
- }
- }
- }
- }
- broadcastAndShowNotification(moduleName, userId, intent, isXposedModule);
- }
- 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)
- broadcastAndShowNotification(moduleName, userId, intent, isXposedModule);
- if (isXposedModule) {
- // it will auto remove obsolete app and scope from database
- ConfigManager.getInstance().updateCache();
- } else if (ConfigManager.getInstance().isUidHooked(uid)) {
- // it will auto remove obsolete scope from database
- ConfigManager.getInstance().updateAppCache();
- }
- }
- }
- boolean removed = Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(intentAction) || Intent.ACTION_UID_REMOVED.equals(intentAction);
-
- Log.d(TAG, "Package changed: uid=" + uid + " userId=" + userId + " action=" + intentAction + " isXposedModule=" + isXposedModule + " isAllUsers=" + allUsers);
-
- if (BuildConfig.DEFAULT_MANAGER_PACKAGE_NAME.equals(moduleName) && userId == 0) {
- Log.d(TAG, "Manager updated");
- ConfigManager.getInstance().updateManager(removed);
- }
- }
-
- private void broadcastAndShowNotification(String packageName, int userId, Intent intent, boolean isXposedModule) {
- Log.d(TAG, "package " + packageName + " changed, dispatching to manager");
- var action = intent.getAction();
- var allUsers = intent.getBooleanExtra(EXTRA_REMOVED_FOR_ALL_USERS, false);
- intent.putExtra("android.intent.extra.PACKAGES", packageName);
- intent.putExtra(Intent.EXTRA_USER, userId);
- intent.putExtra("isXposedModule", isXposedModule);
- LSPManagerService.broadcastIntent(intent);
- if (isXposedModule) {
- var enabledModules = ConfigManager.getInstance().enabledModules();
- var scope = ConfigManager.getInstance().getModuleScope(packageName);
- boolean systemModule = scope != null && scope.parallelStream().anyMatch(app -> app.packageName.equals("system"));
- boolean enabled = Arrays.asList(enabledModules).contains(packageName);
- if (!(Intent.ACTION_UID_REMOVED.equals(action) || Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action) || allUsers))
- LSPNotificationManager.notifyModuleUpdated(packageName, userId, enabled, systemModule);
- }
- }
-
- private void dispatchUserChanged(Intent intent) {
- if (intent == null) return;
- int uid = intent.getIntExtra(EXTRA_USER_HANDLE, AID_NOBODY);
- if (uid == AID_NOBODY || uid <= 0) return;
- LSPManagerService.broadcastIntent(intent);
- }
-
- private void dispatchBootCompleted(Intent intent) {
- bootCompleted = true;
- var configManager = ConfigManager.getInstance();
- if (configManager.enableStatusNotification()) {
- LSPNotificationManager.notifyStatusNotification();
- } else {
- LSPNotificationManager.cancelStatusNotification();
- }
- }
-
- private void dispatchConfigurationChanged(Intent intent) {
- if (!bootCompleted) return;
- ConfigFileManager.reloadConfiguration();
- var configManager = ConfigManager.getInstance();
- if (configManager.enableStatusNotification()) {
- LSPNotificationManager.notifyStatusNotification();
- } else {
- LSPNotificationManager.cancelStatusNotification();
- }
- }
-
- private void dispatchSecretCodeReceive(Intent i) {
- LSPManagerService.openManager(null);
- }
-
- private void dispatchOpenManager(Intent intent) {
- LSPManagerService.openManager(intent.getData());
- }
-
- private void dispatchModuleScope(Intent intent) {
- Log.d(TAG, "dispatchModuleScope: " + intent);
- var data = intent.getData();
- var extras = intent.getExtras();
- if (extras == null || data == null) return;
- var callback = extras.getBinder("callback");
- if (callback == null || !callback.isBinderAlive()) return;
- var authority = data.getEncodedAuthority();
- if (authority == null) return;
- var s = authority.split(":", 2);
- if (s.length != 2) return;
- var packageName = s[0];
- int userId;
- try {
- userId = Integer.parseInt(s[1]);
- } catch (NumberFormatException e) {
- return;
- }
- var scopePackageName = data.getPath();
- if (scopePackageName == null) return;
- scopePackageName = scopePackageName.substring(1);
- var action = data.getQueryParameter("action");
- if (action == null) return;
-
- var iCallback = IXposedScopeCallback.Stub.asInterface(callback);
- try {
- var applicationInfo = PackageService.getApplicationInfo(scopePackageName, 0, userId);
- if (applicationInfo == null) {
- iCallback.onScopeRequestFailed("Package not found");
- return;
- }
-
- switch (action) {
- case "approve" -> {
- ConfigManager.getInstance().setModuleScope(packageName, scopePackageName, userId);
- iCallback.onScopeRequestApproved(Collections.singletonList(scopePackageName));
- }
- case "deny" -> iCallback.onScopeRequestFailed("Request denied by user");
- case "delete" -> iCallback.onScopeRequestFailed("Request timeout");
- case "block" -> {
- ConfigManager.getInstance().blockScopeRequest(packageName);
- iCallback.onScopeRequestFailed("Request blocked by configuration");
- }
- }
- Log.i(TAG, action + " scope " + scopePackageName + " for " + packageName + " in user " + userId);
- } catch (RemoteException e) {
- try {
- iCallback.onScopeRequestFailed(e.getMessage());
- } catch (RemoteException ignored) {
- // callback died
- }
- }
- LSPNotificationManager.cancelNotification(SCOPE_CHANNEL_ID, packageName, userId);
- }
-
- private void registerReceiver(List filters, String requiredPermission, int userId, Consumer task, int flag) {
- 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(() -> {
- try {
- task.accept(intent);
- } catch (Throwable t) {
- Log.e(TAG, "performReceive: ", t);
- }
- });
- if (!ordered && !Objects.equals(intent.getAction(), Intent.ACTION_LOCKED_BOOT_COMPLETED))
- return;
- try {
- ActivityManagerService.finishReceiver(this, appThread, resultCode, data, extras, false, intent.getFlags());
- } catch (RemoteException e) {
- Log.e(TAG, "finish receiver", e);
- }
- }
- };
- try {
- for (var filter : filters) {
- ActivityManagerService.registerReceiver("android", null, receiver, filter, requiredPermission, userId, flag);
- }
- } catch (RemoteException e) {
- Log.e(TAG, "register receiver", e);
- }
- }
-
- private void registerReceiver(List filters, int userId, Consumer task) {
- //noinspection InlinedApi
- registerReceiver(filters, "android.permission.BRICK", userId, task, Context.RECEIVER_NOT_EXPORTED);
- }
-
- private void registerPackageReceiver() {
- var 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");
-
- var uidFilter = new IntentFilter(Intent.ACTION_UID_REMOVED);
-
- registerReceiver(List.of(packageFilter, uidFilter), -1, this::dispatchPackageChanged);
- Log.d(TAG, "registered package receiver");
- }
-
- private void registerConfigurationReceiver() {
- var intentFilter = new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED);
-
- registerReceiver(List.of(intentFilter), 0, this::dispatchConfigurationChanged);
- Log.d(TAG, "registered configuration receiver");
- }
-
- private void registerSecretCodeReceiver() {
- IntentFilter intentFilter = new IntentFilter();
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- intentFilter.addAction(TelephonyManager.ACTION_SECRET_CODE);
- } else {
- // noinspection InlinedApi
- intentFilter.addAction(Telephony.Sms.Intents.SECRET_CODE_ACTION);
- }
- intentFilter.addDataAuthority("5776733", null);
- intentFilter.addDataScheme("android_secret_code");
-
- //noinspection InlinedApi
- registerReceiver(List.of(intentFilter), "android.permission.CONTROL_INCALL_EXPERIENCE",
- 0, this::dispatchSecretCodeReceive, Context.RECEIVER_EXPORTED);
- Log.d(TAG, "registered secret code receiver");
- }
-
- private void registerBootCompleteReceiver() {
- var intentFilter = new IntentFilter(Intent.ACTION_LOCKED_BOOT_COMPLETED);
- intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
- registerReceiver(List.of(intentFilter), 0, this::dispatchBootCompleted);
- Log.d(TAG, "registered boot receiver");
- }
-
- private void registerUserChangeReceiver() {
- var userFilter = new IntentFilter();
- userFilter.addAction(ACTION_USER_ADDED);
- userFilter.addAction(ACTION_USER_REMOVED);
-
- registerReceiver(List.of(userFilter), -1, this::dispatchUserChanged);
- Log.d(TAG, "registered user info change receiver");
- }
-
- private void registerOpenManagerReceiver() {
- var intentFilter = new IntentFilter(LSPNotificationManager.openManagerAction);
- var moduleFilter = new IntentFilter(intentFilter);
- moduleFilter.addDataScheme("module");
-
- registerReceiver(List.of(intentFilter, moduleFilter), 0, this::dispatchOpenManager);
- Log.d(TAG, "registered open manager receiver");
- }
-
- private void registerModuleScopeReceiver() {
- var intentFilter = new IntentFilter(LSPNotificationManager.moduleScope);
- intentFilter.addDataScheme("module");
-
- registerReceiver(List.of(intentFilter), 0, this::dispatchModuleScope);
- Log.d(TAG, "registered module scope receiver");
- }
-
- private void registerUidObserver() {
- try {
- var which = HiddenApiBridge.ActivityManager_UID_OBSERVER_ACTIVE()
- | HiddenApiBridge.ActivityManager_UID_OBSERVER_GONE()
- | HiddenApiBridge.ActivityManager_UID_OBSERVER_IDLE()
- | HiddenApiBridge.ActivityManager_UID_OBSERVER_CACHED();
- LSPModuleService.uidClear();
- ActivityManagerService.registerUidObserver(new IUidObserver.Stub() {
- @Override
- public void onUidActive(int uid) {
- LSPModuleService.uidStarts(uid);
- }
-
- @Override
- public void onUidCachedChanged(int uid, boolean cached) {
- if (!cached) LSPModuleService.uidStarts(uid);
- }
-
- @Override
- public void onUidIdle(int uid, boolean disabled) {
- LSPModuleService.uidStarts(uid);
- }
-
- @Override
- public void onUidGone(int uid, boolean disabled) {
- LSPModuleService.uidGone(uid);
- }
- }, which, HiddenApiBridge.ActivityManager_PROCESS_STATE_UNKNOWN(), null);
- } catch (RemoteException e) {
- Log.e(TAG, "registerUidObserver", e);
- }
- }
-
- @Override
- public void dispatchSystemServerContext(IBinder appThread, IBinder activityToken, String api) {
- Log.d(TAG, "received system context");
- this.appThread = appThread;
- ConfigManager.getInstance().setApi(api);
- ActivityManagerService.onSystemServerContext(IApplicationThread.Stub.asInterface(appThread), activityToken);
- registerBootCompleteReceiver();
- registerPackageReceiver();
- registerConfigurationReceiver();
- registerSecretCodeReceiver();
- registerUserChangeReceiver();
- registerOpenManagerReceiver();
- registerModuleScopeReceiver();
- registerUidObserver();
-
- if (ServiceManager.isLateInject) {
- Log.i(TAG, "System already booted during late injection. Manually triggering boot completed.");
- dispatchBootCompleted(null);
- }
- }
-
- @Override
- public boolean preStartManager() {
- return ServiceManager.getManagerService().preStartManager();
- }
-
- @Override
- public boolean setManagerEnabled(boolean enabled) throws RemoteException {
- return ServiceManager.getManagerService().setEnabled(enabled);
- }
-}
diff --git a/daemon/src/main/java/org/lsposed/lspd/service/LogcatService.java b/daemon/src/main/java/org/lsposed/lspd/service/LogcatService.java
deleted file mode 100644
index 74ba4fae9..000000000
--- a/daemon/src/main/java/org/lsposed/lspd/service/LogcatService.java
+++ /dev/null
@@ -1,222 +0,0 @@
-package org.lsposed.lspd.service;
-
-import android.annotation.SuppressLint;
-import android.os.Build;
-import android.os.ParcelFileDescriptor;
-import android.os.Process;
-import android.os.SELinux;
-import android.os.SystemProperties;
-import android.system.Os;
-import android.util.Log;
-
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.LinkOption;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.LinkedHashMap;
-
-public class LogcatService implements Runnable {
- private static final String TAG = "LSPosedLogcat";
- private static final int mode = ParcelFileDescriptor.MODE_WRITE_ONLY |
- ParcelFileDescriptor.MODE_CREATE |
- ParcelFileDescriptor.MODE_TRUNCATE |
- ParcelFileDescriptor.MODE_APPEND;
- private int modulesFd = -1;
- private int verboseFd = -1;
- private Thread thread = null;
-
- static class LogLRU extends LinkedHashMap {
- private static final int MAX_ENTRIES = 10;
-
- public LogLRU() {
- super(MAX_ENTRIES, 1f, false);
- }
-
- @Override
- synchronized protected boolean removeEldestEntry(Entry eldest) {
- if (size() > MAX_ENTRIES && eldest.getKey().delete()) {
- Log.d(TAG, "Deleted old log " + eldest.getKey().getAbsolutePath());
- return true;
- }
- return false;
- }
- }
-
- @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
- private final LinkedHashMap moduleLogs = new LogLRU();
- @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
- private final LinkedHashMap verboseLogs = new LogLRU();
-
- @SuppressLint("UnsafeDynamicallyLoadedCode")
- public LogcatService() {
- String classPath = System.getProperty("java.class.path");
- var abi = Process.is64Bit() ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0];
- System.load(classPath + "!/lib/" + abi + "/" + System.mapLibraryName("daemon"));
- ConfigFileManager.moveLogDir();
-
- // Meizu devices set this prop and prevent debug logs from being recorded
- if (SystemProperties.getInt("persist.sys.log_reject_level", 0) > 0) {
- SystemProperties.set("persist.sys.log_reject_level", "0");
- }
-
- getprop();
- dmesg();
- }
-
- private static void getprop() {
- // multithreaded process can not change their context type,
- // start a new process to set restricted context to filter privacy props
- var cmd = "echo -n u:r:untrusted_app:s0 > /proc/thread-self/attr/current; getprop";
- try {
- SELinux.setFSCreateContext("u:object_r:app_data_file:s0");
- new ProcessBuilder("sh", "-c", cmd)
- .redirectOutput(ConfigFileManager.getPropsPath())
- .start();
- } catch (IOException e) {
- Log.e(TAG, "getprop: ", e);
- } finally {
- SELinux.setFSCreateContext(null);
- }
- }
-
- private static void dmesg() {
- try {
- new ProcessBuilder("dmesg")
- .redirectOutput(ConfigFileManager.getKmsgPath())
- .start();
- } catch (IOException e) {
- Log.e(TAG, "dmesg: ", e);
- }
- }
-
- private native void runLogcat();
-
- @Override
- public void run() {
- Log.i(TAG, "start running");
- runLogcat();
- Log.i(TAG, "stopped");
- }
-
- @SuppressWarnings("unused")
- private int refreshFd(boolean isVerboseLog) {
- try {
- File log;
- if (isVerboseLog) {
- checkFd(verboseFd);
- log = ConfigFileManager.getNewVerboseLogPath();
- } else {
- checkFd(modulesFd);
- log = ConfigFileManager.getNewModulesLogPath();
- }
- Log.i(TAG, "New log file: " + log);
- ConfigFileManager.chattr0(log.toPath().getParent());
- int fd = ParcelFileDescriptor.open(log, mode).detachFd();
- if (isVerboseLog) {
- synchronized (verboseLogs) {
- verboseLogs.put(log, new Object());
- }
- verboseFd = fd;
- } else {
- synchronized (moduleLogs) {
- moduleLogs.put(log, new Object());
- }
- modulesFd = fd;
- }
- return fd;
- } catch (IOException e) {
- if (isVerboseLog) verboseFd = -1;
- else modulesFd = -1;
- Log.w(TAG, "refreshFd", e);
- return -1;
- }
- }
-
- private static void checkFd(int fd) {
- if (fd == -1) return;
- try {
- var jfd = new FileDescriptor();
- //noinspection JavaReflectionMemberAccess DiscouragedPrivateApi
- jfd.getClass().getDeclaredMethod("setInt$", int.class).invoke(jfd, fd);
- var stat = Os.fstat(jfd);
- if (stat.st_nlink == 0) {
- var file = Files.readSymbolicLink(fdToPath(fd));
- var parent = file.getParent();
- if (!Files.isDirectory(parent, LinkOption.NOFOLLOW_LINKS)) {
- if (ConfigFileManager.chattr0(parent))
- Files.deleteIfExists(parent);
- }
- var name = file.getFileName().toString();
- var originName = name.substring(0, name.lastIndexOf(' '));
- Files.copy(file, parent.resolve(originName));
- }
- } catch (Throwable e) {
- Log.w(TAG, "checkFd " + fd, e);
- }
- }
-
- public boolean isRunning() {
- return thread != null && thread.isAlive();
- }
-
- public void start() {
- if (isRunning()) return;
- thread = new Thread(this);
- thread.setName("logcat");
- thread.setUncaughtExceptionHandler((t, e) -> {
- Log.e(TAG, "Crash unexpectedly: ", e);
- thread = null;
- start();
- });
- thread.start();
- }
-
- public void startVerbose() {
- Log.i(TAG, "!!start_verbose!!");
- }
-
- public void stopVerbose() {
- Log.i(TAG, "!!stop_verbose!!");
- }
-
- public void enableWatchdog() {
- Log.i(TAG, "!!start_watchdog!!");
- }
-
- public void disableWatchdog() {
- Log.i(TAG, "!!stop_watchdog!!");
- }
-
- public void refresh(boolean isVerboseLog) {
- if (isVerboseLog) {
- Log.i(TAG, "!!refresh_verbose!!");
- } else {
- Log.i(TAG, "!!refresh_modules!!");
- }
- }
-
- private static Path fdToPath(int fd) {
- if (fd == -1) return null;
- else return Paths.get("/proc/self/fd", String.valueOf(fd));
- }
-
- public File getVerboseLog() {
- var path = fdToPath(verboseFd);
- return path == null ? null : path.toFile();
- }
-
- public File getModulesLog() {
- var path = fdToPath(modulesFd);
- return path == null ? null : path.toFile();
- }
-
- public void checkLogFile() {
- if (modulesFd == -1)
- refresh(false);
- if (verboseFd == -1)
- refresh(true);
- }
-}
diff --git a/daemon/src/main/java/org/lsposed/lspd/service/ObfuscationManager.java b/daemon/src/main/java/org/lsposed/lspd/service/ObfuscationManager.java
deleted file mode 100644
index 4446f2366..000000000
--- a/daemon/src/main/java/org/lsposed/lspd/service/ObfuscationManager.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package org.lsposed.lspd.service;
-
-import android.os.SharedMemory;
-
-import java.util.HashMap;
-
-public class ObfuscationManager {
- // For module dexes
- static native SharedMemory obfuscateDex(SharedMemory memory);
-
- // generates signature
- static native HashMap getSignatures();
-}
diff --git a/daemon/src/main/java/org/lsposed/lspd/service/PackageService.java b/daemon/src/main/java/org/lsposed/lspd/service/PackageService.java
deleted file mode 100644
index eceaa0746..000000000
--- a/daemon/src/main/java/org/lsposed/lspd/service/PackageService.java
+++ /dev/null
@@ -1,435 +0,0 @@
-/*
- * This file is part of LSPosed.
- *
- * LSPosed is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * LSPosed is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with LSPosed. If not, see .
- *
- * Copyright (C) 2021 LSPosed Contributors
- */
-
-package org.lsposed.lspd.service;
-
-import static android.content.pm.ServiceInfo.FLAG_ISOLATED_PROCESS;
-import static org.lsposed.lspd.service.ServiceManager.TAG;
-import static org.lsposed.lspd.service.ServiceManager.existsInGlobalNamespace;
-
-import android.content.IIntentReceiver;
-import android.content.IIntentSender;
-import android.content.Intent;
-import android.content.IntentSender;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.ComponentInfo;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageManager;
-import android.content.pm.ParceledListSlice;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.content.pm.VersionedPackage;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemProperties;
-import android.util.Log;
-import android.util.Pair;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import org.lsposed.lspd.models.Application;
-
-import java.io.BufferedReader;
-import java.io.InputStreamReader;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import java.util.stream.Collectors;
-
-import rikka.parcelablelist.ParcelableListSlice;
-
-public class PackageService {
-
- static final int INSTALL_FAILED_INTERNAL_ERROR = -110;
- static final int INSTALL_REASON_UNKNOWN = 0;
- static final int MATCH_ANY_USER = 0x00400000; // PackageManager.MATCH_ANY_USER
-
- static final int MATCH_ALL_FLAGS = PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE | PackageManager.MATCH_UNINSTALLED_PACKAGES | MATCH_ANY_USER;
- public static final int PER_USER_RANGE = 100000;
-
- private static IPackageManager pm = null;
- private static IBinder binder = null;
- private static final Method getInstalledPackagesMethod;
-
- static {
- Method method = null;
- try {
- boolean isLongFlags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU;
- Class> flagsType = isLongFlags ? long.class : int.class;
-
- for (Method m : IPackageManager.class.getDeclaredMethods()) {
- if (m.getName().equals("getInstalledPackages") &&
- m.getParameterTypes().length == 2 &&
- m.getParameterTypes()[0] == flagsType) {
- m.setAccessible(true);
- method = m;
- break;
- }
- }
- } catch (Exception e) {
- Log.e("PackageManagerUtils", "Failed to find getInstalledPackages method", e);
- }
- getInstalledPackagesMethod = method;
- }
-
- private static List getInstalledPackagesReflect(IPackageManager pm, int flags, int userId) {
- if (getInstalledPackagesMethod == null || pm == null)
- return Collections.emptyList();
- try {
- Object flagsObj;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- flagsObj = (long) flags;
- } else {
- flagsObj = (int) flags;
- }
- Object result = getInstalledPackagesMethod.invoke(pm, flagsObj, userId);
- if (result instanceof ParceledListSlice) {
- // noinspection unchecked
- return ((ParceledListSlice) result).getList();
- }
- } catch (Exception e) {
- Log.w("PackageManagerUtils", "Reflection call failed", e);
- }
- return Collections.emptyList();
- }
-
- static boolean isAlive() {
- var pm = getPackageManager();
- return pm != null && pm.asBinder().isBinderAlive();
- }
-
- private static final IBinder.DeathRecipient recipient = new IBinder.DeathRecipient() {
- @Override
- public void binderDied() {
- Log.w(TAG, "pm is dead");
- binder.unlinkToDeath(this, 0);
- binder = null;
- pm = null;
- }
- };
-
- private static IPackageManager getPackageManager() {
- if (binder == null || pm == null) {
- binder = ServiceManager.getService("package");
- if (binder == null) return null;
- try {
- binder.linkToDeath(recipient, 0);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(e));
- }
- pm = IPackageManager.Stub.asInterface(binder);
- }
- return pm;
- }
-
- @Nullable
- public static PackageInfo getPackageInfo(String packageName, int flags, int userId) throws RemoteException {
- IPackageManager pm = getPackageManager();
- if (pm == null) return null;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- return pm.getPackageInfo(packageName, (long) flags, userId);
- }
- return pm.getPackageInfo(packageName, flags, userId);
- }
-
- public static @NonNull
- Map getPackageInfoFromAllUsers(String packageName, int flags) throws RemoteException {
- IPackageManager pm = getPackageManager();
- Map res = new HashMap<>();
- if (pm == null) return res;
- for (var user : UserService.getUsers()) {
- var info = getPackageInfo(packageName, flags, user.id);
- if (info != null && info.applicationInfo != null) res.put(user.id, info);
- }
- return res;
- }
-
- @Nullable
- public static ApplicationInfo getApplicationInfo(String packageName, int flags, int userId) throws RemoteException {
- IPackageManager pm = getPackageManager();
- if (pm == null) return null;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- return pm.getApplicationInfo(packageName, (long) flags, userId);
- }
- return pm.getApplicationInfo(packageName, flags, userId);
- }
-
- // Only for manager
- public static ParcelableListSlice getInstalledPackagesFromAllUsers(int flags, boolean filterNoProcess)
- throws RemoteException {
- List res = new ArrayList<>();
- IPackageManager pm = getPackageManager();
- if (pm == null) return ParcelableListSlice.emptyList();
- // Prepare flags once outside the loop
- for (var user : UserService.getUsers()) {
- // Use the reflective helper instead of direct AIDL calls
- List infos = getInstalledPackagesReflect(pm, flags, user.id);
- res.addAll(infos.parallelStream()
- .filter(info -> info.applicationInfo != null
- && info.applicationInfo.uid / PER_USER_RANGE == user.id)
- .filter(info -> {
- try {
- return isPackageAvailable(info.packageName, user.id, true);
- } catch (RemoteException e) {
- return false;
- }
- })
- .collect(Collectors.toList()));
- }
- if (filterNoProcess) {
- return new ParcelableListSlice<>(res.parallelStream().filter(packageInfo -> {
- try {
- PackageInfo pkgInfo = getPackageInfoWithComponents(packageInfo.packageName, MATCH_ALL_FLAGS, packageInfo.applicationInfo.uid / PER_USER_RANGE);
- return !fetchProcesses(pkgInfo).isEmpty();
- } catch (RemoteException e) {
- Log.w(TAG, "filter failed", e);
- return true;
- }
- }).collect(Collectors.toList()));
- }
- return new ParcelableListSlice<>(res);
- }
-
- private static Set fetchProcesses(PackageInfo pkgInfo) {
- HashSet processNames = new HashSet<>();
- if (pkgInfo == null) return processNames;
- for (ComponentInfo[] componentInfos : new ComponentInfo[][]{pkgInfo.activities, pkgInfo.receivers, pkgInfo.providers}) {
- if (componentInfos == null) continue;
- for (ComponentInfo componentInfo : componentInfos) {
- processNames.add(componentInfo.processName);
- }
- }
- if (pkgInfo.services == null) return processNames;
- for (ServiceInfo service : pkgInfo.services) {
- if ((service.flags & FLAG_ISOLATED_PROCESS) == 0) {
- processNames.add(service.processName);
- }
- }
- return processNames;
- }
-
- public static Pair, Integer> fetchProcessesWithUid(Application app) throws RemoteException {
- IPackageManager pm = getPackageManager();
- if (pm == null) return new Pair<>(Collections.emptySet(), -1);
- PackageInfo pkgInfo = getPackageInfoWithComponents(app.packageName, MATCH_ALL_FLAGS, app.userId);
- if (pkgInfo == null || pkgInfo.applicationInfo == null)
- return new Pair<>(Collections.emptySet(), -1);
- return new Pair<>(fetchProcesses(pkgInfo), pkgInfo.applicationInfo.uid);
- }
-
- public static boolean isPackageAvailable(String packageName, int userId, boolean ignoreHidden) throws RemoteException {
- return pm.isPackageAvailable(packageName, userId) || (ignoreHidden && pm.getApplicationHiddenSettingAsUser(packageName, userId));
- }
-
- @SuppressWarnings({"ConstantConditions", "SameParameterValue"})
- @Nullable
- private static PackageInfo getPackageInfoWithComponents(String packageName, int flags, int userId) throws RemoteException {
- IPackageManager pm = getPackageManager();
- if (pm == null) return null;
- PackageInfo pkgInfo;
- try {
- pkgInfo = getPackageInfo(packageName, flags | PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES | PackageManager.GET_RECEIVERS | PackageManager.GET_PROVIDERS, userId);
- } catch (Exception e) {
- pkgInfo = getPackageInfo(packageName, flags, userId);
- if (pkgInfo == null) return null;
- try {
- pkgInfo.activities = getPackageInfo(packageName, flags | PackageManager.GET_ACTIVITIES, userId).activities;
- } catch (Exception ignored) {
-
- }
- try {
- pkgInfo.services = getPackageInfo(packageName, flags | PackageManager.GET_SERVICES, userId).services;
- } catch (Exception ignored) {
-
- }
- try {
- pkgInfo.receivers = getPackageInfo(packageName, flags | PackageManager.GET_RECEIVERS, userId).receivers;
- } catch (Exception ignored) {
-
- }
- try {
- pkgInfo.providers = getPackageInfo(packageName, flags | PackageManager.GET_PROVIDERS, userId).providers;
- } catch (Exception ignored) {
-
- }
- }
- if (pkgInfo == null || pkgInfo.applicationInfo == null || (!pkgInfo.packageName.equals("android") && (pkgInfo.applicationInfo.sourceDir == null || !existsInGlobalNamespace(pkgInfo.applicationInfo.sourceDir) || !isPackageAvailable(packageName, userId, true))))
- return null;
- return pkgInfo;
- }
-
- static abstract class IntentSenderAdaptor extends IIntentSender.Stub {
- public abstract void send(Intent intent);
-
- @Override
- public int send(int code, Intent intent, String resolvedType, IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
- send(intent);
- return 0;
- }
-
- @Override
- public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken, IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
- send(intent);
- }
-
- public IntentSender getIntentSender() throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
- @SuppressWarnings("JavaReflectionMemberAccess")
- Constructor intentSenderConstructor = IntentSender.class.getConstructor(IIntentSender.class);
- intentSenderConstructor.setAccessible(true);
- return intentSenderConstructor.newInstance(this);
- }
- }
-
- public static boolean uninstallPackage(VersionedPackage versionedPackage, int userId) throws RemoteException, InterruptedException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
- CountDownLatch latch = new CountDownLatch(1);
- final boolean[] result = {false};
- var flag = userId == -1 ? 0x00000002 : 0; //PackageManager.DELETE_ALL_USERS = 0x00000002; UserHandle ALL = new UserHandle(-1);
- pm.getPackageInstaller().uninstall(versionedPackage, "android", flag, new IntentSenderAdaptor() {
- @Override
- public void send(Intent intent) {
- int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE);
- result[0] = status == PackageInstaller.STATUS_SUCCESS;
- Log.d(TAG, intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE));
- latch.countDown();
- }
- }.getIntentSender(), userId == -1 ? 0 : userId);
- latch.await();
- return result[0];
- }
-
- public static int installExistingPackageAsUser(String packageName, int userId) throws RemoteException {
- IPackageManager pm = getPackageManager();
- Log.d(TAG, "about to install existing package " + packageName + "/" + userId);
- if (pm == null) return INSTALL_FAILED_INTERNAL_ERROR;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- return pm.installExistingPackageAsUser(packageName, userId, 0, INSTALL_REASON_UNKNOWN, null);
- } else {
- return pm.installExistingPackageAsUser(packageName, userId, 0, INSTALL_REASON_UNKNOWN);
- }
- }
-
- @Nullable
- public static ParcelableListSlice queryIntentActivities(Intent intent, String resolvedType, int flags, int userId) {
- try {
- IPackageManager pm = getPackageManager();
- if (pm == null) return null;
- ParceledListSlice infos;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- infos = pm.queryIntentActivities(intent, resolvedType, (long) flags, userId);
- } else {
- infos = pm.queryIntentActivities(intent, resolvedType, flags, userId);
- }
- return new ParcelableListSlice<>(infos.getList());
- } catch (Exception e) {
- Log.e(TAG, "queryIntentActivities", e);
- return new ParcelableListSlice<>(new ArrayList<>());
- }
- }
-
- @Nullable
- public static Intent getLaunchIntentForPackage(String packageName) throws RemoteException {
- Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
- intentToResolve.addCategory(Intent.CATEGORY_INFO);
- intentToResolve.setPackage(packageName);
- var ris = queryIntentActivities(intentToResolve, intentToResolve.getType(), 0, 0);
-
- // Otherwise, try to find a main launcher activity.
- if (ris == null || ris.getList().size() == 0) {
- // reuse the intent instance
- intentToResolve.removeCategory(Intent.CATEGORY_INFO);
- intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER);
- intentToResolve.setPackage(packageName);
- ris = queryIntentActivities(intentToResolve, intentToResolve.getType(), 0, 0);
- }
- if (ris == null || ris.getList().size() == 0) {
- return null;
- }
- Intent intent = new Intent(intentToResolve);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.setClassName(ris.getList().get(0).activityInfo.packageName,
- ris.getList().get(0).activityInfo.name);
- return intent;
- }
-
- public static void clearApplicationProfileData(String packageName) throws RemoteException {
- IPackageManager pm = getPackageManager();
- if (pm == null) return;
- pm.clearApplicationProfileData(packageName);
- }
-
- public static boolean performDexOptMode(String packageName) throws RemoteException {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
- Process process = null;
- try {
- // The 'speed-profile' filter is a balanced choice for performance.
- String command = "cmd package compile -m speed-profile -f " + packageName;
- process = Runtime.getRuntime().exec(command);
-
- // Capture and log the output for debugging.
- StringBuilder output = new StringBuilder();
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
- String line;
- while ((line = reader.readLine()) != null) {
- output.append(line).append("\n");
- }
- }
-
- int exitCode = process.waitFor();
- Log.i(TAG, "Dexopt command finished for " + packageName + " with exit code: " + exitCode);
-
- // A successful command returns exit code 0 and typically "Success" in its output.
- return exitCode == 0 && output.toString().contains("Success");
-
- } catch (Exception e) {
- Log.e(TAG, "Failed to execute dexopt shell command for " + packageName, e);
- if (e instanceof InterruptedException) {
- // Preserve the interrupted status.
- Thread.currentThread().interrupt();
- }
- return false;
- } finally {
- if (process != null) {
- process.destroy();
- }
- }
- } else {
- // Fallback to the original reflection method for older Android versions.
- IPackageManager pm = getPackageManager();
- if (pm == null) return false;
- return pm.performDexOptMode(packageName,
- SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false),
- SystemProperties.get("pm.dexopt.install", "speed-profile"), true, true, null);
- }
- }
-}
diff --git a/daemon/src/main/java/org/lsposed/lspd/service/PowerService.java b/daemon/src/main/java/org/lsposed/lspd/service/PowerService.java
deleted file mode 100644
index 7ce1ac5b6..000000000
--- a/daemon/src/main/java/org/lsposed/lspd/service/PowerService.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- *
- */
-
-package org.lsposed.lspd.service;
-
-import static android.content.Context.POWER_SERVICE;
-import static org.lsposed.lspd.service.ServiceManager.TAG;
-
-import android.os.IBinder;
-import android.os.IPowerManager;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.util.Log;
-
-public class PowerService {
- private static IPowerManager pm = null;
- private static IBinder binder = null;
- private static final IBinder.DeathRecipient recipient = new IBinder.DeathRecipient() {
- @Override
- public void binderDied() {
- Log.w(TAG, "PowerManager is dead");
- binder.unlinkToDeath(this, 0);
- binder = null;
- pm = null;
- }
- };
-
- private static IPowerManager getPowerManager() {
- if (binder == null || pm == null) {
- binder = ServiceManager.getService(POWER_SERVICE);
- if (binder == null) return null;
- try {
- binder.linkToDeath(recipient, 0);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(e));
- }
- pm = IPowerManager.Stub.asInterface(binder);
- }
- return pm;
- }
-
- public static void reboot(boolean confirm, String reason, boolean wait) throws RemoteException {
- IPowerManager pm = getPowerManager();
- if (pm == null) return;
- pm.reboot(confirm, reason, wait);
- }
-}
diff --git a/daemon/src/main/java/org/lsposed/lspd/service/ServiceManager.java b/daemon/src/main/java/org/lsposed/lspd/service/ServiceManager.java
deleted file mode 100644
index e0fbc16af..000000000
--- a/daemon/src/main/java/org/lsposed/lspd/service/ServiceManager.java
+++ /dev/null
@@ -1,314 +0,0 @@
-/*
- * This file is part of LSPosed.
- *
- * LSPosed is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * LSPosed is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with LSPosed. If not, see .
- *
- * Copyright (C) 2021 LSPosed Contributors
- */
-
-package org.lsposed.lspd.service;
-
-import android.app.ActivityThread;
-import android.app.Notification;
-import android.content.Context;
-import android.ddm.DdmHandleAppName;
-import android.os.Binder;
-import android.os.Build;
-import android.os.IBinder;
-import android.os.IServiceManager;
-import android.os.Looper;
-import android.os.Parcel;
-import android.os.Process;
-import android.os.RemoteException;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-
-import com.android.internal.os.BinderInternal;
-
-import org.lsposed.daemon.BuildConfig;
-import org.lsposed.lspd.util.FakeContext;
-
-import java.io.File;
-import java.lang.AbstractMethodError;
-import java.lang.Class;
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-import hidden.HiddenApiBridge;
-
-public class ServiceManager {
- public static final String TAG = "LSPosedService";
- private static final File globalNamespace = new File("/proc/1/root");
- @SuppressWarnings("FieldCanBeLocal")
- private static LSPosedService mainService = null;
- private static LSPApplicationService applicationService = null;
- private static LSPManagerService managerService = null;
- private static LSPSystemServerService systemServerService = null;
- private static LogcatService logcatService = null;
- private static Dex2OatService dex2OatService = null;
-
- public static boolean isLateInject = false;
- public static String proxyServiceName = "serial";
-
- private static final ExecutorService executorService = Executors.newSingleThreadExecutor();
-
- @RequiresApi(Build.VERSION_CODES.Q)
- public static Dex2OatService getDex2OatService() {
- return dex2OatService;
- }
-
- public static ExecutorService getExecutorService() {
- return executorService;
- }
-
- private static void waitSystemService(String name) {
- while (android.os.ServiceManager.getService(name) == null) {
- try {
- Log.i(TAG, "service " + name + " is not started, wait 1s.");
- //noinspection BusyWait
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- Log.i(TAG, Log.getStackTraceString(e));
- }
- }
- }
-
- public static IServiceManager getSystemServiceManager() {
- return IServiceManager.Stub.asInterface(HiddenApiBridge.Binder_allowBlocking(BinderInternal.getContextObject()));
- }
-
- // call by ourselves
- public static void start(String[] args) {
- if (!ConfigFileManager.tryLock()) System.exit(0);
-
- int systemServerMaxRetry = 1;
- for (String arg : args) {
- if (arg.startsWith("--system-server-max-retry=")) {
- try {
- systemServerMaxRetry = Integer.parseInt(arg.substring(arg.lastIndexOf('=') + 1));
- } catch (Throwable ignored) {
- }
- } else if (arg.equals("--late-inject")) {
- isLateInject = true;
- proxyServiceName = "serial_vector";
- }
- }
-
- Log.i(TAG, "Vector daemon started: lateInject: " + isLateInject);
- Log.i(TAG, String.format("version %s (%d)", BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE));
-
- Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
- Log.e(TAG, "Uncaught exception", e);
- System.exit(1);
- });
-
- logcatService = new LogcatService();
- logcatService.start();
-
- // get config before package service is started
- // otherwise getInstance will trigger module/scope cache
- var configManager = ConfigManager.getInstance();
- // --- DO NOT call ConfigManager.getInstance later!!! ---
-
- // Unblock log watchdog before starting anything else
- if (configManager.isLogWatchdogEnabled())
- logcatService.enableWatchdog();
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
- permissionManagerWorkaround();
-
- Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
- Looper.prepareMainLooper();
-
-
- mainService = new LSPosedService();
- applicationService = new LSPApplicationService();
- managerService = new LSPManagerService();
- systemServerService = new LSPSystemServerService(systemServerMaxRetry, proxyServiceName);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- dex2OatService = new Dex2OatService();
- dex2OatService.start();
- }
-
- systemServerService.putBinderForSystemServer();
-
- ActivityThread.systemMain();
-
- DdmHandleAppName.setAppName("org.lsposed.daemon", 0);
-
- waitSystemService("package");
- waitSystemService("activity");
- waitSystemService(Context.USER_SERVICE);
- waitSystemService(Context.APP_OPS_SERVICE);
-
- ConfigFileManager.reloadConfiguration();
-
- notificationWorkaround();
-
- BridgeService.send(mainService, new BridgeService.Listener() {
- @Override
- public void onSystemServerRestarted() {
- Log.w(TAG, "system restarted...");
- }
-
- @Override
- public void onResponseFromBridgeService(boolean response) {
- if (response) {
- Log.i(TAG, "sent service to bridge");
- } else {
- Log.w(TAG, "no response from bridge");
- }
- systemServerService.maybeRetryInject();
- }
-
- @Override
- public void onSystemServerDied() {
- Log.w(TAG, "system server died");
- systemServerService.putBinderForSystemServer();
- managerService.onSystemServerDied();
- }
- });
-
- // Force logging on boot, now let's see if we need to stop logging
- if (!configManager.verboseLog()) {
- logcatService.stopVerbose();
- }
-
- Looper.loop();
- throw new RuntimeException("Main thread loop unexpectedly exited");
- }
-
- public static LSPApplicationService getApplicationService() {
- return applicationService;
- }
-
- public static LSPApplicationService requestApplicationService(int uid, int pid, String processName, IBinder heartBeat) {
- if (applicationService.registerHeartBeat(uid, pid, processName, heartBeat))
- return applicationService;
- else return null;
- }
-
- public static LSPManagerService getManagerService() {
- return managerService;
- }
-
- public static LogcatService getLogcatService() {
- return logcatService;
- }
-
- public static boolean systemServerRequested() {
- return systemServerService.systemServerRequested();
- }
-
- public static File toGlobalNamespace(File file) {
- return new File(globalNamespace, file.getAbsolutePath());
- }
-
- public static File toGlobalNamespace(String path) {
- if (path == null) return null;
- if (path.startsWith("/")) return new File(globalNamespace, path);
- else return toGlobalNamespace(new File(path));
- }
-
- public static boolean existsInGlobalNamespace(File file) {
- return toGlobalNamespace(file).exists();
- }
-
- public static boolean existsInGlobalNamespace(String path) {
- return toGlobalNamespace(path).exists();
- }
-
- private static void permissionManagerWorkaround() {
- try {
- Field sCacheField = android.os.ServiceManager.class.getDeclaredField("sCache");
- sCacheField.setAccessible(true);
- var sCache = (Map) sCacheField.get(null);
- sCache.put("permissionmgr", new BinderProxy("permissionmgr"));
- sCache.put("legacy_permission", new BinderProxy("legacy_permission"));
- sCache.put("appops", new BinderProxy("appops"));
- } catch (Throwable e) {
- Log.e(TAG, "failed to init permission manager", e);
- }
- }
-
- private static void notificationWorkaround() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
- try {
- Class feature = Class.forName("android.app.FeatureFlagsImpl");
- Field systemui_is_cached = feature.getDeclaredField("systemui_is_cached");
- systemui_is_cached.setAccessible(true);
- systemui_is_cached.set(null, true);
- Log.d(TAG, "set flag systemui_is_cached to true");
- } catch (Throwable e) {
- Log.e(TAG, "failed to change feature flags", e);
- }
- }
-
- try {
- new Notification.Builder(new FakeContext(), "notification_workaround").build();
- } catch (AbstractMethodError e) {
- FakeContext.nullProvider = ! FakeContext.nullProvider;
- } catch (Throwable e) {
- Log.e(TAG, "failed to build notifications", e);
- }
-
- }
-
- private static class BinderProxy extends Binder {
- private static final Method rawGetService;
-
- static {
- try {
- rawGetService = android.os.ServiceManager.class.getDeclaredMethod("rawGetService", String.class);
- rawGetService.setAccessible(true);
- } catch (NoSuchMethodException e) {
- throw new RuntimeException(e);
- }
- }
- private IBinder mReal = null;
- private final String mName;
-
- BinderProxy(String name) {
- mName = name;
- }
-
- @Override
- protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
- synchronized (this) {
- if (mReal == null) {
- try {
- mReal = (IBinder) rawGetService.invoke(null, mName);
- } catch (IllegalAccessException | InvocationTargetException ignored){
-
- }
- }
- if (mReal != null) {
- return mReal.transact(code, data, reply, flags);
- }
- }
- // getSplitPermissions
- if (reply != null && mName.equals("permissionmgr"))
- reply.writeTypedList(List.of());
- return true;
- }
- }
-}
diff --git a/daemon/src/main/java/org/lsposed/lspd/service/UserService.java b/daemon/src/main/java/org/lsposed/lspd/service/UserService.java
deleted file mode 100644
index f6eee5597..000000000
--- a/daemon/src/main/java/org/lsposed/lspd/service/UserService.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * This file is part of LSPosed.
- *
- * LSPosed is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * LSPosed is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with LSPosed. If not, see .
- *
- * Copyright (C) 2021 LSPosed Contributors
- */
-
-package org.lsposed.lspd.service;
-
-import static org.lsposed.lspd.service.ServiceManager.TAG;
-
-import android.content.Context;
-import android.content.pm.UserInfo;
-import android.os.Build;
-import android.os.IBinder;
-import android.os.IUserManager;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.util.Log;
-
-import org.lsposed.lspd.util.Utils;
-
-import java.util.LinkedList;
-import java.util.List;
-
-public class UserService {
- private static IUserManager um = null;
- private static IBinder binder = null;
- private static final IBinder.DeathRecipient recipient = new IBinder.DeathRecipient() {
- @Override
- public void binderDied() {
- Log.w(TAG, "um is dead");
- binder.unlinkToDeath(this, 0);
- binder = null;
- um = null;
- }
- };
-
- static boolean isAlive() {
- var um = getUserManager();
- return um != null && um.asBinder().isBinderAlive();
- }
-
- public static IUserManager getUserManager() {
- if (binder == null || um == null) {
- binder = ServiceManager.getService(Context.USER_SERVICE);
- if (binder == null) return null;
- try {
- binder.linkToDeath(recipient, 0);
- } catch (RemoteException e) {
- Log.e(TAG, Log.getStackTraceString(e));
- }
- um = IUserManager.Stub.asInterface(binder);
- }
- return um;
- }
-
- public static List getUsers() throws RemoteException {
- IUserManager um = getUserManager();
- List users = new LinkedList<>();
- if (um == null) return users;
- try {
- users = um.getUsers(true);
- } catch (NoSuchMethodError e) {
- users = um.getUsers(true, true, true);
- }
- if (Utils.isLENOVO) { // lenovo hides user [900, 910) for app cloning
- var gotUsers = new boolean[10];
- for (var user : users) {
- var residual = user.id - 900;
- if (residual >= 0 && residual < 10) gotUsers[residual] = true;
- }
- for (int i = 900; i <= 909; i++) {
- var user = um.getUserInfo(i);
- if (user != null && !gotUsers[i - 900]) {
- users.add(user);
- }
- }
- }
- return users;
- }
-
- public static UserInfo getUserInfo(int userId) throws RemoteException {
- IUserManager um = getUserManager();
- if (um == null) return null;
- return um.getUserInfo(userId);
- }
-
- public static String getUserName(int userId) {
- try {
- var userInfo = getUserInfo(userId);
- if (userInfo != null) return userInfo.name;
- } catch (RemoteException ignored) {
- }
- return String.valueOf(userId);
- }
-
- public static int getProfileParent(int userId) throws RemoteException {
- IUserManager um = getUserManager();
- if (um == null) return -1;
- var userInfo = um.getProfileParent(userId);
- if (userInfo == null) return userId;
- else return userInfo.id;
- }
-
- public static boolean isUserUnlocked(int userId) throws RemoteException {
- IUserManager um = getUserManager();
- if (um == null) return false;
- return um.isUserUnlocked(userId);
- }
-}
diff --git a/daemon/src/main/java/org/lsposed/lspd/util/FakeContext.java b/daemon/src/main/java/org/lsposed/lspd/util/FakeContext.java
deleted file mode 100644
index ce5d5a3ad..000000000
--- a/daemon/src/main/java/org/lsposed/lspd/util/FakeContext.java
+++ /dev/null
@@ -1,96 +0,0 @@
-package org.lsposed.lspd.util;
-
-import static org.lsposed.lspd.service.ServiceManager.TAG;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.os.UserHandle;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-
-import org.lsposed.lspd.service.ConfigFileManager;
-import org.lsposed.lspd.service.PackageService;
-
-import hidden.HiddenApiBridge;
-
-public class FakeContext extends ContextWrapper {
- static ApplicationInfo systemApplicationInfo = null;
- static Resources.Theme theme = null;
-
- public static Boolean nullProvider = false;
-
- private String packageName = "android";
- public FakeContext() {
- super(null);
- }
-
- public FakeContext(String packageName) {
- super(null);
- this.packageName = packageName;
- }
-
- @Override
- public String getPackageName() {
- return packageName;
- }
-
- @Override
- public Resources getResources() {
- return ConfigFileManager.getResources();
- }
-
- @Override
- public String getOpPackageName() {
- return "android";
- }
-
- @Override
- public ApplicationInfo getApplicationInfo() {
- try {
- if (systemApplicationInfo == null)
- systemApplicationInfo = PackageService.getApplicationInfo("android", 0, 0);
- } catch (Throwable e) {
- Log.e(TAG, "getApplicationInfo", e);
- }
- return systemApplicationInfo;
- }
-
- @Override
- public ContentResolver getContentResolver() {
- if (nullProvider) {
- return null;
- } else {
- return new ContentResolver(this) {};
- }
- }
-
- public int getUserId() {
- return 0;
- }
-
- public UserHandle getUser() {
- return HiddenApiBridge.UserHandle(0);
- }
-
- @Override
- public Resources.Theme getTheme() {
- if (theme == null) theme = getResources().newTheme();
- return theme;
- }
-
- @Nullable
- @Override
- public String getAttributionTag() {
- return null;
- }
-
- @Override
- public Context createPackageContext(String packageName, int flags) throws PackageManager.NameNotFoundException {
- throw new PackageManager.NameNotFoundException(packageName);
- }
-}
diff --git a/daemon/src/main/java/org/lsposed/lspd/util/InstallerVerifier.java b/daemon/src/main/java/org/lsposed/lspd/util/InstallerVerifier.java
deleted file mode 100644
index 1d6b81d15..000000000
--- a/daemon/src/main/java/org/lsposed/lspd/util/InstallerVerifier.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package org.lsposed.lspd.util;
-
-import static org.lsposed.lspd.util.SignInfo.CERTIFICATE;
-
-import com.android.apksig.ApkVerifier;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Arrays;
-
-public class InstallerVerifier {
-
- public static void verifyInstallerSignature(String path) throws IOException {
- ApkVerifier verifier = new ApkVerifier.Builder(new File(path))
- .setMinCheckedPlatformVersion(27)
- .build();
- try {
- ApkVerifier.Result result = verifier.verify();
- if (!result.isVerified()) {
- throw new IOException("apk signature not verified");
- }
- var mainCert = result.getSignerCertificates().get(0);
- if (!Arrays.equals(mainCert.getEncoded(), CERTIFICATE)) {
- var dname = mainCert.getSubjectX500Principal().getName();
- throw new IOException("apk signature mismatch: " + dname);
- }
- } catch (Exception t) {
- throw new IOException(t);
- }
- }
-}
diff --git a/daemon/src/main/jni/dex2oat.cpp b/daemon/src/main/jni/dex2oat.cpp
index 062e41343..32cf77d07 100644
--- a/daemon/src/main/jni/dex2oat.cpp
+++ b/daemon/src/main/jni/dex2oat.cpp
@@ -1,21 +1,3 @@
-/*
- * This file is part of LSPosed.
- *
- * LSPosed is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * LSPosed is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with LSPosed. If not, see .
- *
- * Copyright (C) 2023 LSPosed Contributors
- */
#include
#include
#include
@@ -28,50 +10,71 @@
#include "logging.h"
-extern "C" JNIEXPORT void JNICALL Java_org_lsposed_lspd_service_Dex2OatService_doMountNative(
+// Lightweight RAII wrapper to prevent FD leaks
+struct UniqueFd {
+ int fd;
+ explicit UniqueFd(int fd) : fd(fd) {}
+ ~UniqueFd() {
+ if (fd >= 0) close(fd);
+ }
+ operator int() const { return fd; }
+};
+
+extern "C" JNIEXPORT void JNICALL Java_org_matrix_vector_daemon_env_Dex2OatServer_doMountNative(
JNIEnv *env, jobject, jboolean enabled, jstring r32, jstring d32, jstring r64, jstring d64) {
char dex2oat32[PATH_MAX], dex2oat64[PATH_MAX];
- realpath("bin/dex2oat32", dex2oat32);
- realpath("bin/dex2oat64", dex2oat64);
+ if (realpath("bin/dex2oat32", dex2oat32) == nullptr) {
+ PLOGE("resolve realpath for bin/dex2oat32");
+ }
+ if (realpath("bin/dex2oat64", dex2oat64) == nullptr) {
+ PLOGE("resolve realpath for bin/dex2oat64");
+ }
- if (pid_t pid = fork(); pid > 0) { // parent
+ const char *r32p = r32 ? env->GetStringUTFChars(r32, nullptr) : nullptr;
+ const char *d32p = d32 ? env->GetStringUTFChars(d32, nullptr) : nullptr;
+ const char *r64p = r64 ? env->GetStringUTFChars(r64, nullptr) : nullptr;
+ const char *d64p = d64 ? env->GetStringUTFChars(d64, nullptr) : nullptr;
+
+ pid_t pid = fork();
+ if (pid > 0) { // Parent process
waitpid(pid, nullptr, 0);
- } else { // child
- int ns = open("/proc/1/ns/mnt", O_RDONLY);
- setns(ns, CLONE_NEWNS);
- close(ns);
- const char *r32p, *d32p, *r64p, *d64p;
- if (r32) r32p = env->GetStringUTFChars(r32, nullptr);
- if (d32) d32p = env->GetStringUTFChars(d32, nullptr);
- if (r64) r64p = env->GetStringUTFChars(r64, nullptr);
- if (d64) d64p = env->GetStringUTFChars(d64, nullptr);
+ // Safely release JNI strings in the parent
+ if (r32p) env->ReleaseStringUTFChars(r32, r32p);
+ if (d32p) env->ReleaseStringUTFChars(d32, d32p);
+ if (r64p) env->ReleaseStringUTFChars(r64, r64p);
+ if (d64p) env->ReleaseStringUTFChars(d64, d64p);
+ } else if (pid == 0) { // Child process
+ UniqueFd ns(open("/proc/1/ns/mnt", O_RDONLY));
+ if (ns >= 0) {
+ setns(ns, CLONE_NEWNS);
+ }
if (enabled) {
LOGI("Enable dex2oat wrapper");
- if (r32) {
+ if (r32p) {
mount(dex2oat32, r32p, nullptr, MS_BIND, nullptr);
mount(nullptr, r32p, nullptr, MS_BIND | MS_REMOUNT | MS_RDONLY, nullptr);
}
- if (d32) {
+ if (d32p) {
mount(dex2oat32, d32p, nullptr, MS_BIND, nullptr);
mount(nullptr, d32p, nullptr, MS_BIND | MS_REMOUNT | MS_RDONLY, nullptr);
}
- if (r64) {
+ if (r64p) {
mount(dex2oat64, r64p, nullptr, MS_BIND, nullptr);
mount(nullptr, r64p, nullptr, MS_BIND | MS_REMOUNT | MS_RDONLY, nullptr);
}
- if (d64) {
+ if (d64p) {
mount(dex2oat64, d64p, nullptr, MS_BIND, nullptr);
mount(nullptr, d64p, nullptr, MS_BIND | MS_REMOUNT | MS_RDONLY, nullptr);
}
execlp("resetprop", "resetprop", "--delete", "dalvik.vm.dex2oat-flags", nullptr);
} else {
LOGI("Disable dex2oat wrapper");
- if (r32) umount(r32p);
- if (d32) umount(d32p);
- if (r64) umount(r64p);
- if (d64) umount(d64p);
+ if (r32p) umount(r32p);
+ if (d32p) umount(d32p);
+ if (r64p) umount(r64p);
+ if (d64p) umount(d64p);
execlp("resetprop", "resetprop", "dalvik.vm.dex2oat-flags", "--inline-max-code-units=0",
nullptr);
}
@@ -83,8 +86,9 @@ extern "C" JNIEXPORT void JNICALL Java_org_lsposed_lspd_service_Dex2OatService_d
static int setsockcreatecon_raw(const char *context) {
std::string path = "/proc/self/task/" + std::to_string(gettid()) + "/attr/sockcreate";
- int fd = open(path.c_str(), O_RDWR | O_CLOEXEC);
+ UniqueFd fd(open(path.c_str(), O_RDWR | O_CLOEXEC));
if (fd < 0) return -1;
+
int ret;
if (context) {
do {
@@ -95,20 +99,19 @@ static int setsockcreatecon_raw(const char *context) {
ret = write(fd, nullptr, 0); // clear
} while (ret < 0 && errno == EINTR);
}
- close(fd);
return ret < 0 ? -1 : 0;
}
extern "C" JNIEXPORT jboolean JNICALL
-Java_org_lsposed_lspd_service_Dex2OatService_setSockCreateContext(JNIEnv *env, jclass,
- jstring contextStr) {
- const char *context = env->GetStringUTFChars(contextStr, nullptr);
+Java_org_matrix_vector_daemon_env_Dex2OatServer_setSockCreateContext(JNIEnv *env, jclass,
+ jstring contextStr) {
+ const char *context = contextStr ? env->GetStringUTFChars(contextStr, nullptr) : nullptr;
int ret = setsockcreatecon_raw(context);
- env->ReleaseStringUTFChars(contextStr, context);
+ if (context) env->ReleaseStringUTFChars(contextStr, context);
return ret == 0;
}
extern "C" JNIEXPORT jstring JNICALL
-Java_org_lsposed_lspd_service_Dex2OatService_getSockPath(JNIEnv *env, jobject) {
+Java_org_matrix_vector_daemon_env_Dex2OatServer_getSockPath(JNIEnv *env, jobject) {
return env->NewStringUTF("5291374ceda0aef7c5d86cd2a4f6a3ac\0");
}
diff --git a/daemon/src/main/jni/logcat.cpp b/daemon/src/main/jni/logcat.cpp
index 2e5897a39..4c0a0586c 100644
--- a/daemon/src/main/jni/logcat.cpp
+++ b/daemon/src/main/jni/logcat.cpp
@@ -5,8 +5,8 @@
#include
#include
+#include
#include
-#include
#include
#include
#include
@@ -113,8 +113,6 @@ class Logcat {
static size_t PrintLogLine(const AndroidLogEntry &entry, FILE *out);
- void StartLogWatchDog();
-
JNIEnv *env_;
jobject thiz_;
jmethodID refresh_fd_method_;
@@ -130,7 +128,6 @@ class Logcat {
pid_t my_pid_ = getpid();
bool verbose_ = true;
- std::atomic enable_watchdog = std::atomic(false);
};
size_t Logcat::PrintLogLine(const AndroidLogEntry &entry, FILE *out) {
@@ -217,23 +214,32 @@ void Logcat::ProcessBuffer(struct log_msg *buf) {
if (android_log_processLogBuffer(&buf->entry, &entry) < 0) return;
entry.tagLen--;
-
std::string_view tag(entry.tag, entry.tagLen);
bool shortcut = false;
- if (tag == "LSPosed-Bridge"sv || tag == "XSharedPreferences"sv || tag == "LSPosedContext")
+
+ if (tag == "VectorLegacyBridge"sv || tag == "XSharedPreferences"sv || tag == "VectorContext"sv)
[[unlikely]] {
modules_print_count_ += PrintLogLine(entry, modules_file_.get());
shortcut = true;
}
- if (verbose_ && (shortcut || buf->id() == log_id::LOG_ID_CRASH || entry.pid == my_pid_ ||
- tag == "APatchD"sv || tag == "Dobby"sv || tag.starts_with("dex2oat"sv) ||
- tag == "KernelSU"sv || tag == "LSPlant"sv || tag == "LSPlt"sv ||
- tag.starts_with("LSPosed"sv) || tag == "Magisk"sv || tag == "SELinux"sv ||
- tag == "TEESimulator"sv || tag.starts_with("Vector"sv) ||
- tag.starts_with("zygisk"sv))) [[unlikely]] {
+
+ constexpr std::array exact_tags = {
+ "APatchD"sv, "Dobby"sv, "KernelSU"sv, "LSPlant"sv,
+ "LSPlt"sv, "Magisk"sv, "SELinux"sv, "TEESimulator"sv};
+ constexpr std::array prefix_tags = {"dex2oat"sv, "Vector"sv, "LSPosed"sv,
+ "zygisk"sv};
+
+ bool match_exact =
+ std::any_of(exact_tags.begin(), exact_tags.end(), [&](auto t) { return tag == t; });
+ bool match_prefix = std::any_of(prefix_tags.begin(), prefix_tags.end(),
+ [&](auto t) { return tag.starts_with(t); });
+
+ if (verbose_ && (shortcut || buf->id() == log_id::LOG_ID_CRASH || match_exact || match_prefix))
+ [[unlikely]] {
verbose_print_count_ += PrintLogLine(entry, verbose_file_.get());
}
- if (entry.pid == my_pid_ && tag == "LSPosedLogcat"sv) [[unlikely]] {
+
+ if (entry.pid == my_pid_ && tag == "VectorLogcat"sv) [[unlikely]] {
std::string_view msg(entry.message, entry.messageLen);
if (msg == "!!start_verbose!!"sv) {
verbose_ = true;
@@ -244,83 +250,10 @@ void Logcat::ProcessBuffer(struct log_msg *buf) {
RefreshFd(false);
} else if (msg == "!!refresh_verbose!!"sv) {
RefreshFd(true);
- } else if (msg == "!!start_watchdog!!"sv) {
- if (!enable_watchdog) StartLogWatchDog();
- enable_watchdog = true;
- enable_watchdog.notify_one();
- } else if (msg == "!!stop_watchdog!!"sv) {
- enable_watchdog = false;
- enable_watchdog.notify_one();
- std::system("resetprop -p --delete persist.logd.size");
- std::system("resetprop -p --delete persist.logd.size.crash");
- std::system("resetprop -p --delete persist.logd.size.main");
- std::system("resetprop -p --delete persist.logd.size.system");
-
- // Terminate the watchdog thread by exiting __system_property_wait firs firstt
- std::system("setprop persist.log.tag V");
- std::system("resetprop -p --delete persist.log.tag");
}
}
}
-void Logcat::StartLogWatchDog() {
- constexpr static auto kLogdSizeProp = "persist.logd.size"sv;
- constexpr static auto kLogdTagProp = "persist.log.tag"sv;
- constexpr static auto kLogdCrashSizeProp = "persist.logd.size.crash"sv;
- constexpr static auto kLogdMainSizeProp = "persist.logd.size.main"sv;
- constexpr static auto kLogdSystemSizeProp = "persist.logd.size.system"sv;
- constexpr static long kErr = -1;
- std::thread watchdog([this] {
- Log("[LogWatchDog started]\n");
- while (true) {
- enable_watchdog.wait(false); // Blocking current thread until enable_watchdog is true;
- auto logd_size = GetByteProp(kLogdSizeProp);
- auto logd_tag = GetStrProp(kLogdTagProp);
- auto logd_crash_size = GetByteProp(kLogdCrashSizeProp);
- auto logd_main_size = GetByteProp(kLogdMainSizeProp);
- auto logd_system_size = GetByteProp(kLogdSystemSizeProp);
- Log("[LogWatchDog running] log.tag: " + logd_tag +
- "; logd.[default, crash, main, system].size: [" + std::to_string(logd_size) + "," +
- std::to_string(logd_crash_size) + "," + std::to_string(logd_main_size) + "," +
- std::to_string(logd_system_size) + "]\n");
- if (!logd_tag.empty() ||
- !((logd_crash_size == kErr && logd_main_size == kErr && logd_system_size == kErr &&
- logd_size != kErr && logd_size >= kLogBufferSize) ||
- (logd_crash_size != kErr && logd_crash_size >= kLogBufferSize &&
- logd_main_size != kErr && logd_main_size >= kLogBufferSize &&
- logd_system_size != kErr && logd_system_size >= kLogBufferSize))) {
- SetIntProp(kLogdSizeProp, std::max(kLogBufferSize, logd_size));
- SetIntProp(kLogdCrashSizeProp, std::max(kLogBufferSize, logd_crash_size));
- SetIntProp(kLogdMainSizeProp, std::max(kLogBufferSize, logd_main_size));
- SetIntProp(kLogdSystemSizeProp, std::max(kLogBufferSize, logd_system_size));
- SetStrProp(kLogdTagProp, "");
- SetStrProp("ctl.start", "logd-reinit");
- }
- const auto *pi = __system_property_find(kLogdTagProp.data());
- uint32_t serial = 0;
- if (pi != nullptr) {
- __system_property_read_callback(
- pi, [](auto *c, auto, auto, auto s) { *reinterpret_cast(c) = s; },
- &serial);
- }
- if (!__system_property_wait(pi, serial, &serial, nullptr)) break;
- if (pi != nullptr) {
- if (enable_watchdog) {
- Log("\nProp persist.log.tag changed, resetting log settings\n");
- } else {
- break; // End current thread as expected
- }
- } else {
- // log tag prop was not found; to avoid frequently trigger wait, sleep for a while
- std::this_thread::sleep_for(1s);
- }
- }
- Log("[LogWatchDog stopped]\n");
- });
- pthread_setname_np(watchdog.native_handle(), "watchdog");
- watchdog.detach();
-}
-
void Logcat::Run() {
constexpr size_t tail_after_crash = 10U;
size_t tail = 0;
@@ -354,14 +287,12 @@ void Logcat::Run() {
if (modules_print_count_ >= kMaxLogSize) [[unlikely]]
RefreshFd(false);
}
-
OnCrash(errno);
}
}
extern "C" JNIEXPORT void JNICALL
-// NOLINTNEXTLINE
-Java_org_lsposed_lspd_service_LogcatService_runLogcat(JNIEnv *env, jobject thiz) {
+Java_org_matrix_vector_daemon_env_LogcatMonitor_runLogcat(JNIEnv *env, jobject thiz) {
jclass clazz = env->GetObjectClass(thiz);
jmethodID method = env->GetMethodID(clazz, "refreshFd", "(Z)I");
Logcat logcat(env, thiz, method);
diff --git a/daemon/src/main/jni/logcat.h b/daemon/src/main/jni/logcat.h
index cc39b58a1..e2560ccc3 100644
--- a/daemon/src/main/jni/logcat.h
+++ b/daemon/src/main/jni/logcat.h
@@ -1,10 +1,9 @@
#pragma once
+#include
#include
#include
-#include
-
#define NS_PER_SEC 1000000000L
#define MS_PER_NSEC 1000000
#define LOGGER_ENTRY_MAX_LEN (5 * 1024)
@@ -43,15 +42,13 @@ struct log_msg {
struct logger_entry entry;
};
#ifdef __cplusplus
- log_id_t id() {
- return static_cast(entry.lid);
- }
+ log_id_t id() { return static_cast(entry.lid); }
#endif
};
struct logger;
struct logger_list;
-long android_logger_get_log_size(struct logger* logger);
+long android_logger_get_log_size(struct logger *logger);
int android_logger_set_log_size(struct logger *logger, unsigned long size);
struct logger_list *android_logger_list_alloc(int mode, unsigned int tail, pid_t pid);
void android_logger_list_free(struct logger_list *logger_list);
diff --git a/daemon/src/main/jni/logging.h b/daemon/src/main/jni/logging.h
index 86a7220ac..e25e000dc 100644
--- a/daemon/src/main/jni/logging.h
+++ b/daemon/src/main/jni/logging.h
@@ -1,23 +1,3 @@
-/*
- * This file is part of LSPosed.
- *
- * LSPosed is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * LSPosed is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with LSPosed. If not, see .
- *
- * Copyright (C) 2020 EdXposed Contributors
- * Copyright (C) 2021 LSPosed Contributors
- */
-
#ifndef _LOGGING_H
#define _LOGGING_H
@@ -25,7 +5,7 @@
#include
#ifndef LOG_TAG
-#define LOG_TAG "LSPosed"
+#define LOG_TAG "VectorNativeDaemon"
#endif
#ifdef LOG_DISABLED
@@ -36,8 +16,12 @@
#define LOGE(...) 0
#else
#ifndef NDEBUG
-#define LOGD(fmt, ...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "%s:%d#%s" ": " fmt, __FILE_NAME__, __LINE__, __PRETTY_FUNCTION__ __VA_OPT__(,) __VA_ARGS__)
-#define LOGV(fmt, ...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, "%s:%d#%s" ": " fmt, __FILE_NAME__, __LINE__, __PRETTY_FUNCTION__ __VA_OPT__(,) __VA_ARGS__)
+#define LOGD(fmt, ...) \
+ __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, \
+ "%s:%d#%s" \
+ ": " fmt, \
+ __FILE_NAME__, __LINE__, __PRETTY_FUNCTION__ __VA_OPT__(, ) __VA_ARGS__)
+#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
#else
#define LOGD(...) 0
#define LOGV(...) 0
diff --git a/daemon/src/main/jni/obfuscation.cpp b/daemon/src/main/jni/obfuscation.cpp
index 9965f42f1..d6ecc83fa 100644
--- a/daemon/src/main/jni/obfuscation.cpp
+++ b/daemon/src/main/jni/obfuscation.cpp
@@ -23,10 +23,10 @@ namespace {
std::once_flag init_flag;
std::map signatures = {
- {"Lde/robv/android/xposed/", ""}, {"Landroid/app/AndroidApp", ""},
- {"Landroid/content/res/XRes", ""}, {"Landroid/content/res/XModule", ""},
- {"Lorg/matrix/vector/core/", ""}, {"Lorg/matrix/vector/nativebridge/", ""},
- {"Lorg/matrix/vector/service/", ""},
+ {"Lde/robv/android/xposed/", ""}, {"Landroid/app/AndroidApp", ""},
+ {"Landroid/content/res/XRes", ""}, {"Landroid/content/res/XModule", ""},
+ {"Lio/github/libxposed/api/Xposed", ""}, {"Lorg/matrix/vector/core/", ""},
+ {"Lorg/matrix/vector/nativebridge/", ""}, {"Lorg/matrix/vector/service/", ""},
};
jclass class_file_descriptor = nullptr;
@@ -112,7 +112,7 @@ static void ensureInitialized(JNIEnv *env) {
for (auto &i : signatures) {
i.second = regen(i.first);
- LOGD("%s => %s", i.first.c_str(), i.second.c_str());
+ LOGV("%s => %s", i.first.c_str(), i.second.c_str());
}
LOGD("ObfuscationManager init successfully");
@@ -145,7 +145,8 @@ static jobject stringMapToJavaHashMap(JNIEnv *env, const std::map(dex_data), size};
reader.CreateFullIr();
auto ir = reader.GetIr();
@@ -191,29 +191,32 @@ static int obfuscateDexBuffer(const void *dex_data, size_t size) {
return allocator.GetFd();
}
-extern "C" JNIEXPORT jobject JNICALL Java_org_lsposed_lspd_service_ObfuscationManager_obfuscateDex(
- JNIEnv *env, [[maybe_unused]] jclass clazz, jobject memory) {
+extern "C" JNIEXPORT jobject JNICALL
+Java_org_matrix_vector_daemon_utils_ObfuscationManager_obfuscateDex(JNIEnv *env,
+ [[maybe_unused]] jclass clazz,
+ jobject memory) {
ensureInitialized(env);
int fd = ASharedMemory_dupFromJava(env, memory);
if (fd < 0) return nullptr;
auto size = ASharedMemory_getSize(fd);
- LOGD("obfuscateDex: fd=%d, size=%zu", fd, size);
+ LOGV("obfuscateDex: fd=%d, size=%zu", fd, size);
// CRITICAL: We MUST use MAP_SHARED here, not MAP_PRIVATE.
- // 1. Android's SharedMemory is backed by purely virtual IPC buffers (ashmem/memfd).
- // If we use MAP_PRIVATE, the kernel attempts to create a Copy-On-Write snapshot.
- // Because the Java side just populated this virtual buffer and immediately passed
- // it to JNI, mapping it MAP_PRIVATE often results in mapping unpopulated zero-pages,
- // which causes Slicer to read a corrupted/empty header and abort.
- // 2. Using MAP_SHARED gives us direct pointers to the exact physical memory pages
- // populated by Java.
- // 3. ZERO-COPY ARCHITECTURE: Because Slicer's IR holds direct pointers to this mapped
- // memory, mutating strings in-place (via const_cast) instantly updates the IR
- // without allocating new memory. Since the Java caller discards the original
- // SharedMemory buffer anyway, this in-place mutation is completely safe and highly
- // efficient.
+ // 1. Android's SharedMemory is backed by ashmem or memfd. Mapping these as
+ // MAP_PRIVATE creates a Copy-On-Write (COW) layer. In many Android kernel
+ // configurations, this COW layer does not correctly fault-in the initial
+ // contents from the shared source, resulting in the JNI side seeing
+ // unpopulated zero-pages. This causes slicer to fail immediately.
+ // 2. Using MAP_SHARED ensures we have direct access to the same physical
+ // pages populated by the Java layer.
+ // 3. ZERO-COPY MUTATION: Slicer's Intermediate Representation (IR) points
+ // directly into this mapped memory for string data. By mutating the
+ // buffer in-place, we update the IR's state without any additional
+ // heap allocations. This is safe here because the Daemon owns the
+ // lifecycle of this temporary buffer and the Java caller will discard
+ // the un-obfuscated original anyway.
void *mem = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (mem == MAP_FAILED) {
LOGE("Failed to map input dex");
@@ -230,7 +233,7 @@ extern "C" JNIEXPORT jobject JNICALL Java_org_lsposed_lspd_service_ObfuscationMa
}
if (!needs_obfuscation) {
- LOGD("No target signatures found in fd=%d, skipping slicer.", fd);
+ LOGV("No target signatures found in fd=%d, skipping slicer.", fd);
munmap(mem, size);
// Wrap the duplicated FD into Java objects and return instantly
diff --git a/daemon/src/main/jni/obfuscation.h b/daemon/src/main/jni/obfuscation.h
index 16b81124c..27861659a 100644
--- a/daemon/src/main/jni/obfuscation.h
+++ b/daemon/src/main/jni/obfuscation.h
@@ -16,7 +16,7 @@ class DexAllocator : public dex::Writer::Allocator {
public:
inline void* Allocate(size_t size) override {
- LOGD("DexAllocator: attempting to allocate %zu bytes", size);
+ LOGV("DexAllocator: attempting to allocate %zu bytes", size);
fd_ = ASharedMemory_create("obfuscated_dex", size);
if (fd_ < 0) {
@@ -37,7 +37,7 @@ class DexAllocator : public dex::Writer::Allocator {
mapped_mem_ = nullptr;
}
- LOGD("DexAllocator: success, mapped at %p, fd=%d", mapped_mem_, fd_);
+ LOGV("DexAllocator: success, mapped at %p, fd=%d", mapped_mem_, fd_);
return mapped_mem_;
}
diff --git a/daemon/src/main/kotlin/org/matrix/vector/daemon/Cli.kt b/daemon/src/main/kotlin/org/matrix/vector/daemon/Cli.kt
new file mode 100644
index 000000000..b305fbc11
--- /dev/null
+++ b/daemon/src/main/kotlin/org/matrix/vector/daemon/Cli.kt
@@ -0,0 +1,364 @@
+package org.matrix.vector.daemon
+
+import android.net.LocalSocket
+import android.net.LocalSocketAddress
+import android.os.Process
+import com.google.gson.Gson
+import com.google.gson.GsonBuilder
+import com.google.gson.ToNumberPolicy
+import java.io.DataInputStream
+import java.io.DataOutputStream
+import java.io.FileDescriptor
+import java.io.FileInputStream
+import java.util.concurrent.Callable
+import kotlin.system.exitProcess
+import org.matrix.vector.daemon.data.FileSystem
+import picocli.CommandLine
+import picocli.CommandLine.*
+
+// --- IPC Data Models ---
+data class CliRequest(
+ val command: String,
+ val action: String = "",
+ val targets: List = emptyList(),
+ val options: Map = emptyMap()
+)
+
+data class CliResponse(
+ val success: Boolean,
+ val data: Any? = null,
+ val error: String? = null,
+ val isFdAttached: Boolean = false
+)
+
+// --- IPC Client Logic ---
+object VectorIPC {
+ val gson: Gson =
+ GsonBuilder()
+ .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) // Handles Any/Object fields
+ .setNumberToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) // Handles Map values
+ .setPrettyPrinting()
+ .create()
+
+ fun transmit(request: CliRequest): CliResponse {
+ val socket = LocalSocket()
+ return try {
+ val cliSocket = FileSystem.socketPath.toString()
+ val socketFile = java.io.File(cliSocket)
+
+ if (!socketFile.exists()) {
+ System.err.println("Error: Socket file not found at $cliSocket")
+ System.err.println("Current UID: ${android.os.Process.myUid()}")
+ }
+ socket.connect(LocalSocketAddress(cliSocket, LocalSocketAddress.Namespace.FILESYSTEM))
+
+ val output = DataOutputStream(socket.outputStream)
+ val input = DataInputStream(socket.inputStream)
+
+ // Send Security Token
+ output.writeLong(BuildConfig.CLI_TOKEN_MSB)
+ output.writeLong(BuildConfig.CLI_TOKEN_LSB)
+
+ // Send Request
+ output.writeUTF(gson.toJson(request))
+
+ // Read Response
+ val responseJson = input.readUTF()
+ val response = gson.fromJson(responseJson, CliResponse::class.java)
+
+ // Handle Log Streaming
+ if (response.isFdAttached) {
+ val hasFd = input.readByte()
+ if (hasFd.toInt() == 1) {
+ val fds = socket.getAncillaryFileDescriptors()
+ if (!fds.isNullOrEmpty()) {
+ streamLog(fds[0], request.options["follow"] as? Boolean ?: false)
+ }
+ }
+ }
+ response
+ } catch (e: Exception) {
+ CliResponse(success = false, error = "Socket Failure: ${e.message}")
+ } finally {
+ socket.close()
+ }
+ }
+
+ private fun streamLog(fd: java.io.FileDescriptor, follow: Boolean) {
+ // Wrap the raw FileDescriptor in a FileInputStream.
+ // 'use' ensures that fis.close() (and thus the FD) is called
+ // when the block finishes or if an exception is thrown.
+ FileInputStream(fd).use { fis ->
+ val reader = fis.bufferedReader()
+
+ try {
+ while (true) {
+ val line = reader.readLine()
+ if (line != null) {
+ println(line)
+ } else {
+ if (!follow) break // EOF reached, exit
+
+ // In follow mode, wait for new data to be written to the log
+ Thread.sleep(100)
+ }
+
+ // Check if thread was interrupted (e.g. by a shutdown hook)
+ if (Thread.interrupted()) break
+ }
+ } catch (e: Exception) {
+ if (e !is InterruptedException) {
+ System.err.println("Log streaming error: ${e.message}")
+ }
+ }
+ } // FD is closed here automatically
+ }
+}
+
+// --- UI Formatter ---
+object OutputFormatter {
+ /**
+ * Auto-formats the Daemon's output. Prints ASCII tables for lists, Key-Value for maps, or raw
+ * JSON.
+ */
+ @Suppress("UNCHECKED_CAST")
+ fun print(response: CliResponse, isJson: Boolean): Int {
+ if (isJson) {
+ println(VectorIPC.gson.toJson(response))
+ return if (response.success) 0 else 1
+ }
+
+ if (!response.success) {
+ System.err.println("Error: ${response.error}")
+ return 1
+ }
+
+ val data = response.data ?: return 0
+
+ when (data) {
+ is List<*> -> {
+ if (data.isEmpty()) {
+ println("No records found.")
+ return 0
+ }
+ // Check if it's a list of objects/maps to draw a table
+ val first = data[0]
+ if (first is Map<*, *>) {
+ printTable(data as List