diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml
index d3b6ac7953e..e827084d1ed 100644
--- a/.github/workflows/core.yml
+++ b/.github/workflows/core.yml
@@ -62,9 +62,6 @@ jobs:
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- - name: Grant Execute Permission for gradlew
- run: chmod +x gradlew
-
- name: Setup ninja
uses: seanmiddleditch/gha-setup-ninja@v5
with:
@@ -75,14 +72,6 @@ jobs:
with:
version: latest
- - name: Setup ccache
- uses: hendrikmuhs/ccache-action@v1.2
- with:
- max-size: 2G
- key: ${{ runner.os }}
- restore-keys: ${{ runner.os }}
- save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
-
- name: Remove Android's cmake
shell: bash
run: rm -rf $ANDROID_HOME/cmake
diff --git a/.gitmodules b/.gitmodules
index 533f7b78365..6b59e25a71f 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -19,3 +19,6 @@
[submodule "external/xz-embedded"]
path = external/xz-embedded
url = https://github.com/tukaani-project/xz-embedded
+[submodule "apache/commons-lang"]
+ path = apache/commons-lang
+ url = https://github.com/apache/commons-lang
diff --git a/apache/build.gradle.kts b/apache/build.gradle.kts
new file mode 100644
index 00000000000..af8055912b2
--- /dev/null
+++ b/apache/build.gradle.kts
@@ -0,0 +1,16 @@
+val androidSourceCompatibility: JavaVersion by rootProject.extra
+val androidTargetCompatibility: JavaVersion by rootProject.extra
+
+plugins {
+ id("java-library")
+}
+
+java {
+ sourceCompatibility = androidSourceCompatibility
+ targetCompatibility = androidTargetCompatibility
+ sourceSets {
+ main {
+ java.srcDirs("commons-lang/src/main/java", "local")
+ }
+ }
+}
diff --git a/apache/commons-lang b/apache/commons-lang
new file mode 160000
index 00000000000..c2cd0525d97
--- /dev/null
+++ b/apache/commons-lang
@@ -0,0 +1 @@
+Subproject commit c2cd0525d97495daa820d2fcb29d6875b52c09a6
diff --git a/apache/local/org/apache/commons/lang3/reflect/MemberUtilsX.java b/apache/local/org/apache/commons/lang3/reflect/MemberUtilsX.java
new file mode 100644
index 00000000000..36c872096fb
--- /dev/null
+++ b/apache/local/org/apache/commons/lang3/reflect/MemberUtilsX.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3.reflect;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+public class MemberUtilsX {
+ public static int compareConstructorFit(final Constructor> left, final Constructor> right, final Class>[] actual) {
+ return MemberUtils.compareConstructorFit(left, right, actual);
+ }
+
+ public static int compareMethodFit(final Method left, final Method right, final Class>[] actual) {
+ return MemberUtils.compareMethodFit(left, right, actual);
+ }
+}
diff --git a/app/src/main/java/org/lsposed/manager/ConfigManager.java b/app/src/main/java/org/lsposed/manager/ConfigManager.java
index 22dfca148bf..983c76fbb50 100644
--- a/app/src/main/java/org/lsposed/manager/ConfigManager.java
+++ b/app/src/main/java/org/lsposed/manager/ConfigManager.java
@@ -178,6 +178,25 @@ public static boolean setVerboseLogEnabled(boolean enabled) {
}
}
+ public static boolean isLogWatchdogEnabled() {
+ try {
+ return LSPManagerServiceHolder.getService().isLogWatchdogEnabled();
+ } catch (RemoteException e) {
+ Log.e(App.TAG, Log.getStackTraceString(e));
+ return false;
+ }
+ }
+
+ public static boolean setLogWatchdog(boolean enabled) {
+ try {
+ LSPManagerServiceHolder.getService().setLogWatchdog(enabled);
+ return true;
+ } catch (RemoteException e) {
+ Log.e(App.TAG, Log.getStackTraceString(e));
+ return false;
+ }
+ }
+
public static ParcelFileDescriptor getLog(boolean verbose) {
try {
return verbose ? LSPManagerServiceHolder.getService().getVerboseLog() : LSPManagerServiceHolder.getService().getModulesLog();
diff --git a/app/src/main/java/org/lsposed/manager/ui/fragment/ModulesFragment.java b/app/src/main/java/org/lsposed/manager/ui/fragment/ModulesFragment.java
index d9c8a85a955..3df1a1c3a52 100644
--- a/app/src/main/java/org/lsposed/manager/ui/fragment/ModulesFragment.java
+++ b/app/src/main/java/org/lsposed/manager/ui/fragment/ModulesFragment.java
@@ -608,7 +608,9 @@ public void onLoadCleared(@Nullable Drawable placeholder) {
holder.root.setAlpha(moduleUtil.isModuleEnabled(item.packageName) ? 1.0f : .5f);
holder.itemView.setOnClickListener(v -> {
searchView.clearFocus();
- safeNavigate(ModulesFragmentDirections.actionModulesFragmentToAppListFragment(item.packageName, item.userId));
+ if (isLoaded()) {
+ safeNavigate(ModulesFragmentDirections.actionModulesFragmentToAppListFragment(item.packageName, item.userId));
+ }
});
holder.itemView.setOnLongClickListener(v -> {
searchView.clearFocus();
diff --git a/app/src/main/java/org/lsposed/manager/ui/fragment/SettingsFragment.java b/app/src/main/java/org/lsposed/manager/ui/fragment/SettingsFragment.java
index 2d06c1e50b3..ed7d759176e 100644
--- a/app/src/main/java/org/lsposed/manager/ui/fragment/SettingsFragment.java
+++ b/app/src/main/java/org/lsposed/manager/ui/fragment/SettingsFragment.java
@@ -160,6 +160,13 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
prefVerboseLogs.setOnPreferenceChangeListener((preference, newValue) -> ConfigManager.setVerboseLogEnabled(!(boolean) newValue));
}
+ MaterialSwitchPreference prefEnableLog = findPreference("force_enable_log");
+ if (prefEnableLog != null) {
+ prefEnableLog.setEnabled(!BuildConfig.DEBUG && installed);
+ prefEnableLog.setChecked(!installed || ConfigManager.isLogWatchdogEnabled());
+ prefEnableLog.setOnPreferenceChangeListener((preference, newValue) -> ConfigManager.setLogWatchdog((boolean) newValue));
+ }
+
MaterialSwitchPreference prefDexObfuscate = findPreference("enable_dex_obfuscate");
if (prefDexObfuscate != null) {
prefDexObfuscate.setEnabled(installed);
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 1ad115eabd6..a3f506803f5 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -175,6 +175,8 @@
Framework
Disable verbose logs
Report issues request to include verbose logs
+ Enable log watchdog
+ Log watchdog of LSPosed modifies system properties, which could be exploited to detect LSPosed
Black dark theme
Use the pure black theme if dark theme is enabled
Theme
diff --git a/app/src/main/res/xml/prefs.xml b/app/src/main/res/xml/prefs.xml
index f72192884b8..69b918e6581 100644
--- a/app/src/main/res/xml/prefs.xml
+++ b/app/src/main/res/xml/prefs.xml
@@ -91,6 +91,14 @@
android:summary="@string/settings_disable_verbose_log_summary"
android:title="@string/settings_disable_verbose_log" />
+
+
= Build.VERSION_CODES.R)
permissionManagerWorkaround();
@@ -133,11 +142,6 @@ public static void start(String[] args) {
systemServerService.putBinderForSystemServer();
- // get config before package service is started
- // otherwise getInstance will trigger module/scope cache
- var configManager = ConfigManager.getInstance();
- // --- DO NOT call ConfigManager.getInstance later!!! ---
-
ActivityThread.systemMain();
DdmHandleAppName.setAppName("org.lsposed.daemon", 0);
diff --git a/daemon/src/main/jni/logcat.cpp b/daemon/src/main/jni/logcat.cpp
index af5aeb73965..3f0ec340f03 100644
--- a/daemon/src/main/jni/logcat.cpp
+++ b/daemon/src/main/jni/logcat.cpp
@@ -5,6 +5,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -129,6 +130,7 @@ 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) {
@@ -241,6 +243,12 @@ void Logcat::ProcessBuffer(struct log_msg *buf) {
RefreshFd(false);
} else if (msg == "!!refresh_verbose!!"sv) {
RefreshFd(true);
+ } else if (msg == "!!start_watchdog!!"sv) {
+ enable_watchdog = true;
+ enable_watchdog.notify_one();
+ } else if (msg == "!!stop_watchdog!!"sv) {
+ enable_watchdog = false;
+ enable_watchdog.notify_one();
}
}
}
@@ -253,6 +261,7 @@ void Logcat::EnsureLogWatchDog() {
constexpr static size_t kErr = -1;
std::thread watch_dog([this] {
while (true) {
+ enable_watchdog.wait(false);
auto logd_size = GetByteProp(kLogdSizeProp);
auto logd_tag = GetStrProp(kLogdTagProp);
auto logd_main_size = GetByteProp(kLogdMainSizeProp);
@@ -277,8 +286,9 @@ void Logcat::EnsureLogWatchDog() {
}, &serial);
}
if (!__system_property_wait(pi, serial, &serial, nullptr)) break;
- if (pi != nullptr) Log("\nResetting log settings\n");
- else std::this_thread::sleep_for(1s);
+ if (pi != nullptr) {
+ if (enable_watchdog) Log("\nResetting log settings\n");
+ } else std::this_thread::sleep_for(1s);
// log tag prop was not found; to avoid frequently trigger wait, sleep for a while
}
});
diff --git a/dex2oat/src/main/cpp/dex2oat.c b/dex2oat/src/main/cpp/dex2oat.c
index 1e120623e96..8db3c79aab0 100644
--- a/dex2oat/src/main/cpp/dex2oat.c
+++ b/dex2oat/src/main/cpp/dex2oat.c
@@ -23,6 +23,7 @@
#include
#include
+#include
#include
#include
#include
@@ -31,9 +32,9 @@
#include "logging.h"
#if defined(__LP64__)
-# define LP_SELECT(lp32, lp64) lp64
+#define LP_SELECT(lp32, lp64) lp64
#else
-# define LP_SELECT(lp32, lp64) lp32
+#define LP_SELECT(lp32, lp64) lp32
#endif
#define ID_VEC(is64, is_debug) (((is64) << 1) | (is_debug))
@@ -50,23 +51,17 @@ static ssize_t xrecvmsg(int sockfd, struct msghdr *msg, int flags) {
static void *recv_fds(int sockfd, char *cmsgbuf, size_t bufsz, int cnt) {
struct iovec iov = {
- .iov_base = &cnt,
- .iov_len = sizeof(cnt),
+ .iov_base = &cnt,
+ .iov_len = sizeof(cnt),
};
struct msghdr msg = {
- .msg_iov = &iov,
- .msg_iovlen = 1,
- .msg_control = cmsgbuf,
- .msg_controllen = bufsz
- };
+ .msg_iov = &iov, .msg_iovlen = 1, .msg_control = cmsgbuf, .msg_controllen = bufsz};
xrecvmsg(sockfd, &msg, MSG_WAITALL);
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
- if (msg.msg_controllen != bufsz ||
- cmsg == NULL ||
- cmsg->cmsg_len != CMSG_LEN(sizeof(int) * cnt) ||
- cmsg->cmsg_level != SOL_SOCKET ||
+ if (msg.msg_controllen != bufsz || cmsg == NULL ||
+ cmsg->cmsg_len != CMSG_LEN(sizeof(int) * cnt) || cmsg->cmsg_level != SOL_SOCKET ||
cmsg->cmsg_type != SCM_RIGHTS) {
return NULL;
}
@@ -78,8 +73,7 @@ static int recv_fd(int sockfd) {
char cmsgbuf[CMSG_SPACE(sizeof(int))];
void *data = recv_fds(sockfd, cmsgbuf, sizeof(cmsgbuf), 1);
- if (data == NULL)
- return -1;
+ if (data == NULL) return -1;
int result;
memcpy(&result, data, sizeof(int));
@@ -88,8 +82,7 @@ static int recv_fd(int sockfd) {
static int read_int(int fd) {
int val;
- if (read(fd, &val, sizeof(val)) != sizeof(val))
- return -1;
+ if (read(fd, &val, sizeof(val)) != sizeof(val)) return -1;
return val;
}
@@ -105,7 +98,7 @@ int main(int argc, char **argv) {
strlcpy(sock.sun_path + 1, kSockName, sizeof(sock.sun_path) - 1);
int sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
size_t len = sizeof(sa_family_t) + strlen(sock.sun_path + 1) + 1;
- if (connect(sock_fd, (struct sockaddr *) &sock, len)) {
+ if (connect(sock_fd, (struct sockaddr *)&sock, len)) {
PLOGE("failed to connect to %s", sock.sun_path + 1);
return 1;
}
@@ -119,7 +112,14 @@ int main(int argc, char **argv) {
for (int i = 0; i < argc; i++) new_argv[i] = argv[i];
new_argv[argc] = "--inline-max-code-units=0";
new_argv[argc + 1] = NULL;
- fexecve(stock_fd, (char **) new_argv, environ);
+
+ if (getenv("LD_LIBRARY_PATH") == NULL) {
+ char const *libenv =
+ "LD_LIBRARY_PATH=/apex/com.android.art/lib64:/apex/com.android.art/lib"
+ ":/apex/com.android.os.statsd/lib64:/apex/com.android.os.statsd/lib";
+ putenv((char *)libenv);
+ }
+ fexecve(stock_fd, (char **)new_argv, environ);
PLOGE("fexecve failed");
return 2;
}
diff --git a/external/lsplant b/external/lsplant
index d73aa7f9742..acdd096d3ef 160000
--- a/external/lsplant
+++ b/external/lsplant
@@ -1 +1 @@
-Subproject commit d73aa7f9742b3c13084bb719962cfcd9358dd0f3
+Subproject commit acdd096d3ef4220d6cf27b71308606eaa8741aac
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 05c492804b0..73fe5f49dbc 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,7 +1,7 @@
[versions]
-agp = "8.6.0"
+agp = "8.6.1"
kotlin = "2.0.20"
-nav = "2.7.7"
+nav = "2.8.0"
appcenter = "5.0.4"
libxposed = "100"
glide = "4.16.0"
@@ -35,12 +35,12 @@ rikkax-recyclerview = { module = "dev.rikka.rikkax.recyclerview:recyclerview-ktx
rikkax-widget-borderview = { module = "dev.rikka.rikkax.widget:borderview", version = "1.1.0" }
rikkax-widget-mainswitchbar = { module = "dev.rikka.rikkax.widget:mainswitchbar", version = "1.0.2" }
-androidx-activity = { module = "androidx.activity:activity", version = "1.9.1" }
+androidx-activity = { module = "androidx.activity:activity", version = "1.9.2" }
androidx-annotation = { module = "androidx.annotation:annotation", version = "1.8.2" }
androidx-browser = { module = "androidx.browser:browser", version = "1.8.0" }
androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version = "2.1.4" }
androidx-core = { module = "androidx.core:core", version = "1.13.1" }
-androidx-fragment = { module = "androidx.fragment:fragment", version = "1.8.2" }
+androidx-fragment = { module = "androidx.fragment:fragment", version = "1.8.3" }
androidx-navigation-fragment = { group = "androidx.navigation", name = "navigation-fragment", version.ref = "nav" }
androidx-navigation-ui = { group = "androidx.navigation", name = "navigation-ui", version.ref = "nav" }
androidx-preference = { module = "androidx.preference:preference", version = "1.2.1" }
@@ -56,12 +56,11 @@ okhttp-logging-interceptor = { group = "com.squareup.okhttp3", name = "logging-i
agp-apksig = { group = "com.android.tools.build", name = "apksig", version.ref = "agp" }
appiconloader = { module = "me.zhanghai.android.appiconloader:appiconloader", version = "1.5.0" }
-commons-lang3 = { module = "org.apache.commons:commons-lang3", version = "3.17.0" }
material = { module = "com.google.android.material:material", version = "1.12.0" }
gson = { module = "com.google.code.gson:gson", version = "2.11.0" }
hiddenapibypass = { module = "org.lsposed.hiddenapibypass:hiddenapibypass", version = "4.3" }
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
-kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version = "1.8.1" }
+kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version = "1.9.0" }
libxposed-api = { group = "io.github.libxposed", name = "api", version.ref = "libxposed" }
libxposed-interface = { group = "io.github.libxposed", name = "interface", version.ref = "libxposed" }
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 9355b415575..0aaefbcaf0f 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/services/manager-service/src/main/aidl/org/lsposed/lspd/ILSPManagerService.aidl b/services/manager-service/src/main/aidl/org/lsposed/lspd/ILSPManagerService.aidl
index ca1c0488fc0..ea82be2f6e0 100644
--- a/services/manager-service/src/main/aidl/org/lsposed/lspd/ILSPManagerService.aidl
+++ b/services/manager-service/src/main/aidl/org/lsposed/lspd/ILSPManagerService.aidl
@@ -87,4 +87,8 @@ interface ILSPManagerService {
boolean enableStatusNotification() = 47;
void setEnableStatusNotification(boolean enable) = 48;
+
+ void setLogWatchdog(boolean enable) = 49;
+
+ boolean isLogWatchdogEnabled() = 50;
}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index f4cdaa479b9..2d4e03d10f8 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -26,6 +26,7 @@ dependencyResolutionManagement {
rootProject.name = "LSPosed"
include(
+ ":apache",
":app",
":axml",
":core",