diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index baf5669a..4d107dc0 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -51,8 +51,6 @@
-
-
diff --git a/app/src/main/java/org/lsposed/manager/receivers/ServiceReceiver.java b/app/src/main/java/org/lsposed/manager/receivers/ServiceReceiver.java
deleted file mode 100644
index 4f2177e1..00000000
--- a/app/src/main/java/org/lsposed/manager/receivers/ServiceReceiver.java
+++ /dev/null
@@ -1,55 +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) 2020 EdXposed Contributors
- * Copyright (C) 2021 LSPosed Contributors
- */
-
-package org.lsposed.manager.receivers;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-
-import org.lsposed.manager.util.ModuleUtil;
-import org.lsposed.manager.util.NotificationUtil;
-
-public class ServiceReceiver extends BroadcastReceiver {
-
- private static String getPackageName(Intent intent) {
- Uri uri = intent.getData();
- return (uri != null) ? uri.getSchemeSpecificPart() : null;
- }
-
- @Override
- public void onReceive(final Context context, final Intent intent) {
- int userId = intent.getIntExtra(Intent.EXTRA_USER, 0);
- String packageName = getPackageName(intent);
- if (packageName == null) {
- return;
- }
-
- ModuleUtil.InstalledModule module = ModuleUtil.getInstance().reloadSingleModule(packageName, userId);
- if (module == null) {
- return;
- }
-
- var enabled = "org.lsposed.action.MODULE_UPDATED".equals(intent.getAction());
- var systemModule = intent.getBooleanExtra("systemModule", false);
- NotificationUtil.showNotification(context, packageName, module.getAppName(), userId, enabled, systemModule);
- }
-}
diff --git a/app/src/main/java/org/lsposed/manager/ui/activity/MainActivity.java b/app/src/main/java/org/lsposed/manager/ui/activity/MainActivity.java
index 99b11b5f..b2a53eb7 100644
--- a/app/src/main/java/org/lsposed/manager/ui/activity/MainActivity.java
+++ b/app/src/main/java/org/lsposed/manager/ui/activity/MainActivity.java
@@ -39,7 +39,6 @@ import org.lsposed.manager.NavGraphDirections;
import org.lsposed.manager.R;
import org.lsposed.manager.databinding.ActivityMainBinding;
import org.lsposed.manager.ui.activity.base.BaseActivity;
-import org.lsposed.manager.util.NotificationUtil;
public class MainActivity extends BaseActivity {
private static final String KEY_PREFIX = MainActivity.class.getName() + '.';
@@ -100,13 +99,7 @@ public class MainActivity extends BaseActivity {
if (intent.getAction() != null && intent.getAction().equals("android.intent.action.APPLICATION_PREFERENCES")) {
navController.navigate(R.id.action_settings_fragment);
} else if (ConfigManager.isBinderAlive()) {
- if (NotificationUtil.NOTIFICATION_UUID.equals(intent.getStringExtra("uuid"))) {
- navController.navigate(
- NavGraphDirections.actionAppListFragment(
- intent.getStringExtra("modulePackageName"),
- intent.getIntExtra("moduleUserId", -1))
- );
- } else if (!TextUtils.isEmpty(intent.getDataString())) {
+ if (!TextUtils.isEmpty(intent.getDataString())) {
switch (intent.getDataString()) {
case "modules":
navController.navigate(R.id.action_modules_fragment);
@@ -119,6 +112,15 @@ public class MainActivity extends BaseActivity {
navController.navigate(R.id.action_repo_fragment);
}
break;
+ default:
+ var data = intent.getData();
+ if (data.getScheme().equals("module")) {
+ navController.navigate(
+ NavGraphDirections.actionAppListFragment(
+ data.getHost(),
+ data.getPort())
+ );
+ }
}
}
}
@@ -135,12 +137,16 @@ public class MainActivity extends BaseActivity {
if (BuildCompat.isAtLeastS()) {
recreate();
} else {
- Bundle savedInstanceState = new Bundle();
- onSaveInstanceState(savedInstanceState);
- finish();
- startActivity(newIntent(savedInstanceState, this));
- overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
- restarting = true;
+ try {
+ Bundle savedInstanceState = new Bundle();
+ onSaveInstanceState(savedInstanceState);
+ startActivity(newIntent(savedInstanceState, this));
+ finish();
+ overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
+ restarting = true;
+ } catch (Throwable e) {
+ recreate();
+ }
}
}
diff --git a/app/src/main/java/org/lsposed/manager/util/NotificationUtil.java b/app/src/main/java/org/lsposed/manager/util/NotificationUtil.java
deleted file mode 100644
index 81736f34..00000000
--- a/app/src/main/java/org/lsposed/manager/util/NotificationUtil.java
+++ /dev/null
@@ -1,90 +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) 2020 EdXposed Contributors
- * Copyright (C) 2021 LSPosed Contributors
- */
-
-package org.lsposed.manager.util;
-
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-
-import androidx.core.app.NotificationCompat;
-
-import org.lsposed.manager.R;
-import org.lsposed.manager.ui.activity.MainActivity;
-
-import java.util.UUID;
-
-public final class NotificationUtil {
-
- public static final String NOTIFICATION_UUID = UUID.randomUUID().toString();
- private static final int NOTIFICATION_MODULE_NOT_ACTIVATED_YET = 0;
- private static final int NOTIFICATION_MODULES_UPDATED = 1;
- private static final int PENDING_INTENT_OPEN_APP_LIST = 0;
- private static final String NOTIFICATION_MODULES_CHANNEL = "modules_channel_2";
-
- public static void showNotification(Context context,
- String modulePackageName,
- String moduleName,
- int moduleUserId,
- boolean enabled,
- boolean systemModule) {
- NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
-
- NotificationChannel channel = new NotificationChannel(NOTIFICATION_MODULES_CHANNEL,
- context.getString(R.string.Modules),
- NotificationManager.IMPORTANCE_HIGH);
- channel.setSound(null, null);
- channel.setVibrationPattern(null);
- notificationManager.createNotificationChannel(channel);
-
- 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 :
- R.string.module_is_not_activated_yet_detailed, moduleName);
-
- Intent intent = new Intent(context, MainActivity.class)
- .putExtra("uuid", NOTIFICATION_UUID)
- .putExtra("modulePackageName", modulePackageName)
- .putExtra("moduleUserId", moduleUserId)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
- PendingIntent contentIntent = PendingIntent.getActivity(context, PENDING_INTENT_OPEN_APP_LIST, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
-
- NotificationCompat.BigTextStyle style = new NotificationCompat.BigTextStyle();
- style.bigText(content);
-
- NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NOTIFICATION_MODULES_CHANNEL)
- .setContentTitle(title)
- .setContentText(content)
- .setAutoCancel(true)
- .setSmallIcon(R.drawable.ic_extension)
- .setColor(context.getColor(R.color.color_primary))
- .setContentIntent(contentIntent)
- .setStyle(style);
-
- notificationManager.notify(modulePackageName, enabled ? NOTIFICATION_MODULES_UPDATED : NOTIFICATION_MODULE_NOT_ACTIVATED_YET, builder.build());
- }
-}
diff --git a/core/build.gradle.kts b/core/build.gradle.kts
index 331a5a13..8034360c 100644
--- a/core/build.gradle.kts
+++ b/core/build.gradle.kts
@@ -26,6 +26,9 @@ import org.apache.tools.ant.filters.FixCrLfFilter
import org.apache.tools.ant.filters.ReplaceTokens
import java.io.PrintStream
import java.security.MessageDigest
+import java.util.jar.JarFile
+import java.util.zip.ZipOutputStream
+import java.io.FileOutputStream
plugins {
id("com.android.application")
@@ -58,13 +61,19 @@ dependencies {
implementation("dev.rikka.ndk:riru:26.0.0")
implementation("dev.rikka.ndk.thirdparty:cxx:1.1.0")
implementation("io.github.vvb2060.ndk:dobby:1.2")
- implementation("com.android.tools.build:apksig:7.0.1")
+ implementation("com.android.tools.build:apksig:7.0.2")
implementation("org.apache.commons:commons-lang3:3.12.0")
implementation("de.upb.cs.swt:axml:2.1.1")
compileOnly("androidx.annotation:annotation:1.2.0")
compileOnly(project(":hiddenapi-stubs"))
implementation(project(":hiddenapi-bridge"))
implementation(project(":manager-service"))
+ debugImplementation(files(File(project.buildDir, "tmp/debugR.jar")) {
+ builtBy("generateAppDebugRFile")
+ })
+ releaseImplementation(files(File(project.buildDir, "tmp/releaseR.jar")) {
+ builtBy("generateAppReleaseRFile")
+ })
}
android {
@@ -93,7 +102,13 @@ android {
}
buildConfigField("int", "API_CODE", "$apiCode")
- buildConfigField("String", "DEFAULT_MANAGER_PACKAGE_NAME", "\"$defaultManagerPackageName\"")
+ buildConfigField(
+ "String",
+ "DEFAULT_MANAGER_PACKAGE_NAME",
+ """"$defaultManagerPackageName""""
+ )
+ buildConfigField("String", "MANAGER_INJECTED_PKG_NAME", """"com.android.settings"""")
+ buildConfigField("int", "MANAGER_INJECTED_UID", """1000""")
}
lint {
@@ -129,6 +144,27 @@ androidComponents.onVariants { v ->
val zipFileName = "$moduleName-v$verName-$verCode-$variantLowered.zip"
val magiskDir = "$buildDir/magisk/$variantLowered"
+ task("generateApp${variantCapped}RFile", Jar::class) {
+ dependsOn(":app:process${variantCapped}Resources")
+ doLast {
+ val rFile = JarFile(
+ File(
+ project(":app").buildDir,
+ "intermediates/compile_and_runtime_not_namespaced_r_class_jar/${variantLowered}/R.jar"
+ )
+ )
+ ZipOutputStream(FileOutputStream(File(project.buildDir, "tmp/${variantLowered}R.jar"))).use {
+ for (entry in rFile.entries()) {
+ if (entry.name.startsWith("org/lsposed/manager")) {
+ it.putNextEntry(entry)
+ rFile.getInputStream(entry).transferTo(it)
+ it.closeEntry()
+ }
+ }
+ }
+ }
+ }
+
afterEvaluate {
val app = rootProject.project(":app").extensions.getByName("android")
val outSrcDir = file("$buildDir/generated/source/signInfo/${variantLowered}")
@@ -191,7 +227,7 @@ androidComponents.onVariants { v ->
filter("tokens" to tokens)
filter("eol" to FixCrLfFilter.CrLf.newInstance("lf"))
}
- from("${project(":app").buildDir}/outputs/apk/${variantLowered}") {
+ from("${project(":app").buildDir}/${if (rootProject.extra.properties["android.injected.invoked.from.ide"] == "true") "intermediates" else "outputs"}/apk/${variantLowered}") {
include("*.apk")
rename(".*\\.apk", "manager.apk")
}
diff --git a/core/proguard-rules.pro b/core/proguard-rules.pro
index e3012624..9375dce5 100644
--- a/core/proguard-rules.pro
+++ b/core/proguard-rules.pro
@@ -12,12 +12,16 @@
public static void onPostFixupStaticTrampolines(java.lang.Class);
}
-keepclasseswithmembers class org.lsposed.lspd.service.BridgeService {
- public static boolean execTransact(int, long, long, int);
+ public static boolean *(android.os.IBinder, int, long, long, int);
public static android.os.IBinder getApplicationServiceForSystemServer(android.os.IBinder, android.os.IBinder);
}
-keepclasseswithmembers class org.lsposed.lspd.service.LogcatService {
private int refreshFd(boolean);
}
+
+-keepclassmembers class ** implements android.content.ContextWrapper {
+ public int getUserId();
+}
-assumenosideeffects class android.util.Log {
public static *** v(...);
public static *** d(...);
diff --git a/core/src/main/aidl/org/lsposed/lspd/service/ILSPApplicationService.aidl b/core/src/main/aidl/org/lsposed/lspd/service/ILSPApplicationService.aidl
index 939d4d9b..404e6dc2 100644
--- a/core/src/main/aidl/org/lsposed/lspd/service/ILSPApplicationService.aidl
+++ b/core/src/main/aidl/org/lsposed/lspd/service/ILSPApplicationService.aidl
@@ -14,4 +14,6 @@ interface ILSPApplicationService {
String getPrefsPath(String packageName);
Bundle requestRemotePreference(String packageName, int userId, IBinder callback);
+
+ ParcelFileDescriptor requestInjectedManagerBinder(out List binder);
}
diff --git a/core/src/main/aidl/org/lsposed/lspd/service/ILSPosedService.aidl b/core/src/main/aidl/org/lsposed/lspd/service/ILSPosedService.aidl
index df10b2f2..b9c939ea 100644
--- a/core/src/main/aidl/org/lsposed/lspd/service/ILSPosedService.aidl
+++ b/core/src/main/aidl/org/lsposed/lspd/service/ILSPosedService.aidl
@@ -6,4 +6,6 @@ interface ILSPosedService {
ILSPApplicationService requestApplicationService(int uid, int pid, String processName, IBinder heartBeat);
oneway void dispatchSystemServerContext(in IBinder activityThread, in IBinder activityToken);
+
+ boolean preStartManager(String pkgName, in Intent intent);
}
diff --git a/core/src/main/cpp/main/include/jni_helper.h b/core/src/main/cpp/main/include/jni_helper.h
index 47326b4a..53909542 100644
--- a/core/src/main/cpp/main/include/jni_helper.h
+++ b/core/src/main/cpp/main/include/jni_helper.h
@@ -266,6 +266,12 @@ inline auto JNI_GetStaticObjectField(JNIEnv *env, const Class &clazz, jfieldID f
return JNI_SafeInvoke(env, &JNIEnv::GetStaticObjectField, clazz, fieldId);
}
+template
+[[maybe_unused]]
+inline auto JNI_GetStaticIntField(JNIEnv *env, const Class &clazz, jfieldID fieldId) {
+ return JNI_SafeInvoke(env, &JNIEnv::GetStaticIntField, clazz, fieldId);
+}
+
template
[[maybe_unused]]
inline auto
@@ -292,6 +298,12 @@ inline auto JNI_CallStaticIntMethod(JNIEnv *env, const Class &clazz, Args &&...a
return JNI_SafeInvoke(env, &JNIEnv::CallStaticIntMethod, clazz, std::forward(args)...);
}
+template
+[[maybe_unused]]
+inline auto JNI_CallStaticBooleanMethod(JNIEnv *env, const Class &clazz, Args &&...args) {
+ return JNI_SafeInvoke(env, &JNIEnv::CallStaticBooleanMethod, clazz, std::forward(args)...);
+}
+
template Array>
[[maybe_unused]]
inline auto JNI_GetArrayLength(JNIEnv *env, const Array &array) {
diff --git a/core/src/main/cpp/main/src/main.cpp b/core/src/main/cpp/main/src/main.cpp
index 1d890e69..34f8b765 100644
--- a/core/src/main/cpp/main/src/main.cpp
+++ b/core/src/main/cpp/main/src/main.cpp
@@ -31,6 +31,9 @@
namespace lspd {
int *allowUnload = nullptr;
+ static constexpr uid_t kAidShell = 2000;
+ static constexpr uid_t kAidInet = 3003;
+
namespace {
std::string magiskPath;
@@ -46,7 +49,7 @@ namespace lspd {
}
void nativeForkAndSpecializePre(JNIEnv *env, jclass, jint *_uid, jint *,
- jintArray *, jint *,
+ jintArray *gids, jint *,
jobjectArray *, jint *,
jstring *, jstring *nice_name,
jintArray *, jintArray *,
@@ -56,6 +59,16 @@ namespace lspd {
jobjectArray *,
jboolean *,
jboolean *) {
+ if (*_uid == kAidShell) {
+ int array_size = *gids ? env->GetArrayLength(*gids) : 0;
+ auto region = std::make_unique(array_size + 1);
+ auto *new_gids = env->NewIntArray(array_size + 1);
+ if (*gids) env->GetIntArrayRegion(*gids, 0, array_size, region.get());
+ region.get()[array_size] = kAidInet;
+ env->SetIntArrayRegion(new_gids, 0, array_size + 1, region.get());
+ if (*gids) env->SetIntArrayRegion(*gids, 0, 1, region.get() + array_size);
+ *gids = new_gids;
+ }
Context::GetInstance()->OnNativeForkAndSpecializePre(env, *_uid,
*nice_name,
*start_child_zygote,
diff --git a/core/src/main/cpp/main/src/service.cpp b/core/src/main/cpp/main/src/service.cpp
index df12c264..cfe99d73 100644
--- a/core/src/main/cpp/main/src/service.cpp
+++ b/core/src/main/cpp/main/src/service.cpp
@@ -33,19 +33,39 @@ namespace lspd {
jboolean
Service::exec_transact_replace(jboolean *res, JNIEnv *env, [[maybe_unused]] jobject obj,
va_list args) {
- jint code;
va_list copy;
va_copy(copy, args);
- code = va_arg(copy, jint);
+ auto code = va_arg(copy, jint);
+ auto data_obj = va_arg(copy, jlong);
+ auto reply_obj = va_arg(copy, jlong);
+ auto flags = va_arg(copy, jint);
va_end(copy);
if (code == BRIDGE_TRANSACTION_CODE) [[unlikely]] {
- *res = env->CallStaticBooleanMethodV(instance()->bridge_service_class_,
- instance()->exec_transact_replace_methodID_,
- args);
+ *res = JNI_CallStaticBooleanMethod(env, instance()->bridge_service_class_,
+ instance()->exec_transact_replace_methodID_,
+ obj, code, data_obj, reply_obj, flags);
return true;
+ } else if (SET_ACTIVITY_CONTROLLER_CODE != -1 &&
+ code == SET_ACTIVITY_CONTROLLER_CODE) [[unlikely]] {
+ va_copy(copy, args);
+ if (instance()->replace_activity_controller_methodID_) {
+ *res = JNI_CallStaticBooleanMethod(env, instance()->bridge_service_class_,
+ instance()->replace_activity_controller_methodID_,
+ obj, code, data_obj, reply_obj, flags);
+ }
+ va_end(copy);
+ // fallback the backup
+ } else if (code == (('_' << 24) | ('C' << 16) | ('M' << 8) | 'D')) {
+ va_copy(copy, args);
+ if (instance()->replace_shell_command_methodID_) {
+ *res = JNI_CallStaticBooleanMethod(env, instance()->bridge_service_class_,
+ instance()->replace_shell_command_methodID_,
+ obj, code, data_obj, reply_obj, flags);
+ }
+ va_end(copy);
+ return *res;
}
-
return false;
}
@@ -64,28 +84,28 @@ namespace lspd {
if (initialized_) [[unlikely]] return;
// ServiceManager
- if (auto service_manager_class = JNI_FindClass(env, "android/os/ServiceManager"))
+ if (auto service_manager_class = JNI_FindClass(env, "android/os/ServiceManager")) {
service_manager_class_ = JNI_NewGlobalRef(env, service_manager_class);
- else return;
+ } else return;
get_service_method_ = JNI_GetStaticMethodID(env, service_manager_class_, "getService",
"(Ljava/lang/String;)Landroid/os/IBinder;");
if (!get_service_method_) return;
// IBinder
- if (auto ibinder_class = JNI_FindClass(env, "android/os/IBinder"))
+ if (auto ibinder_class = JNI_FindClass(env, "android/os/IBinder")) {
transact_method_ = JNI_GetMethodID(env, ibinder_class, "transact",
"(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z");
- else return;
+ } else return;
- if (auto binder_class = JNI_FindClass(env, "android/os/Binder"))
+ if (auto binder_class = JNI_FindClass(env, "android/os/Binder")) {
binder_class_ = JNI_NewGlobalRef(env, binder_class);
- else return;
+ } else return;
binder_ctor_ = JNI_GetMethodID(env, binder_class_, "", "()V");
// Parcel
- if (auto parcel_class = JNI_FindClass(env, "android/os/Parcel"))
+ if (auto parcel_class = JNI_FindClass(env, "android/os/Parcel")) {
parcel_class_ = JNI_NewGlobalRef(env, parcel_class);
- else return;
+ } else return;
obtain_method_ = JNI_GetStaticMethodID(env, parcel_class_, "obtain",
"()Landroid/os/Parcel;");
recycleMethod_ = JNI_GetMethodID(env, parcel_class_, "recycle", "()V");
@@ -102,17 +122,19 @@ namespace lspd {
// createStringArray_ = env->GetMethodID(parcel_class_, "createStringArray",
// "()[Ljava/lang/String;");
- if (auto deadObjectExceptionClass = JNI_FindClass(env, "android/os/DeadObjectException"))
- deadObjectExceptionClass_ = JNI_NewGlobalRef(env, deadObjectExceptionClass);
+ if (auto dead_object_exception_class = JNI_FindClass(env,
+ "android/os/DeadObjectException")) {
+ deadObjectExceptionClass_ = JNI_NewGlobalRef(env, dead_object_exception_class);
+ }
initialized_ = true;
}
void Service::HookBridge(const Context &context, JNIEnv *env) {
- static bool hooked = false;
+ static bool kHooked = false;
// This should only be ran once, so unlikely
- if (hooked) [[unlikely]] return;
+ if (kHooked) [[unlikely]] return;
if (!initialized_) [[unlikely]] return;
- hooked = true;
+ kHooked = true;
if (auto bridge_service_class = context.FindClassFromCurrentLoader(env,
kBridgeServiceClassName))
bridge_service_class_ = JNI_NewGlobalRef(env, bridge_service_class);
@@ -120,16 +142,34 @@ namespace lspd {
LOGE("server class not found");
return;
}
+
+ constexpr const auto *hooker_sig = "(Landroid/os/IBinder;IJJI)Z";
+
exec_transact_replace_methodID_ = JNI_GetStaticMethodID(env, bridge_service_class_,
"execTransact",
- "(IJJI)Z");
+ hooker_sig);
if (!exec_transact_replace_methodID_) {
LOGE("execTransact class not found");
return;
}
- auto binderClass = JNI_FindClass(env, "android/os/Binder");
- exec_transact_backup_methodID_ = JNI_GetMethodID(env, binderClass, "execTransact",
+
+ replace_activity_controller_methodID_ = JNI_GetStaticMethodID(env, bridge_service_class_,
+ "replaceActivityController",
+ hooker_sig);
+ if (!replace_activity_controller_methodID_) {
+ LOGE("replaceActivityShell class not found");
+ }
+
+ replace_shell_command_methodID_ = JNI_GetStaticMethodID(env, bridge_service_class_,
+ "replaceShellCommand",
+ hooker_sig);
+ if (!replace_shell_command_methodID_) {
+ LOGE("replaceShellCommand class not found");
+ }
+
+ auto binder_class = JNI_FindClass(env, "android/os/Binder");
+ exec_transact_backup_methodID_ = JNI_GetMethodID(env, binder_class, "execTransact",
"(IJJI)Z");
if (!sym_set_table_override) {
LOGE("set table override not found");
@@ -143,6 +183,15 @@ namespace lspd {
reinterpret_cast(sym_set_table_override)(
&native_interface_replace_);
}
+ if (auto activity_thread_class = JNI_FindClass(env, "android/app/IActivityManager$Stub")) {
+ if (auto *set_activity_controller_field = JNI_GetStaticFieldID(env,
+ activity_thread_class,
+ "TRANSACTION_setActivityController",
+ "I")) {
+ SET_ACTIVITY_CONTROLLER_CODE = JNI_GetStaticIntField(env, activity_thread_class,
+ set_activity_controller_field);
+ }
+ }
LOGD("Done InitService");
}
@@ -153,10 +202,10 @@ namespace lspd {
return {env, nullptr};
}
- auto bridgeServiceName = env->NewStringUTF(BRIDGE_SERVICE_NAME.data());
- auto bridgeService = JNI_CallStaticObjectMethod(env, service_manager_class_,
- get_service_method_, bridgeServiceName);
- if (!bridgeService) {
+ auto *bridge_service_name = env->NewStringUTF(BRIDGE_SERVICE_NAME.data());
+ auto bridge_service = JNI_CallStaticObjectMethod(env, service_manager_class_,
+ get_service_method_, bridge_service_name);
+ if (!bridge_service) {
LOGD("can't get %s", BRIDGE_SERVICE_NAME.data());
return {env, nullptr};
}
@@ -166,13 +215,13 @@ namespace lspd {
auto data = JNI_CallStaticObjectMethod(env, parcel_class_, obtain_method_);
auto reply = JNI_CallStaticObjectMethod(env, parcel_class_, obtain_method_);
- auto descriptor = env->NewStringUTF(BRIDGE_SERVICE_DESCRIPTOR.data());
+ auto *descriptor = env->NewStringUTF(BRIDGE_SERVICE_DESCRIPTOR.data());
JNI_CallVoidMethod(env, data, write_interface_token_method_, descriptor);
JNI_CallVoidMethod(env, data, write_int_method_, BRIDGE_ACTION_GET_BINDER);
JNI_CallVoidMethod(env, data, write_string_method_, nice_name);
JNI_CallVoidMethod(env, data, write_strong_binder_method_, heart_beat_binder);
- auto res = JNI_CallBooleanMethod(env, bridgeService, transact_method_,
+ auto res = JNI_CallBooleanMethod(env, bridge_service, transact_method_,
BRIDGE_TRANSACTION_CODE,
data,
reply, 0);
@@ -196,28 +245,26 @@ namespace lspd {
LOGE("Service not initialized");
return {env, nullptr};
}
- auto bridgeServiceName = env->NewStringUTF(SYSTEM_SERVER_BRIDGE_SERVICE_NAME.data());
+ auto *bridge_service_name = env->NewStringUTF(SYSTEM_SERVER_BRIDGE_SERVICE_NAME.data());
ScopedLocalRef binder{env, nullptr};
for (int i = 0; i < 3; ++i) {
binder = JNI_CallStaticObjectMethod(env, service_manager_class_,
- get_service_method_, bridgeServiceName);
+ get_service_method_, bridge_service_name);
if (binder) {
LOGD("Got binder for system server");
break;
- } else {
- LOGI("Fail to get binder for system server, try again in 1s");
- using namespace std::chrono_literals;
- std::this_thread::sleep_for(1s);
}
-
+ LOGI("Fail to get binder for system server, try again in 1s");
+ using namespace std::chrono_literals;
+ std::this_thread::sleep_for(1s);
}
if (!binder) {
LOGW("Fail to get binder for system server");
return {env, nullptr};
}
- auto method = JNI_GetStaticMethodID(env, bridge_service_class_,
- "getApplicationServiceForSystemServer",
- "(Landroid/os/IBinder;Landroid/os/IBinder;)Landroid/os/IBinder;");
+ auto *method = JNI_GetStaticMethodID(env, bridge_service_class_,
+ "getApplicationServiceForSystemServer",
+ "(Landroid/os/IBinder;Landroid/os/IBinder;)Landroid/os/IBinder;");
auto heart_beat_binder = JNI_NewObject(env, binder_class_, binder_ctor_);
auto app_binder = JNI_CallStaticObjectMethod(env, bridge_service_class_, method, binder,
heart_beat_binder);
@@ -226,4 +273,4 @@ namespace lspd {
}
return app_binder;
}
-}
+} // namespace lspd
diff --git a/core/src/main/cpp/main/src/service.h b/core/src/main/cpp/main/src/service.h
index 0bd66af7..28522ff3 100644
--- a/core/src/main/cpp/main/src/service.h
+++ b/core/src/main/cpp/main/src/service.h
@@ -36,6 +36,7 @@ namespace lspd {
constexpr static auto BRIDGE_SERVICE_NAME = "activity"sv;
constexpr static auto SYSTEM_SERVER_BRIDGE_SERVICE_NAME = "serial"sv;
constexpr static jint BRIDGE_ACTION_GET_BINDER = 2;
+ inline static jint SET_ACTIVITY_CONTROLLER_CODE = -1;
public:
inline static Service* instance() {
@@ -72,6 +73,8 @@ namespace lspd {
jclass bridge_service_class_ = nullptr;
jmethodID exec_transact_replace_methodID_ = nullptr;
+ jmethodID replace_activity_controller_methodID_ = nullptr;
+ jmethodID replace_shell_command_methodID_ = nullptr;
jclass binder_class_ = nullptr;
jmethodID binder_ctor_ = nullptr;
diff --git a/core/src/main/java/org/lsposed/lspd/config/LSPApplicationServiceClient.java b/core/src/main/java/org/lsposed/lspd/config/LSPApplicationServiceClient.java
index acbad996..fdaae1e1 100644
--- a/core/src/main/java/org/lsposed/lspd/config/LSPApplicationServiceClient.java
+++ b/core/src/main/java/org/lsposed/lspd/config/LSPApplicationServiceClient.java
@@ -110,7 +110,16 @@ public class LSPApplicationServiceClient extends ApplicationServiceClient {
}
@Override
- public Bundle requestRemotePreference(String packageName, int userId, IBinder callback) throws RemoteException {
+ public Bundle requestRemotePreference(String packageName, int userId, IBinder callback) {
+ return null;
+ }
+
+ @Override
+ public ParcelFileDescriptor requestInjectedManagerBinder(List binder) {
+ try {
+ return service.requestInjectedManagerBinder(binder);
+ } catch (RemoteException | NullPointerException ignored) {
+ }
return null;
}
diff --git a/core/src/main/java/org/lsposed/lspd/core/Main.java b/core/src/main/java/org/lsposed/lspd/core/Main.java
index 3f8f4755..3a45f7ee 100644
--- a/core/src/main/java/org/lsposed/lspd/core/Main.java
+++ b/core/src/main/java/org/lsposed/lspd/core/Main.java
@@ -20,8 +20,6 @@
package org.lsposed.lspd.core;
-import static org.lsposed.lspd.config.LSPApplicationServiceClient.serviceClient;
-
import android.app.ActivityThread;
import android.app.LoadedApk;
import android.content.pm.ApplicationInfo;
@@ -30,6 +28,7 @@ import android.os.Environment;
import android.os.IBinder;
import android.os.Process;
+import org.lsposed.lspd.BuildConfig;
import org.lsposed.lspd.config.LSPApplicationServiceClient;
import org.lsposed.lspd.deopt.PrebuiltMethodsDeopter;
import org.lsposed.lspd.hooker.CrashDumpHooker;
@@ -37,6 +36,7 @@ import org.lsposed.lspd.hooker.HandleBindAppHooker;
import org.lsposed.lspd.hooker.LoadedApkCstrHooker;
import org.lsposed.lspd.hooker.SystemMainHooker;
import org.lsposed.lspd.service.ServiceManager;
+import org.lsposed.lspd.util.ParasiticManagerHooker;
import org.lsposed.lspd.util.Utils;
import org.lsposed.lspd.yahfa.hooker.YahfaHooker;
@@ -81,6 +81,10 @@ public class Main {
XposedBridge.initXResources();
XposedInit.startsSystemServer = isSystem;
PrebuiltMethodsDeopter.deoptBootMethods(); // do it once for secondary zygote
+ if (niceName.equals(BuildConfig.MANAGER_INJECTED_PKG_NAME) && ParasiticManagerHooker.start()) {
+ Utils.logI("Loaded manager, skipping next steps");
+ return;
+ }
installBootstrapHooks(isSystem, appDataDir);
Utils.logI("Loading modules for " + niceName + "/" + Process.myUid());
XposedInit.loadModules();
diff --git a/core/src/main/java/org/lsposed/lspd/hooker/HandleBindAppHooker.java b/core/src/main/java/org/lsposed/lspd/hooker/HandleBindAppHooker.java
index 7c3cbdda..50f5cae5 100644
--- a/core/src/main/java/org/lsposed/lspd/hooker/HandleBindAppHooker.java
+++ b/core/src/main/java/org/lsposed/lspd/hooker/HandleBindAppHooker.java
@@ -51,6 +51,7 @@ public class HandleBindAppHooker extends XC_MethodHook {
// save app process name here for later use
String appProcessName = (String) XposedHelpers.getObjectField(bindData, "processName");
String reportedPackageName = appInfo.packageName.equals("android") ? "system" : appInfo.packageName;
+
// Note: packageName="android" -> system_server process, ams pms etc;
// packageName="system" -> android pkg, system dialogues.
Utils.logD("processName=" + appProcessName +
diff --git a/core/src/main/java/org/lsposed/lspd/service/ActivityController.java b/core/src/main/java/org/lsposed/lspd/service/ActivityController.java
new file mode 100644
index 00000000..3ff5b1c5
--- /dev/null
+++ b/core/src/main/java/org/lsposed/lspd/service/ActivityController.java
@@ -0,0 +1,202 @@
+package org.lsposed.lspd.service;
+
+import static org.lsposed.lspd.service.ServiceManager.TAG;
+
+import android.annotation.SuppressLint;
+import android.app.ActivityThread;
+import android.app.IActivityController;
+import android.app.IActivityManager;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.ShellCommand;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import org.lsposed.lspd.BuildConfig;
+
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+public class ActivityController extends IActivityController.Stub {
+ private static Constructor> myActivityControllerConstructor = null;
+ private static Method myActivityControllerRunner = null;
+ private static boolean inited = false;
+
+ private static IActivityController controller = null;
+
+ static private ActivityController instance;
+
+ static {
+ try {
+ Context ctx = ActivityThread.currentActivityThread().getSystemContext();
+ var systemClassLoader = ctx.getClassLoader();
+ @SuppressLint("PrivateApi") var myActivityControllerClass = Class.forName("com.android.server.am.ActivityManagerShellCommand$MyActivityController", false, systemClassLoader);
+ myActivityControllerConstructor = myActivityControllerClass.getDeclaredConstructor(IActivityManager.class, PrintWriter.class, InputStream.class,
+ String.class, boolean.class);
+ myActivityControllerConstructor.setAccessible(true);
+ myActivityControllerRunner = myActivityControllerClass.getDeclaredMethod("run");
+ myActivityControllerRunner.setAccessible(true);
+ inited = true;
+ } catch (Throwable e) {
+ Log.e(TAG, "Failed to init ActivityController", e);
+ }
+ }
+
+ private ActivityController() {
+ instance = this;
+ }
+
+ static private @NonNull
+ ActivityController getInstance() {
+ if (instance == null) new ActivityController();
+ return instance;
+ }
+
+ static boolean replaceShellCommand(IBinder am, Parcel data) {
+ if (!inited) return false;
+ try {
+ var in = data.readFileDescriptor();
+ var out = data.readFileDescriptor();
+ var err = data.readFileDescriptor();
+ String[] args = data.createStringArray();
+ ShellCallback shellCallback = ShellCallback.CREATOR.createFromParcel(data);
+ ResultReceiver resultReceiver = ResultReceiver.CREATOR.createFromParcel(data);
+
+ if (args.length > 0 && "monitor".equals(args[0])) {
+ new ShellCommand() {
+ @Override
+ public int onCommand(String cmd) {
+ final PrintWriter pw = getOutPrintWriter();
+ String opt;
+ String gdbPort = null;
+ boolean monkey = false;
+ while ((opt = getNextOption()) != null) {
+ if (opt.equals("--gdb")) {
+ gdbPort = getNextArgRequired();
+ } else if (opt.equals("-m")) {
+ monkey = true;
+ } else {
+ getErrPrintWriter().println("Error: Unknown option: " + opt);
+ return -1;
+ }
+ }
+
+ return replaceMyControllerActivity(am, pw, getRawInputStream(), gdbPort, monkey);
+ }
+
+ @Override
+ public void onHelp() {
+
+ }
+ }.exec((Binder) am, in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), args, shellCallback, resultReceiver);
+ return true;
+ }
+ } catch (Throwable e) {
+ Log.e(TAG, "replace shell command", e);
+ } finally {
+ data.setDataPosition(0);
+ }
+ return false;
+ }
+
+ static boolean replaceActivityController(Parcel data) {
+ if (!inited) return false;
+ try {
+ var position = data.dataPosition();
+ var controller = replaceActivityController(IActivityController.Stub.asInterface(data.readStrongBinder()));
+ var b = data.readInt();
+ data.setDataSize(position);
+ data.setDataPosition(position);
+ data.writeStrongInterface(controller);
+ data.writeInt(b);
+ } catch (Throwable e) {
+ Log.e(TAG, "replace activity controller", e);
+ } finally {
+ data.setDataPosition(0);
+ }
+ return false;
+ }
+
+ static private int replaceMyControllerActivity(IBinder am, PrintWriter pw, InputStream stream, String gdbPort, boolean monkey) {
+ try {
+ InvocationHandler handler = (proxy, method, args1) -> {
+ if (method.getName().equals("setActivityController")) {
+ try {
+ args1[0] = replaceActivityController((IActivityController) args1[0]);
+ } catch (Throwable e) {
+ Log.e(TAG, "replace activity controller", e);
+ }
+ }
+ return method.invoke(am, args1);
+ };
+ var amProxy = Proxy.newProxyInstance(BridgeService.class.getClassLoader(),
+ new Class[]{myActivityControllerConstructor.getParameterTypes()[0]}, handler);
+ var ctrl = myActivityControllerConstructor.newInstance(amProxy, pw, stream, gdbPort, monkey);
+ myActivityControllerRunner.invoke(ctrl);
+ return 0;
+ } catch (Throwable e) {
+ Log.e(TAG, "run monitor", e);
+ return 1;
+ }
+ }
+
+ static private IActivityController replaceActivityController(IActivityController controller) {
+ Log.d(TAG, "android.app.IActivityManager.setActivityController is called");
+ ActivityController.controller = controller;
+ return getInstance();
+ }
+
+ @Override
+ public boolean activityStarting(Intent intent, String pkg) {
+ Log.d(TAG, "activity from " + pkg + " with " + intent + " with extras " + intent.getExtras() + " is starting");
+ var snapshot = BridgeService.getService();
+ if (snapshot != null && BuildConfig.MANAGER_INJECTED_PKG_NAME.equals(pkg)) {
+ try {
+ return snapshot.preStartManager(pkg, intent);
+ } catch (Throwable e) {
+ Log.e(TAG, "request manager", e);
+ }
+ }
+ return controller == null || controller.activityStarting(intent, pkg);
+ }
+
+ @Override
+ public boolean activityResuming(String pkg) {
+ return controller == null || controller.activityResuming(pkg);
+ }
+
+ @Override
+ public boolean appCrashed(String processName, int pid, String shortMsg, String longMsg, long timeMillis, String stackTrace) {
+ return controller == null || controller.appCrashed(processName, pid, shortMsg, longMsg, timeMillis, stackTrace);
+ }
+
+ @Override
+ public int appEarlyNotResponding(String processName, int pid, String annotation) {
+ return controller == null ? 0 : controller.appNotResponding(processName, pid, annotation);
+ }
+
+ @Override
+ public int appNotResponding(String processName, int pid, String processStats) {
+ return controller == null ? 0 : controller.appNotResponding(processName, pid, processStats);
+ }
+
+ @Override
+ public int systemNotResponding(String msg) {
+ return controller == null ? -1 : controller.systemNotResponding(msg);
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return this;
+ }
+}
diff --git a/core/src/main/java/org/lsposed/lspd/service/ActivityManagerService.java b/core/src/main/java/org/lsposed/lspd/service/ActivityManagerService.java
index ac90161d..6df6daa7 100644
--- a/core/src/main/java/org/lsposed/lspd/service/ActivityManagerService.java
+++ b/core/src/main/java/org/lsposed/lspd/service/ActivityManagerService.java
@@ -60,10 +60,11 @@ public class ActivityManagerService {
if (binder == null) return null;
try {
binder.linkToDeath(deathRecipient, 0);
+ am = IActivityManager.Stub.asInterface(binder);
+ am.setActivityController(null, false);
} catch (RemoteException e) {
Log.e(TAG, Log.getStackTraceString(e));
}
- am = IActivityManager.Stub.asInterface(binder);
}
return am;
}
@@ -161,4 +162,5 @@ public class ActivityManagerService {
if (am == null) return null;
return am.getCurrentUser();
}
+
}
diff --git a/core/src/main/java/org/lsposed/lspd/service/BridgeService.java b/core/src/main/java/org/lsposed/lspd/service/BridgeService.java
index 65f184ad..3cd3e606 100644
--- a/core/src/main/java/org/lsposed/lspd/service/BridgeService.java
+++ b/core/src/main/java/org/lsposed/lspd/service/BridgeService.java
@@ -34,6 +34,8 @@ import android.os.Parcel;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.system.ErrnoException;
+import android.system.Os;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -46,6 +48,7 @@ public class BridgeService {
private static final int TRANSACTION_CODE = ('_' << 24) | ('L' << 16) | ('S' << 8) | 'P';
private static final String DESCRIPTOR = "LSPosed";
private static final String SERVICE_NAME = "activity";
+ private static final String SHORTCUT_ID = "org.lsposed.manager.shortcut";
enum ACTION {
ACTION_UNKNOWN,
@@ -103,7 +106,6 @@ public class BridgeService {
};
public interface Listener {
-
void onSystemServerRestarted();
void onResponseFromBridgeService(boolean response);
@@ -114,72 +116,87 @@ public class BridgeService {
private static Listener listener;
// For service
+ // This MUST run in main thread
private static synchronized void sendToBridge(IBinder binder, boolean isRestart) {
- do {
- bridgeService = ServiceManager.getService(SERVICE_NAME);
- if (bridgeService != null && bridgeService.pingBinder()) {
- break;
- }
-
- Log.i(TAG, "service " + SERVICE_NAME + " is not started, wait 1s.");
-
- try {
- //noinspection BusyWait
- Thread.sleep(1000);
- } catch (Throwable e) {
- Log.w(TAG, "sleep" + Log.getStackTraceString(e));
- }
- } while (true);
-
- if (isRestart && listener != null) {
- listener.onSystemServerRestarted();
- }
-
+ assert Looper.myLooper() == Looper.getMainLooper();
try {
- bridgeService.linkToDeath(bridgeRecipient, 0);
- } catch (Throwable e) {
- Log.w(TAG, "linkToDeath " + Log.getStackTraceString(e));
- var snapshot = bridgeService;
- sendToBridge(binder, snapshot == null || !snapshot.isBinderAlive());
- return;
+ Os.seteuid(0);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "seteuid 0", e);
}
+ try {
+ do {
+ bridgeService = ServiceManager.getService(SERVICE_NAME);
+ if (bridgeService != null && bridgeService.pingBinder()) {
+ break;
+ }
+
+ Log.i(TAG, "service " + SERVICE_NAME + " is not started, wait 1s.");
+
+ try {
+ //noinspection BusyWait
+ Thread.sleep(1000);
+ } catch (Throwable e) {
+ Log.w(TAG, "sleep" + Log.getStackTraceString(e));
+ }
+ } while (true);
+
+ if (isRestart && listener != null) {
+ listener.onSystemServerRestarted();
+ }
- Parcel data = Parcel.obtain();
- Parcel reply = Parcel.obtain();
- boolean res = false;
- // try at most three times
- for (int i = 0; i < 3; i++) {
try {
- data.writeInterfaceToken(DESCRIPTOR);
- data.writeInt(ACTION.ACTION_SEND_BINDER.ordinal());
- Log.v(TAG, "binder " + binder.toString());
- data.writeStrongBinder(binder);
- if (bridgeService == null) break;
- res = bridgeService.transact(TRANSACTION_CODE, data, reply, 0);
- reply.readException();
+ bridgeService.linkToDeath(bridgeRecipient, 0);
} catch (Throwable e) {
- Log.e(TAG, "send binder " + Log.getStackTraceString(e));
+ Log.w(TAG, "linkToDeath " + Log.getStackTraceString(e));
var snapshot = bridgeService;
sendToBridge(binder, snapshot == null || !snapshot.isBinderAlive());
return;
- } finally {
- data.recycle();
- reply.recycle();
}
- if (res) break;
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ boolean res = false;
+ // try at most three times
+ for (int i = 0; i < 3; i++) {
+ try {
+ data.writeInterfaceToken(DESCRIPTOR);
+ data.writeInt(ACTION.ACTION_SEND_BINDER.ordinal());
+ Log.v(TAG, "binder " + binder.toString());
+ data.writeStrongBinder(binder);
+ if (bridgeService == null) break;
+ res = bridgeService.transact(TRANSACTION_CODE, data, reply, 0);
+ reply.readException();
+ } catch (Throwable e) {
+ Log.e(TAG, "send binder " + Log.getStackTraceString(e));
+ var snapshot = bridgeService;
+ sendToBridge(binder, snapshot == null || !snapshot.isBinderAlive());
+ return;
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
- Log.w(TAG, "no response from bridge, retry in 1s");
+ if (res) break;
+ Log.w(TAG, "no response from bridge, retry in 1s");
+
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException ignored) {
+ }
+ }
+
+ if (listener != null) {
+ listener.onResponseFromBridgeService(res);
+ }
+ } finally {
try {
- Thread.sleep(1000);
- } catch (InterruptedException ignored) {
+ Os.seteuid(1000);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "seteuid 1000", e);
}
}
-
- if (listener != null) {
- listener.onResponseFromBridgeService(res);
- }
}
// For client
@@ -204,8 +221,8 @@ public class BridgeService {
}
try {
IApplicationThread at = ActivityThread.currentActivityThread().getApplicationThread();
- Context ct = ActivityThread.currentActivityThread().getSystemContext();
- service.dispatchSystemServerContext(at.asBinder(), Context_getActivityToken(ct));
+ Context ctx = ActivityThread.currentActivityThread().getSystemContext();
+ service.dispatchSystemServerContext(at.asBinder(), Context_getActivityToken(ctx));
} catch (Throwable e) {
Log.e(TAG, "dispatch context: ", e);
}
@@ -264,7 +281,55 @@ public class BridgeService {
return false;
}
- public static boolean execTransact(int code, long dataObj, long replyObj, int flags) {
+ @SuppressWarnings("unused")
+ public static boolean replaceShellCommand(IBinder obj, int code, long dataObj, long replyObj, int flags) {
+ Parcel data = ParcelUtils.fromNativePointer(dataObj);
+ Parcel reply = ParcelUtils.fromNativePointer(replyObj);
+
+ if (data == null || reply == null) {
+ Log.w(TAG, "Got transaction with null data or reply");
+ return false;
+ }
+
+ try {
+ String descriptor = obj.getInterfaceDescriptor();
+ if (descriptor == null || (!descriptor.equals("android.app.IActivityManager") &&
+ !descriptor.equals("com.sonymobile.hookservice.HookActivityService"))) {
+ return false;
+ }
+ return ActivityController.replaceShellCommand(obj, data);
+ } catch (Throwable e) {
+ Log.e(TAG, "replace shell command", e);
+ return false;
+ } finally {
+ data.setDataPosition(0);
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static boolean replaceActivityController(IBinder obj, int code, long dataObj, long replyObj, int flags) {
+ Parcel data = ParcelUtils.fromNativePointer(dataObj);
+ Parcel reply = ParcelUtils.fromNativePointer(replyObj);
+
+ if (data == null || reply == null) {
+ Log.w(TAG, "Got transaction with null data or reply");
+ return false;
+ }
+
+ try {
+ String descriptor = ParcelUtils.readInterfaceDescriptor(data);
+ if (!descriptor.equals("android.app.IActivityManager") &&
+ !descriptor.equals("com.sonymobile.hookservice.HookActivityService")) {
+ return false;
+ }
+ return ActivityController.replaceActivityController(data);
+ } finally {
+ data.setDataPosition(0);
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static boolean execTransact(IBinder obj, int code, long dataObj, long replyObj, int flags) {
if (code != TRANSACTION_CODE) return false;
Parcel data = ParcelUtils.fromNativePointer(dataObj);
@@ -312,5 +377,4 @@ public class BridgeService {
}
return null;
}
-
}
diff --git a/core/src/main/java/org/lsposed/lspd/service/ConfigFileManager.java b/core/src/main/java/org/lsposed/lspd/service/ConfigFileManager.java
index 1efe4c14..49048ee9 100644
--- a/core/src/main/java/org/lsposed/lspd/service/ConfigFileManager.java
+++ b/core/src/main/java/org/lsposed/lspd/service/ConfigFileManager.java
@@ -3,9 +3,12 @@ package org.lsposed.lspd.service;
import static org.lsposed.lspd.service.ServiceManager.TAG;
import static org.lsposed.lspd.service.ServiceManager.toGlobalNamespace;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
import android.os.ParcelFileDescriptor;
import android.os.SELinux;
import android.os.SharedMemory;
+import android.os.SystemProperties;
import android.system.ErrnoException;
import android.system.OsConstants;
import android.util.Log;
@@ -14,11 +17,14 @@ import androidx.annotation.Nullable;
import org.lsposed.lspd.models.PreLoadedApk;
import org.lsposed.lspd.util.Utils;
+import org.w3c.dom.ls.LSResourceResolver;
import java.io.BufferedReader;
import java.io.File;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
+import java.lang.reflect.Method;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
@@ -37,10 +43,11 @@ import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.zip.ZipFile;
-class ConfigFileManager {
+public class ConfigFileManager {
static final Path basePath = Paths.get("/data/adb/lspd");
static final File managerApkPath = basePath.resolve("manager.apk").toFile();
private static final Path lockPath = basePath.resolve("lock");
@@ -53,6 +60,8 @@ class ConfigFileManager {
@SuppressWarnings("FieldCanBeLocal")
private static FileLocker locker = null;
+ private static final Resources res;
+
static {
try {
Files.createDirectories(basePath);
@@ -62,6 +71,71 @@ class ConfigFileManager {
} catch (IOException e) {
Log.e(TAG, Log.getStackTraceString(e));
}
+ Resources tmpRes;
+ try {
+ AssetManager am = AssetManager.class.newInstance();
+ Method addAssetPath = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
+ addAssetPath.setAccessible(true);
+ addAssetPath.invoke(am, managerApkPath.getAbsolutePath());
+ tmpRes = new Resources(am, null, null);
+ } catch (Throwable e) {
+ tmpRes = null;
+ }
+ res = tmpRes;
+ }
+
+ public static Resources getResources() {
+ return res;
+ }
+
+ // from AndroidRuntime.cpp
+ private static String readLocale() {
+ String locale = SystemProperties.get("persist.sys.locale", "");
+ if (!locale.isEmpty()) {
+ return locale;
+ }
+
+ String language = SystemProperties.get("persist.sys.language", "");
+ if (!language.isEmpty()) {
+ String country = SystemProperties.get("persist.sys.country", "");
+ String variant = SystemProperties.get("persist.sys.localevar", "");
+
+ String out = language;
+ if (!country.isEmpty()) {
+ out = out + "-" + country;
+ }
+
+ if (!variant.isEmpty()) {
+ out = out + "-" + variant;
+ }
+
+ return out;
+ }
+
+ String productLocale = SystemProperties.get("ro.product.locale", "");
+ if (!productLocale.isEmpty()) {
+ return productLocale;
+ }
+
+ // If persist.sys.locale and ro.product.locale are missing,
+ // construct a locale value from the individual locale components.
+ String productLanguage = SystemProperties.get("ro.product.locale.language", "en");
+ String productRegion = SystemProperties.get("ro.product.locale.region", "US");
+
+ return productLanguage + "-" + productRegion;
+ }
+
+ static void reloadLocale() {
+ Locale locale = Locale.forLanguageTag(readLocale());
+ Locale.setDefault(locale);
+ var conf = res.getConfiguration();
+ conf.setLocale(Locale.forLanguageTag(readLocale()));
+ res.updateConfiguration(conf, res.getDisplayMetrics());
+ }
+
+ static ParcelFileDescriptor getManagerApk() throws FileNotFoundException {
+ SELinux.setFileContext(managerApkPath.getAbsolutePath(), "u:object_r:system_file:s0");
+ return ParcelFileDescriptor.open(managerApkPath.getAbsoluteFile(), ParcelFileDescriptor.MODE_READ_ONLY);
}
static void deleteFolderIfExists(Path target) throws IOException {
diff --git a/core/src/main/java/org/lsposed/lspd/service/ConfigManager.java b/core/src/main/java/org/lsposed/lspd/service/ConfigManager.java
index b1907abd..006d9e1c 100644
--- a/core/src/main/java/org/lsposed/lspd/service/ConfigManager.java
+++ b/core/src/main/java/org/lsposed/lspd/service/ConfigManager.java
@@ -244,15 +244,6 @@ public class ConfigManager {
}
}
- public void ensureManager() {
- if (!PackageService.isAlive()) return;
- new Thread(() -> {
- if (PackageService.installManagerIfAbsent(manager, ConfigFileManager.managerApkPath)) {
- updateManager();
- }
- }).start();
- }
-
static ConfigManager getInstance() {
if (instance == null)
instance = new ConfigManager();
@@ -536,9 +527,7 @@ public class ConfigManager {
// This is called when a new process created, use the cached result
public boolean shouldSkipProcess(ProcessScope scope) {
- return !cachedScope.containsKey(scope) &&
- !isManager(scope.uid) &&
- !shouldBlock(scope.processName);
+ return !cachedScope.containsKey(scope) && !isManager(scope.uid);
}
public boolean isUidHooked(int uid) {
@@ -793,6 +782,15 @@ public class ConfigManager {
return verboseLog;
}
+ public ParcelFileDescriptor getManagerApk() {
+ try {
+ return ConfigFileManager.getManagerApk();
+ } catch (Throwable e) {
+ Log.e(TAG, "failed to open manager apk", e);
+ return null;
+ }
+ }
+
public ParcelFileDescriptor getModulesLog() {
try {
var modulesLog = ServiceManager.getLogcatService().getModulesLog();
@@ -828,10 +826,6 @@ public class ConfigManager {
return uid == managerUid;
}
- public boolean shouldBlock(String packageName) {
- return packageName.equals("io.github.lsposed.manager") || isManager(packageName);
- }
-
public String getPrefsPath(String fileName, int uid) {
int userId = uid / PER_USER_RANGE;
return miscPath + "/prefs" + (userId == 0 ? "" : String.valueOf(userId)) + "/" + fileName;
diff --git a/core/src/main/java/org/lsposed/lspd/service/LSPApplicationService.java b/core/src/main/java/org/lsposed/lspd/service/LSPApplicationService.java
index 8ef566fe..3b4f610e 100644
--- a/core/src/main/java/org/lsposed/lspd/service/LSPApplicationService.java
+++ b/core/src/main/java/org/lsposed/lspd/service/LSPApplicationService.java
@@ -23,14 +23,15 @@ import static org.lsposed.lspd.service.ServiceManager.TAG;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Log;
import android.util.Pair;
import org.lsposed.lspd.models.Module;
import org.lsposed.lspd.util.InstallerVerifier;
-import org.lsposed.lspd.util.Utils;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -74,11 +75,14 @@ public class LSPApplicationService extends ILSPApplicationService.Stub {
@Override
public List getModulesList(String processName) throws RemoteException {
ensureRegistered();
- int callingUid = getCallingUid();
- if (callingUid == 1000 && processName.equals("android")) {
+ int pid = getCallingPid();
+ int uid = getCallingUid();
+ if (uid == 1000 && processName.equals("android")) {
return ConfigManager.getInstance().getModulesForSystemServer();
}
- return ConfigManager.getInstance().getModulesForProcess(processName, callingUid);
+ if (ServiceManager.getManagerService().isRunningManager(pid, uid))
+ return Collections.emptyList();
+ return ConfigManager.getInstance().getModulesForProcess(processName, uid);
}
@Override
@@ -105,17 +109,33 @@ public class LSPApplicationService extends ILSPApplicationService.Stub {
@Override
public boolean requestManagerBinder(String packageName, String path, List binder) throws RemoteException {
ensureRegistered();
- if (ConfigManager.getInstance().isManager(getCallingUid()) &&
+ var pid = getCallingPid();
+ var uid = getCallingUid();
+ if (ConfigManager.getInstance().isManager(uid) &&
ConfigManager.getInstance().isManager(packageName) &&
InstallerVerifier.verifyInstallerSignature(path)) {
- var service = ServiceManager.getManagerService();
- if (Utils.isMIUI) {
- service.new ManagerGuard(handles.get(getCallingPid()));
+ var heartbeat = handles.getOrDefault(pid, null);
+ if (heartbeat != null) {
+ binder.add(ServiceManager.getManagerService().obtainManagerBinder(heartbeat, pid, uid));
}
- binder.add(service);
return false;
}
- return ConfigManager.getInstance().shouldBlock(packageName);
+ return ConfigManager.getInstance().isManager(packageName);
+ }
+
+ @Override
+ public ParcelFileDescriptor requestInjectedManagerBinder(List binder) throws RemoteException {
+ ensureRegistered();
+ var pid = getCallingPid();
+ var uid = getCallingUid();
+ if (ServiceManager.getManagerService().postStartManager(pid, uid)) {
+ var heartbeat = handles.get(pid);
+ if (heartbeat != null) {
+ binder.add(ServiceManager.getManagerService().obtainManagerBinder(heartbeat, pid, uid));
+ }
+
+ }
+ return ConfigManager.getInstance().getManagerApk();
}
public boolean hasRegister(int uid, int pid) {
diff --git a/core/src/main/java/org/lsposed/lspd/service/LSPManagerService.java b/core/src/main/java/org/lsposed/lspd/service/LSPManagerService.java
index 5f2b28c4..73a67d7f 100644
--- a/core/src/main/java/org/lsposed/lspd/service/LSPManagerService.java
+++ b/core/src/main/java/org/lsposed/lspd/service/LSPManagerService.java
@@ -22,77 +22,399 @@ package org.lsposed.lspd.service;
import static android.content.Context.BIND_AUTO_CREATE;
import static org.lsposed.lspd.service.ServiceManager.TAG;
+import android.app.INotificationManager;
import android.app.IServiceConnection;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
import android.content.pm.VersionedPackage;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.os.SELinux;
import android.os.SystemProperties;
import android.util.Log;
+import androidx.annotation.NonNull;
+
import org.lsposed.lspd.BuildConfig;
import org.lsposed.lspd.ILSPManagerService;
import org.lsposed.lspd.models.Application;
import org.lsposed.lspd.models.UserInfo;
+import org.lsposed.lspd.util.FakeContext;
+import org.lsposed.lspd.util.Utils;
+import java.io.File;
import java.io.FileDescriptor;
import java.lang.reflect.InvocationTargetException;
+import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.UUID;
import de.robv.android.xposed.XposedBridge;
+import hidden.HiddenApiBridge;
import io.github.xposed.xposedservice.utils.ParceledListSlice;
public class LSPManagerService extends ILSPManagerService.Stub {
-
- public Object guard = null;
-
private static final String PROP_NAME = "dalvik.vm.dex2oat-flags";
private static final String PROP_VALUE = "--inline-max-code-units=0";
+ // this maybe useful when obtaining the manager binder
+ private static final String RANDOM_UUID = UUID.randomUUID().toString();
+ private static final String SHORTCUT_ID = "org.lsposed.manager.shortcut";
+ public static final int NOTIFICATION_ID = 114514;
+ public static final String CHANNEL_ID = "lsposed";
+ public static final String CHANNEL_NAME = "LSPosed Manager";
+ public static final int CHANNEL_IMP = NotificationManager.IMPORTANCE_HIGH;
+
+ private static Icon managerIcon = null;
+ private static Icon notificationIcon = null;
+ private static Intent managerIntent = null;
public class ManagerGuard implements IBinder.DeathRecipient {
- private final IBinder binder;
+ private final @NonNull
+ IBinder binder;
+ private final int pid;
+ private final int uid;
private final IServiceConnection connection = new IServiceConnection.Stub() {
@Override
public void connected(ComponentName name, IBinder service, boolean dead) {
}
};
- public ManagerGuard(IBinder binder) {
+ public ManagerGuard(@NonNull IBinder binder, int pid, int uid) {
guard = this;
+ this.pid = pid;
+ this.uid = uid;
this.binder = binder;
try {
this.binder.linkToDeath(this, 0);
- var intent = new Intent();
- intent.setComponent(ComponentName.unflattenFromString("com.miui.securitycore/com.miui.xspace.service.XSpaceService"));
- ActivityManagerService.bindService(intent, intent.getType(), connection, BIND_AUTO_CREATE, "android", 0);
+ if (Utils.isMIUI) {
+ var intent = new Intent();
+ intent.setComponent(ComponentName.unflattenFromString("com.miui.securitycore/com.miui.xspace.service.XSpaceService"));
+ ActivityManagerService.bindService(intent, intent.getType(), connection, BIND_AUTO_CREATE, "android", 0);
+ }
} catch (Throwable e) {
Log.e(TAG, "manager guard", e);
+ guard = null;
}
}
@Override
public void binderDied() {
try {
- if (binder != null) binder.unlinkToDeath(this, 0);
+ binder.unlinkToDeath(this, 0);
ActivityManagerService.unbindService(connection);
} catch (Throwable e) {
Log.e(TAG, "manager guard", e);
}
guard = null;
}
+
+ boolean isAlive() {
+ return binder.isBinderAlive();
+ }
}
+ public ManagerGuard guard = null;
+
+ // guard to determine the manager or the injected app
+ // that is to say, to make the parasitic success,
+ // we should make sure no extra launch after parasitic
+ // launch is queued and before the process is started
+ private boolean pendingManager = false;
+ private int managerPid = -1;
+
LSPManagerService() {
}
+ private static Icon getIcon(int res) {
+ var icon = ConfigFileManager.getResources().getDrawable(res, ConfigFileManager.getResources().newTheme());
+ var bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(), icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+ icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
+ icon.draw(new Canvas(bitmap));
+ return Icon.createWithBitmap(bitmap);
+ }
+
+ private static Icon getManagerIcon() {
+ if (managerIcon == null) {
+ managerIcon = getIcon(org.lsposed.manager.R.drawable.ic_launcher);
+ }
+ return managerIcon;
+ }
+
+ private static Icon getNotificationIcon() {
+ if (notificationIcon == null) {
+ notificationIcon = getIcon(org.lsposed.manager.R.drawable.ic_extension);
+ }
+ return notificationIcon;
+ }
+
+ private static Intent getManagerIntent() {
+ try {
+ if (managerIntent == null) {
+ var intent = PackageService.getLaunchIntentForPackage(BuildConfig.MANAGER_INJECTED_PKG_NAME);
+ if (intent == null) {
+ var pkgInfo = PackageService.getPackageInfo(BuildConfig.MANAGER_INJECTED_PKG_NAME, PackageManager.GET_ACTIVITIES, 0);
+ if (pkgInfo.activities != null && pkgInfo.activities.length > 0) {
+ for (var activityInfo : pkgInfo.activities) {
+ if (activityInfo.processName.equals(activityInfo.packageName)) {
+ intent = new Intent();
+ intent.setComponent(new ComponentName(activityInfo.packageName, activityInfo.name));
+ intent.setAction(Intent.ACTION_MAIN);
+ break;
+ }
+ }
+ }
+ }
+ if (intent.getCategories() != null) intent.getCategories().clear();
+ intent.addCategory("org.lsposed.manager.LAUNCH_MANAGER");
+ managerIntent = (Intent) intent.clone();
+ }
+ } catch (Throwable e) {
+ Log.e(TAG, "get Intent", e);
+ }
+ return managerIntent;
+
+ }
+
+ public static PendingIntent getNotificationIntent(String modulePackageName, int moduleUserId) {
+ try {
+ var intent = (Intent) getManagerIntent().clone();
+ intent.setData(Uri.parse("module://" + modulePackageName + ":" + moduleUserId));
+ return PendingIntent.getActivity(new FakeContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ } catch (Throwable e) {
+ Log.e(TAG, "get notification intent", e);
+ return null;
+ }
+ }
+
+ public static void showNotification(String modulePackageName,
+ int moduleUserId,
+ boolean enabled,
+ boolean systemModule) {
+ try {
+ var context = new FakeContext();
+ String title = context.getString(enabled ? systemModule ?
+ org.lsposed.manager.R.string.xposed_module_updated_notification_title_system :
+ org.lsposed.manager.R.string.xposed_module_updated_notification_title :
+ org.lsposed.manager.R.string.module_is_not_activated_yet);
+ String content = context.getString(enabled ? systemModule ?
+ org.lsposed.manager.R.string.xposed_module_updated_notification_content_system :
+ org.lsposed.manager.R.string.xposed_module_updated_notification_content :
+ org.lsposed.manager.R.string.module_is_not_activated_yet_detailed, modulePackageName);
+
+ var notification = new Notification.Builder(context, CHANNEL_ID)
+ .setContentTitle(title)
+ .setContentText(content)
+ .setSmallIcon(getNotificationIcon())
+ .setColor(context.getResources().getColor(org.lsposed.manager.R.color.color_primary))
+ .setContentIntent(getNotificationIntent(modulePackageName, moduleUserId))
+ .setAutoCancel(true)
+ .build();
+ notification.extras.putString("android.substName", "LSPosed");
+ var im = INotificationManager.Stub.asInterface(android.os.ServiceManager.getService("notification"));
+ final NotificationChannel channel =
+ new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, CHANNEL_IMP);
+ im.createNotificationChannels("android",
+ new android.content.pm.ParceledListSlice<>(Collections.singletonList(channel)));
+ im.enqueueNotificationWithTag("android", "android", "114514", NOTIFICATION_ID, notification, 0);
+ } catch (Throwable e) {
+ Log.e(TAG, "posted notification", e);
+ }
+ }
+
+ public static void createOrUpdateShortcut() {
+ try {
+ var smCtor = ShortcutManager.class.getDeclaredConstructor(Context.class);
+ smCtor.setAccessible(true);
+ var context = new FakeContext();
+ var sm = smCtor.newInstance(context);
+ if (!sm.isRequestPinShortcutSupported()) {
+ Log.d(TAG, "pinned shortcut not supported, skipping");
+ return;
+ }
+ var shortcut = new ShortcutInfo.Builder(context, SHORTCUT_ID)
+ .setShortLabel("LSPosed")
+ .setLongLabel("LSPosed")
+ .setIntent(getManagerIntent())
+ .setIcon(getManagerIcon())
+ .build();
+
+ for (var shortcutInfo : sm.getPinnedShortcuts()) {
+ if (SHORTCUT_ID.equals(shortcutInfo.getId())) {
+ Log.d(TAG, "shortcut exists, updating");
+ sm.updateShortcuts(Collections.singletonList(shortcut));
+ return;
+ }
+ }
+
+ sm.requestPinShortcut(shortcut, null);
+ Log.d(TAG, "done add shortcut");
+ } catch (Throwable e) {
+ Log.e(TAG, "add shortcut", e);
+ }
+ }
+
+ public ManagerGuard guardSnapshot() {
+ var snapshot = guard;
+ return snapshot != null && snapshot.isAlive() ? snapshot : null;
+ }
+
+ private void ensureWebViewPermission(File f) {
+ if (!f.exists()) return;
+ SELinux.setFileContext(f.getAbsolutePath(), "u:object_r:privapp_data_file:s0");
+ if (f.isDirectory()) {
+ for (var g : f.listFiles()) {
+ ensureWebViewPermission(g);
+ }
+ }
+ }
+
+ private void ensureWebViewPermission() {
+ try {
+ var pkgInfo = PackageService.getPackageInfo(BuildConfig.MANAGER_INJECTED_PKG_NAME, 0, 0);
+ var cacheDir = new File(HiddenApiBridge.ApplicationInfo_credentialProtectedDataDir(pkgInfo.applicationInfo) + "/cache");
+ var webviewDir = new File(cacheDir, "WebView");
+ var httpCacheDir = new File(cacheDir, "http_cache");
+ ensureWebViewPermission(webviewDir);
+ ensureWebViewPermission(httpCacheDir);
+ } catch (Throwable e) {
+ Log.w(TAG, "cannot ensure webview dir", e);
+ }
+ }
+
+ // To start injected manager, we should take care about conflict
+ // with the target app since we won't inject into it
+ // if we are not going to display manager.
+ // Thus, when someone launching manager, we should no matter
+ // stop any process of the target app
+ // Ideally we should call force stop package here,
+ // however it's not feasible because it will cause deadlock
+ // Thus we will cancel the launch of the activity
+ // and manually start activity with force stopping
+ // However, the intent we got here is not complete since
+ // there's no extras. We cannot do the same thing
+ // where starting the target app while the manager is
+ // still running.
+ // We instead let the manager to restart the activity.
+ synchronized boolean preStartManager(String pkgName, Intent intent) {
+ // first, check if it's our target app, if not continue the start
+ if (BuildConfig.MANAGER_INJECTED_PKG_NAME.equals(pkgName)) {
+ Log.d(TAG, "starting target app of parasitic manager");
+ // check if it's launching our manager
+ if (intent.getCategories() != null &&
+ intent.getCategories().contains("org.lsposed.manager.LAUNCH_MANAGER")) {
+ Log.d(TAG, "requesting launch of manager");
+ // a new launch for the manager
+ // check if there's one running
+ // or it's run by ourselves after force stopping
+ var snapshot = guardSnapshot();
+ if (intent.getCategories().contains(RANDOM_UUID) ||
+ (snapshot != null && snapshot.isAlive() && snapshot.uid == BuildConfig.MANAGER_INJECTED_UID)) {
+ Log.d(TAG, "manager is still running or is on its way");
+ // there's one running parasitic manager
+ // or it's run by ourself after killing, resume it
+ return true;
+ } else {
+ // new parasitic manager launch, set the flag and kill
+ // old processes
+ // we do it by cancelling the launch (return false)
+ // and start activity in a new thread
+ pendingManager = true;
+ new Thread(() -> {
+ ensureWebViewPermission();
+ stopAndStartActivity(pkgName, intent, true);
+ }).start();
+ Log.d(TAG, "requested to launch manager");
+ return false;
+ }
+ } else if (pendingManager) {
+ // there's still parasitic manager, cancel a normal launch until
+ // the parasitic manager is launch
+ Log.d(TAG, "previous request is not yet done");
+ return false;
+ }
+ // this is a normal launch of the target app
+ // send it to the manager and let it to restart the package
+ // if the manager is running
+ // or normally restart without injecting
+ Log.d(TAG, "launching the target app normally");
+ return true;
+ }
+ return true;
+ }
+
+ synchronized void stopAndStartActivity(String packageName, Intent intent, boolean addUUID) {
+ try {
+ ActivityManagerService.forceStopPackage(packageName, 0);
+ Log.d(TAG, "stopped old package");
+ if (addUUID) {
+ intent = (Intent) intent.clone();
+ intent.addCategory(RANDOM_UUID);
+ }
+ ActivityManagerService.startActivityAsUserWithFeature("android", null, intent, intent.getType(), null, null, 0, 0, null, null, 0);
+ Log.d(TAG, "relaunching");
+ } catch (RemoteException e) {
+ Log.e(TAG, "stop and start activity", e);
+ }
+ }
+
+ // return true to inject manager
+ synchronized boolean shouldStartManager(int pid, int uid, String processName) {
+ if (uid != BuildConfig.MANAGER_INJECTED_UID || !BuildConfig.MANAGER_INJECTED_PKG_NAME.equals(processName) || !pendingManager)
+ return false;
+ // pending parasitic manager launch it processes
+ // now we have its pid so we allow it to be killed
+ // and thus reset the pending flag and mark its pid
+ pendingManager = false;
+ managerPid = pid;
+ Log.d(TAG, "starting injected manager: pid = " + pid + " uid = " + uid + " processName = " + processName);
+ return true;
+ }
+
+ // return true to send manager binder
+ synchronized boolean postStartManager(int pid, int uid) {
+ return pid == managerPid && uid == BuildConfig.MANAGER_INJECTED_UID;
+ }
+
+ public @NonNull
+ IBinder obtainManagerBinder(@NonNull IBinder heartbeat, int pid, int uid) {
+ new ManagerGuard(heartbeat, pid, uid);
+ if (postStartManager(pid, uid)) {
+ managerPid = 0;
+ }
+ return this;
+ }
+
+ public boolean isRunningManager(int pid, int uid) {
+ var snapshotPid = managerPid;
+ var snapshotGuard = guardSnapshot();
+ return (pid == snapshotPid && uid == BuildConfig.MANAGER_INJECTED_UID) || (snapshotGuard != null && snapshotGuard.pid == pid && snapshotGuard.uid == uid);
+ }
+
+ void onSystemServerDied() {
+ pendingManager = false;
+ managerPid = 0;
+ guard = null;
+ }
+
@Override
public IBinder asBinder() {
return this;
@@ -287,4 +609,10 @@ public class LSPManagerService extends ILSPManagerService.Stub {
public Map getLogs() {
return ConfigFileManager.getLogs();
}
+
+ @Override
+ public void restartFor(Intent intent) throws RemoteException {
+ forceStopPackage(BuildConfig.MANAGER_INJECTED_PKG_NAME, 0);
+ stopAndStartActivity(BuildConfig.MANAGER_INJECTED_PKG_NAME, intent, false);
+ }
}
diff --git a/core/src/main/java/org/lsposed/lspd/service/LSPSystemServerService.java b/core/src/main/java/org/lsposed/lspd/service/LSPSystemServerService.java
index a242df52..8e1d9946 100644
--- a/core/src/main/java/org/lsposed/lspd/service/LSPSystemServerService.java
+++ b/core/src/main/java/org/lsposed/lspd/service/LSPSystemServerService.java
@@ -71,7 +71,7 @@ public class LSPSystemServerService extends ILSPSystemServerService.Stub impleme
}
@Override
- public ILSPApplicationService requestApplicationService(int uid, int pid, String processName, IBinder heartBeat) throws RemoteException {
+ public ILSPApplicationService requestApplicationService(int uid, int pid, String processName, IBinder heartBeat) {
requested = true;
if (ConfigManager.getInstance().shouldSkipSystemServer() || uid != 1000 || heartBeat == null || !"android".equals(processName))
return null;
diff --git a/core/src/main/java/org/lsposed/lspd/service/LSPosedService.java b/core/src/main/java/org/lsposed/lspd/service/LSPosedService.java
index 50acf6ec..2b6a1c0d 100644
--- a/core/src/main/java/org/lsposed/lspd/service/LSPosedService.java
+++ b/core/src/main/java/org/lsposed/lspd/service/LSPosedService.java
@@ -23,7 +23,6 @@ import static org.lsposed.lspd.service.PackageService.PER_USER_RANGE;
import static org.lsposed.lspd.service.ServiceManager.TAG;
import android.app.IApplicationThread;
-import android.content.ComponentName;
import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.IntentFilter;
@@ -33,7 +32,6 @@ import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
-import android.os.RemoteException;
import android.util.Log;
import java.util.Arrays;
@@ -42,20 +40,22 @@ public class LSPosedService extends ILSPosedService.Stub {
private static final int AID_NOBODY = 9999;
private static final int USER_NULL = -10000;
+
@Override
public ILSPApplicationService requestApplicationService(int uid, int pid, String processName, IBinder heartBeat) {
if (Binder.getCallingUid() != 1000) {
Log.w(TAG, "Someone else got my binder!?");
return null;
}
- if (ConfigManager.getInstance().shouldSkipProcess(new ConfigManager.ProcessScope(processName, uid))) {
- Log.d(TAG, "Skipped " + processName + "/" + uid);
- 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, heartBeat);
}
@@ -143,28 +143,7 @@ public class LSPosedService extends ILSPosedService.Stub {
boolean enabled = Arrays.asList(enabledModules).contains(moduleName);
boolean removed = intent.getAction().equals(Intent.ACTION_PACKAGE_FULLY_REMOVED) ||
intent.getAction().equals(Intent.ACTION_UID_REMOVED);
- var action = enabled || removed ? "org.lsposed.action.MODULE_UPDATED" :
- "org.lsposed.action.MODULE_NOT_ACTIVATAED";
- Intent broadcastIntent = new Intent(action);
- broadcastIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
- broadcastIntent.addFlags(0x01000000);
- broadcastIntent.addFlags(0x00400000);
- broadcastIntent.setData(intent.getData());
- broadcastIntent.putExtras(intent.getExtras());
- broadcastIntent.putExtra(Intent.EXTRA_USER, userId);
- broadcastIntent.putExtra("systemModule", systemModule);
- var manager = ConfigManager.getInstance().getManagerPackageName();
- var component = ComponentName.createRelative(manager, ".receivers.ServiceReceiver");
- broadcastIntent.setComponent(component);
-
- try {
- ActivityManagerService.broadcastIntentWithFeature(null, broadcastIntent,
- null, null, 0, null, null,
- null, -1, null, true, false,
- 0);
- } catch (Throwable t) {
- Log.e(TAG, "Broadcast to manager failed: ", t);
- }
+ LSPManagerService.showNotification(moduleName, userId, enabled || removed, systemModule);
}
if (moduleName != null && ConfigManager.getInstance().isManager(moduleName) && userId == 0) {
@@ -178,8 +157,16 @@ public class LSPosedService extends ILSPosedService.Stub {
}
- synchronized public void dispatchBootCompleted(Intent intent) {
- ConfigManager.getInstance().ensureManager();
+ synchronized public void dispatchUserUnlocked(Intent intent) {
+ try {
+ while (!UserService.isUserUnlocked(0)) {
+ Log.d(TAG, "user is not yet unlocked, waiting for 1s...");
+ Thread.sleep(1000);
+ }
+ LSPManagerService.createOrUpdateShortcut();
+ } catch (Throwable e) {
+ Log.e(TAG, "dispatch user unlocked", e);
+ }
}
private void registerPackageReceiver() {
@@ -213,15 +200,15 @@ public class LSPosedService extends ILSPosedService.Stub {
Log.d(TAG, "registered package receiver");
}
- private void registerBootReceiver() {
+ private void registerUnlockReceiver() {
try {
IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(Intent.ACTION_LOCKED_BOOT_COMPLETED);
+ intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
ActivityManagerService.registerReceiver("android", null, new IIntentReceiver.Stub() {
@Override
public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
- new Thread(() -> dispatchBootCompleted(intent)).start();
+ new Thread(() -> dispatchUserUnlocked(intent)).start();
try {
ActivityManagerService.finishReceiver(this, resultCode, data, extras, false, intent.getFlags());
} catch (Throwable e) {
@@ -230,16 +217,22 @@ public class LSPosedService extends ILSPosedService.Stub {
}
}, intentFilter, null, 0, 0);
} catch (Throwable e) {
- Log.e(TAG, "register boot receiver", e);
+ Log.e(TAG, "register unlock receiver", e);
}
- Log.d(TAG, "registered boot receiver");
+ Log.d(TAG, "registered unlock receiver");
}
@Override
- public void dispatchSystemServerContext(IBinder activityThread, IBinder activityToken) throws RemoteException {
+ public void dispatchSystemServerContext(IBinder activityThread, IBinder activityToken) {
Log.d(TAG, "received system context");
ActivityManagerService.onSystemServerContext(IApplicationThread.Stub.asInterface(activityThread), activityToken);
- registerBootReceiver();
registerPackageReceiver();
+ registerUnlockReceiver();
+ }
+
+ @Override
+ public boolean preStartManager(String pkgName, Intent intent) {
+ Log.d(TAG, "checking manager intent");
+ return ServiceManager.getManagerService().preStartManager(pkgName, intent);
}
}
diff --git a/core/src/main/java/org/lsposed/lspd/service/PackageService.java b/core/src/main/java/org/lsposed/lspd/service/PackageService.java
index a3417c7b..de25c542 100644
--- a/core/src/main/java/org/lsposed/lspd/service/PackageService.java
+++ b/core/src/main/java/org/lsposed/lspd/service/PackageService.java
@@ -29,7 +29,6 @@ import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
import android.content.pm.ComponentInfo;
-import android.content.pm.IPackageInstaller;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
@@ -47,14 +46,8 @@ import android.util.Pair;
import androidx.annotation.NonNull;
-import org.lsposed.lspd.BuildConfig;
import org.lsposed.lspd.models.Application;
-import org.lsposed.lspd.util.InstallerVerifier;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
@@ -66,7 +59,6 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
-import hidden.HiddenApiBridge;
import io.github.xposed.xposedservice.utils.ParceledListSlice;
public class PackageService {
@@ -153,12 +145,6 @@ public class PackageService {
return new ParceledListSlice<>(res);
}
- public static void grantRuntimePermission(String packageName, String permissionName, int userId) throws RemoteException {
- IPackageManager pm = getPackageManager();
- if (pm == null) return;
- pm.grantRuntimePermission(packageName, permissionName, userId);
- }
-
private static Set fetchProcesses(PackageInfo pkgInfo) {
HashSet processNames = new HashSet<>();
if (pkgInfo == null) return processNames;
@@ -281,79 +267,27 @@ public class PackageService {
return new ParceledListSlice<>(pm.queryIntentActivities(intent, resolvedType, flags, userId).getList());
}
- @SuppressWarnings("JavaReflectionMemberAccess")
- public static synchronized boolean installManagerIfAbsent(String packageName, File apkFile) {
- IPackageManager pm = getPackageManager();
- if (pm == null) return false;
+ public static Intent getLaunchIntentForPackage(String packageName) throws RemoteException {
+ Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
+ intentToResolve.addCategory(Intent.CATEGORY_INFO);
+ intentToResolve.setPackage(packageName);
+ ParceledListSlice ris = queryIntentActivities(intentToResolve, intentToResolve.getType(), 0, 0);
- try {
- // Uninstall manager when needed
- PackageInfo pkgInfo = pm.getPackageInfo(packageName, 0, 0);
- if (pkgInfo != null && pkgInfo.versionName != null && pkgInfo.applicationInfo != null) {
- boolean versionMatch = pkgInfo.versionName.equals(BuildConfig.VERSION_NAME);
- boolean signatureMatch = InstallerVerifier.verifyInstallerSignature(pkgInfo.applicationInfo.sourceDir);
- if (versionMatch && signatureMatch && pkgInfo.versionCode >= BuildConfig.VERSION_CODE)
- return false;
- if (!signatureMatch || !versionMatch && pkgInfo.versionCode > BuildConfig.VERSION_CODE)
- uninstallPackage(new VersionedPackage(pkgInfo.packageName, pkgInfo.versionCode), -1);
- }
-
- if (!InstallerVerifier.verifyInstallerSignature(apkFile.getPath())) {
- Log.w(TAG, apkFile + " verify signature false! skip install.");
- return false;
- }
-
- // Install manager
- IPackageInstaller installerService = pm.getPackageInstaller();
- PackageInstaller installer = null;
- // S Preview
- if (Build.VERSION.SDK_INT > 30 || Build.VERSION.SDK_INT == 30 && Build.VERSION.PREVIEW_SDK_INT != 0) {
- try {
- Constructor installerConstructor = PackageInstaller.class.getConstructor(IPackageInstaller.class, String.class, String.class, int.class);
- installerConstructor.setAccessible(true);
- installer = installerConstructor.newInstance(installerService, null, null, 0);
- } catch (Throwable ignored) {
- }
- }
- if (installer == null) {
- Constructor installerConstructor = PackageInstaller.class.getConstructor(IPackageInstaller.class, String.class, int.class);
- installerConstructor.setAccessible(true);
- installer = installerConstructor.newInstance(installerService, null, 0);
- }
- PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL);
- int installFlags = HiddenApiBridge.PackageInstaller_SessionParams_installFlags(params);
- installFlags |= 0x00000002/*PackageManager.INSTALL_REPLACE_EXISTING*/;
- HiddenApiBridge.PackageInstaller_SessionParams_installFlags(params, installFlags);
-
- int sessionId = installer.createSession(params);
- try (PackageInstaller.Session session = installer.openSession(sessionId)) {
- try (InputStream is = new FileInputStream(apkFile); OutputStream os = session.openWrite(apkFile.getName(), 0, -1)) {
- byte[] buf = new byte[8192];
- int len;
- while ((len = is.read(buf)) > 0) {
- os.write(buf, 0, len);
- os.flush();
- session.fsync(os);
- }
- }
- session.commit(new IntentSenderAdaptor() {
- @Override
- public void send(Intent result) {
- int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE);
- String message = result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
- if (status != PackageInstaller.STATUS_SUCCESS) {
- Log.w(TAG, "installation failed: " + status + " " + message);
- } else {
- Log.i(TAG, "installed manager successfully");
- }
- }
- }.getIntentSender());
- }
- return true;
- } catch (Throwable e) {
- Log.e(TAG, e.getMessage(), e);
- return false;
+ // 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;
}
-
}
diff --git a/core/src/main/java/org/lsposed/lspd/service/ServiceManager.java b/core/src/main/java/org/lsposed/lspd/service/ServiceManager.java
index 0d146e9c..1e4718b7 100644
--- a/core/src/main/java/org/lsposed/lspd/service/ServiceManager.java
+++ b/core/src/main/java/org/lsposed/lspd/service/ServiceManager.java
@@ -104,6 +104,8 @@ public class ServiceManager {
waitSystemService(Context.USER_SERVICE);
waitSystemService(Context.APP_OPS_SERVICE);
+ ConfigFileManager.reloadLocale();
+
BridgeService.send(mainService, new BridgeService.Listener() {
@Override
public void onSystemServerRestarted() {
@@ -123,6 +125,7 @@ public class ServiceManager {
public void onSystemServerDied() {
Log.w(TAG, "system server died");
systemServerService.putBinderForSystemServer();
+ managerService.onSystemServerDied();
}
});
diff --git a/core/src/main/java/org/lsposed/lspd/service/UserService.java b/core/src/main/java/org/lsposed/lspd/service/UserService.java
index 0a521f4e..bd0bfdbb 100644
--- a/core/src/main/java/org/lsposed/lspd/service/UserService.java
+++ b/core/src/main/java/org/lsposed/lspd/service/UserService.java
@@ -82,4 +82,10 @@ public class UserService {
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/core/src/main/java/org/lsposed/lspd/util/FakeContext.java b/core/src/main/java/org/lsposed/lspd/util/FakeContext.java
new file mode 100644
index 00000000..637a9fb1
--- /dev/null
+++ b/core/src/main/java/org/lsposed/lspd/util/FakeContext.java
@@ -0,0 +1,65 @@
+package org.lsposed.lspd.util;
+
+import static org.lsposed.lspd.service.ServiceManager.TAG;
+
+import android.content.ContentResolver;
+import android.content.ContextWrapper;
+import android.content.pm.ApplicationInfo;
+import android.content.res.Resources;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import org.lsposed.lspd.service.ConfigFileManager;
+import org.lsposed.lspd.service.PackageService;
+
+public class FakeContext extends ContextWrapper {
+ static ApplicationInfo systemApplicationInfo = null;
+ static Resources.Theme theme = null;
+
+ public FakeContext() {
+ super(null);
+ }
+
+ @Override
+ public String getPackageName() {
+ return "android";
+ }
+
+ @Override
+ public Resources getResources() {
+ return ConfigFileManager.getResources();
+ }
+
+ @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() {
+ return null;
+ }
+
+ public int getUserId() {
+ return 0;
+ }
+
+ @Override
+ public Resources.Theme getTheme() {
+ if (theme == null) theme = getResources().newTheme();
+ return theme;
+ }
+
+ @Nullable
+ @Override
+ public String getAttributionTag() {
+ return null;
+ }
+}
diff --git a/core/src/main/java/org/lsposed/lspd/util/ParasiticManagerHooker.java b/core/src/main/java/org/lsposed/lspd/util/ParasiticManagerHooker.java
new file mode 100644
index 00000000..88ed1818
--- /dev/null
+++ b/core/src/main/java/org/lsposed/lspd/util/ParasiticManagerHooker.java
@@ -0,0 +1,199 @@
+package org.lsposed.lspd.util;
+
+import static org.lsposed.lspd.config.ApplicationServiceClient.serviceClient;
+
+import android.app.ActivityThread;
+import android.app.LoadedApk;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.AndroidRuntimeException;
+import android.webkit.WebViewDelegate;
+import android.webkit.WebViewFactory;
+import android.webkit.WebViewFactoryProvider;
+
+import org.lsposed.lspd.BuildConfig;
+import org.lsposed.lspd.ILSPManagerService;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+import de.robv.android.xposed.XC_MethodHook;
+import de.robv.android.xposed.XC_MethodReplacement;
+import de.robv.android.xposed.XposedBridge;
+import de.robv.android.xposed.XposedHelpers;
+import hidden.HiddenApiBridge;
+
+public class ParasiticManagerHooker {
+ private static final String CHROMIUM_WEBVIEW_FACTORY_METHOD = "create";
+
+ private static PackageInfo managerPkgInfo = null;
+ private static int managerFd = -1;
+
+ private synchronized static PackageInfo getManagerPkgInfo(ApplicationInfo appInfo) {
+ if (managerPkgInfo == null) {
+ Context ctx = ActivityThread.currentActivityThread().getSystemContext();
+ var sourceDir = "/proc/self/fd/" + managerFd;
+ managerPkgInfo = ctx.getPackageManager().getPackageArchiveInfo(sourceDir, PackageManager.GET_ACTIVITIES);
+ var newAppInfo = managerPkgInfo.applicationInfo;
+ newAppInfo.sourceDir = sourceDir;
+ newAppInfo.publicSourceDir = sourceDir;
+ newAppInfo.nativeLibraryDir = appInfo.nativeLibraryDir;
+ newAppInfo.packageName = appInfo.packageName;
+ newAppInfo.dataDir = HiddenApiBridge.ApplicationInfo_credentialProtectedDataDir(appInfo);
+ newAppInfo.deviceProtectedDataDir = appInfo.deviceProtectedDataDir;
+ HiddenApiBridge.ApplicationInfo_credentialProtectedDataDir(newAppInfo, HiddenApiBridge.ApplicationInfo_credentialProtectedDataDir(appInfo));
+ newAppInfo.uid = appInfo.uid;
+ }
+ return managerPkgInfo;
+ }
+
+ private static void hookForManager(ILSPManagerService managerService) {
+ var activityHooker = new XC_MethodHook() {
+ @Override
+ protected void beforeHookedMethod(MethodHookParam param) {
+ for (var i = 0; i < param.args.length; ++i) {
+ if (param.args[i] instanceof ActivityInfo) {
+ var pkgInfo = getManagerPkgInfo(((ActivityInfo) param.args[i]).applicationInfo);
+ for (var activity : pkgInfo.activities) {
+ if ("org.lsposed.manager.ui.activity.MainActivity".equals(activity.name)) {
+ activity.applicationInfo = pkgInfo.applicationInfo;
+ param.args[i] = activity;
+ }
+ }
+ }
+ if (param.args[i] instanceof Intent) {
+ var intent = (Intent) param.args[i];
+ checkIntent(managerService, intent);
+ intent.setComponent(new ComponentName(BuildConfig.MANAGER_INJECTED_PKG_NAME, "org.lsposed.manager.ui.activity.MainActivity"));
+ }
+ }
+ }
+ };
+ var managerApkHooker = new XC_MethodHook() {
+ @Override
+ protected void beforeHookedMethod(MethodHookParam param) {
+ Hookers.logD("ActivityThread#handleBindApplication() starts");
+ Object bindData = param.args[0];
+ ApplicationInfo appInfo = (ApplicationInfo) XposedHelpers.getObjectField(bindData, "appInfo");
+ XposedHelpers.setObjectField(bindData, "appInfo", getManagerPkgInfo(appInfo).applicationInfo);
+ XposedHelpers.setObjectField(bindData, "providers", new ArrayList<>());
+ }
+ };
+ XposedHelpers.findAndHookMethod(ActivityThread.class,
+ "handleBindApplication",
+ "android.app.ActivityThread$AppBindData",
+ managerApkHooker);
+
+ XposedBridge.hookAllConstructors(ActivityThread.ActivityClientRecord.class, activityHooker);
+
+ var unhooks = new XC_MethodHook.Unhook[]{null};
+ unhooks[0] = XposedHelpers.findAndHookMethod(
+ LoadedApk.class, "getClassLoader", new XC_MethodHook() {
+ @Override
+ protected void afterHookedMethod(MethodHookParam param) {
+ if (XposedHelpers.getObjectField(param.thisObject, "mApplicationInfo") == getManagerPkgInfo(null).applicationInfo) {
+ InstallerVerifier.sendBinderToManager((ClassLoader) param.getResult(), managerService.asBinder());
+ unhooks[0].unhook();
+ }
+ }
+ });
+
+ XposedBridge.hookAllMethods(ActivityThread.class, "handleReceiver", new XC_MethodReplacement() {
+ @Override
+ protected Object replaceHookedMethod(MethodHookParam param) {
+ for (var arg : param.args) {
+ if (arg instanceof BroadcastReceiver.PendingResult) {
+ ((BroadcastReceiver.PendingResult) arg).finish();
+ }
+ }
+ return null;
+ }
+ });
+
+ XposedHelpers.findAndHookMethod(ActivityThread.class, "deliverNewIntents", ActivityThread.ActivityClientRecord.class, List.class, new XC_MethodHook() {
+ @Override
+ protected void beforeHookedMethod(MethodHookParam param) {
+ if (param.args[1] == null) return;
+ for (var intent : (List) param.args[1]) {
+ checkIntent(managerService, intent);
+ }
+ }
+ });
+
+ if (Process.myUid() == 1000) {
+ XposedHelpers.findAndHookMethod(WebViewFactory.class, "getProvider", new XC_MethodReplacement() {
+ @Override
+ protected Object replaceHookedMethod(MethodHookParam param) {
+ var sProviderInstance = XposedHelpers.getStaticObjectField(WebViewFactory.class, "sProviderInstance");
+ if (sProviderInstance != null) return sProviderInstance;
+ //noinspection unchecked
+ var providerClass = (Class) XposedHelpers.callStaticMethod(WebViewFactory.class, "getProviderClass");
+ Method staticFactory = null;
+ try {
+ staticFactory = providerClass.getMethod(
+ CHROMIUM_WEBVIEW_FACTORY_METHOD, WebViewDelegate.class);
+ } catch (Exception e) {
+ Hookers.logE("error instantiating provider with static factory method", e);
+ }
+
+ try {
+ var webViewDelegateConstructor = WebViewDelegate.class.getDeclaredConstructor();
+ webViewDelegateConstructor.setAccessible(true);
+ sProviderInstance = staticFactory.invoke(null, webViewDelegateConstructor.newInstance());
+ XposedHelpers.setStaticObjectField(WebViewFactory.class, "sProviderInstance", sProviderInstance);
+ Hookers.logD("Loaded provider: " + sProviderInstance);
+ return sProviderInstance;
+ } catch (Exception e) {
+ Hookers.logE("error instantiating provider", e);
+ throw new AndroidRuntimeException(e);
+ }
+ }
+ });
+ }
+ }
+
+ private static void checkIntent(ILSPManagerService managerService, Intent intent) {
+ if (managerService == null) return;
+ if (intent.getCategories() == null || !intent.getCategories().contains("org.lsposed.manager.LAUNCH_MANAGER")) {
+ Hookers.logD("Launching the original app, restarting");
+ try {
+ managerService.restartFor(intent);
+ } catch (RemoteException e) {
+ Hookers.logE("restart failed", e);
+ } finally {
+ Process.killProcess(Process.myPid());
+ }
+ }
+ }
+
+
+ static public boolean start() {
+ try {
+ List binder = new ArrayList<>(1);
+ var managerParcelFd = serviceClient.requestInjectedManagerBinder(binder);
+ if (binder.size() > 0 && binder.get(0) != null && managerParcelFd != null) {
+ managerFd = managerParcelFd.detachFd();
+ var managerService = ILSPManagerService.Stub.asInterface(binder.get(0));
+ hookForManager(managerService);
+ Utils.logD("injected manager");
+ return true;
+ } else {
+ // Not manager
+ return false;
+ }
+ } catch (Throwable e) {
+ Utils.logE("failed to inject manager", e);
+ return false;
+ }
+ }
+}
diff --git a/hiddenapi-bridge/src/main/java/hidden/HiddenApiBridge.java b/hiddenapi-bridge/src/main/java/hidden/HiddenApiBridge.java
index 2f5d73e3..58577658 100644
--- a/hiddenapi-bridge/src/main/java/hidden/HiddenApiBridge.java
+++ b/hiddenapi-bridge/src/main/java/hidden/HiddenApiBridge.java
@@ -19,14 +19,20 @@
package hidden;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInstaller;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.content.res.ResourcesImpl;
import android.os.Binder;
import android.os.Environment;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.UserHandle;
import java.io.File;
@@ -58,4 +64,26 @@ public class HiddenApiBridge {
public static File Environment_getDataProfilesDePackageDirectory(int userId, String packageName) {
return Environment.getDataProfilesDePackageDirectory(userId, packageName);
}
+
+ public static Intent Context_registerReceiverAsUser(Context ctx, BroadcastReceiver receiver, UserHandle user,
+ IntentFilter filter, String broadcastPermission, Handler scheduler) {
+
+ return ctx.registerReceiverAsUser(receiver, user, filter, broadcastPermission, scheduler);
+ }
+
+ public static UserHandle UserHandle_ALL(){
+ return UserHandle.ALL;
+ }
+
+ public static UserHandle UserHandle(int h){
+ return new UserHandle(h);
+ }
+
+ public static String ApplicationInfo_credentialProtectedDataDir(ApplicationInfo applicationInfo) {
+ return applicationInfo.credentialProtectedDataDir;
+ }
+
+ public static void ApplicationInfo_credentialProtectedDataDir(ApplicationInfo applicationInfo, String dir) {
+ applicationInfo.credentialProtectedDataDir = dir;
+ }
}
diff --git a/hiddenapi-stubs/src/main/java/android/app/ActivityThread.java b/hiddenapi-stubs/src/main/java/android/app/ActivityThread.java
index e9da3a1f..829d1e96 100644
--- a/hiddenapi-stubs/src/main/java/android/app/ActivityThread.java
+++ b/hiddenapi-stubs/src/main/java/android/app/ActivityThread.java
@@ -44,4 +44,7 @@ public final class ActivityThread {
return null;
}
}
+ public static final class ActivityClientRecord {
+
+ }
}
diff --git a/hiddenapi-stubs/src/main/java/android/app/IActivityController.java b/hiddenapi-stubs/src/main/java/android/app/IActivityController.java
new file mode 100644
index 00000000..2a359f5f
--- /dev/null
+++ b/hiddenapi-stubs/src/main/java/android/app/IActivityController.java
@@ -0,0 +1,60 @@
+package android.app;
+
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.IInterface;
+
+public interface IActivityController extends IInterface {
+ /**
+ * The system is trying to start an activity. Return true to allow
+ * it to be started as normal, or false to cancel/reject this activity.
+ */
+ boolean activityStarting(Intent intent, String pkg);
+
+ /**
+ * The system is trying to return to an activity. Return true to allow
+ * it to be resumed as normal, or false to cancel/reject this activity.
+ */
+ boolean activityResuming(String pkg);
+
+ /**
+ * An application process has crashed (in Java). Return true for the
+ * normal error recovery (app crash dialog) to occur, false to kill
+ * it immediately.
+ */
+ boolean appCrashed(String processName, int pid,
+ String shortMsg, String longMsg,
+ long timeMillis, String stackTrace);
+
+ /**
+ * Early call as soon as an ANR is detected.
+ */
+ int appEarlyNotResponding(String processName, int pid, String annotation);
+
+ /**
+ * An application process is not responding. Return 0 to show the "app
+ * not responding" dialog, 1 to continue waiting, or -1 to kill it
+ * immediately.
+ */
+ int appNotResponding(String processName, int pid, String processStats);
+
+ /**
+ * The system process watchdog has detected that the system seems to be
+ * hung. Return 1 to continue waiting, or -1 to let it continue with its
+ * normal kill.
+ */
+ int systemNotResponding(String msg);
+
+ abstract class Stub extends Binder implements IActivityController {
+
+ public static IActivityController asInterface(IBinder obj) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public IBinder asBinder() {
+ throw new UnsupportedOperationException();
+ }
+ }
+}
diff --git a/hiddenapi-stubs/src/main/java/android/app/IActivityManager.java b/hiddenapi-stubs/src/main/java/android/app/IActivityManager.java
index fb9387d1..ea757376 100644
--- a/hiddenapi-stubs/src/main/java/android/app/IActivityManager.java
+++ b/hiddenapi-stubs/src/main/java/android/app/IActivityManager.java
@@ -95,7 +95,10 @@ public interface IActivityManager extends IInterface {
UserInfo getCurrentUser() throws RemoteException;
+ void setActivityController(IActivityController watcher, boolean imAMonkey) throws RemoteException;
+
abstract class Stub extends Binder implements IActivityManager {
+ public static int TRANSACTION_setActivityController;
public static IActivityManager asInterface(IBinder obj) {
throw new UnsupportedOperationException();
diff --git a/hiddenapi-stubs/src/main/java/android/app/INotificationManager.java b/hiddenapi-stubs/src/main/java/android/app/INotificationManager.java
new file mode 100644
index 00000000..afd47239
--- /dev/null
+++ b/hiddenapi-stubs/src/main/java/android/app/INotificationManager.java
@@ -0,0 +1,19 @@
+package android.app;
+
+import android.content.pm.ParceledListSlice;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.RemoteException;
+
+public interface INotificationManager extends IInterface {
+ void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id,
+ Notification notification, int userId) throws RemoteException;
+ void createNotificationChannels(String pkg, ParceledListSlice channelsList) throws RemoteException;
+
+ abstract class Stub extends Binder implements INotificationManager {
+ public static INotificationManager asInterface(IBinder obj) {
+ throw new UnsupportedOperationException();
+ }
+ }
+}
diff --git a/hiddenapi-stubs/src/main/java/android/app/Notification.java b/hiddenapi-stubs/src/main/java/android/app/Notification.java
new file mode 100644
index 00000000..14778691
--- /dev/null
+++ b/hiddenapi-stubs/src/main/java/android/app/Notification.java
@@ -0,0 +1,4 @@
+package android.app;
+
+public class Notification {
+}
diff --git a/hiddenapi-stubs/src/main/java/android/app/NotificationChannel.java b/hiddenapi-stubs/src/main/java/android/app/NotificationChannel.java
new file mode 100644
index 00000000..dbd15704
--- /dev/null
+++ b/hiddenapi-stubs/src/main/java/android/app/NotificationChannel.java
@@ -0,0 +1,4 @@
+package android.app;
+
+public class NotificationChannel {
+}
diff --git a/hiddenapi-stubs/src/main/java/android/content/BroadcastReceiver.java b/hiddenapi-stubs/src/main/java/android/content/BroadcastReceiver.java
new file mode 100644
index 00000000..d70f080a
--- /dev/null
+++ b/hiddenapi-stubs/src/main/java/android/content/BroadcastReceiver.java
@@ -0,0 +1,5 @@
+package android.content;
+
+public abstract class BroadcastReceiver {
+ public abstract void onReceive(Context context, Intent intent);
+}
diff --git a/hiddenapi-stubs/src/main/java/android/content/Context.java b/hiddenapi-stubs/src/main/java/android/content/Context.java
index 1084a435..fc22c449 100644
--- a/hiddenapi-stubs/src/main/java/android/content/Context.java
+++ b/hiddenapi-stubs/src/main/java/android/content/Context.java
@@ -1,9 +1,15 @@
package android.content;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.UserHandle;
public class Context {
public IBinder getActivityToken() {
throw new UnsupportedOperationException("STUB");
}
+ public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
+ IntentFilter filter, String broadcastPermission, Handler scheduler) {
+ throw new UnsupportedOperationException("STUB");
+ }
}
diff --git a/hiddenapi-stubs/src/main/java/android/content/pm/ApplicationInfo.java b/hiddenapi-stubs/src/main/java/android/content/pm/ApplicationInfo.java
index e0d81c50..3f48f397 100644
--- a/hiddenapi-stubs/src/main/java/android/content/pm/ApplicationInfo.java
+++ b/hiddenapi-stubs/src/main/java/android/content/pm/ApplicationInfo.java
@@ -1,4 +1,5 @@
package android.content.pm;
public class ApplicationInfo {
+ public String credentialProtectedDataDir;
}
diff --git a/hiddenapi-stubs/src/main/java/android/content/pm/IShortcutService.java b/hiddenapi-stubs/src/main/java/android/content/pm/IShortcutService.java
new file mode 100644
index 00000000..75d351fd
--- /dev/null
+++ b/hiddenapi-stubs/src/main/java/android/content/pm/IShortcutService.java
@@ -0,0 +1,24 @@
+package android.content.pm;
+
+import android.content.IntentSender;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.RemoteException;
+
+public interface IShortcutService extends IInterface {
+ boolean isRequestPinItemSupported(int user, int requestType) throws RemoteException;
+ ParceledListSlice getShortcuts(String packageName, int matchFlags, int userId) throws RemoteException;
+ ParceledListSlice getPinnedShortcuts(String packageName, int userId) throws RemoteException;
+ boolean requestPinShortcut(String packageName, ShortcutInfo shortcut,
+ IntentSender resultIntent, int userId) throws RemoteException;
+
+ boolean updateShortcuts(String packageName, ParceledListSlice shortcuts, int userId) throws RemoteException;
+
+ abstract class Stub extends Binder implements IShortcutService {
+
+ public static IShortcutService asInterface(IBinder obj) {
+ throw new RuntimeException("STUB");
+ }
+ }
+}
diff --git a/hiddenapi-stubs/src/main/java/android/content/pm/ParceledListSlice.java b/hiddenapi-stubs/src/main/java/android/content/pm/ParceledListSlice.java
index 927754cc..6b98d7bd 100644
--- a/hiddenapi-stubs/src/main/java/android/content/pm/ParceledListSlice.java
+++ b/hiddenapi-stubs/src/main/java/android/content/pm/ParceledListSlice.java
@@ -1,4 +1,10 @@
package android.content.pm;
+import java.util.List;
+
public class ParceledListSlice extends BaseParceledListSlice {
+ public ParceledListSlice(List list) {
+ throw new IllegalArgumentException("STUB");
+ }
+
}
diff --git a/hiddenapi-stubs/src/main/java/android/content/pm/ShortcutInfo.java b/hiddenapi-stubs/src/main/java/android/content/pm/ShortcutInfo.java
new file mode 100644
index 00000000..24b8b062
--- /dev/null
+++ b/hiddenapi-stubs/src/main/java/android/content/pm/ShortcutInfo.java
@@ -0,0 +1,28 @@
+package android.content.pm;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class ShortcutInfo implements Parcelable {
+ public static final Creator CREATOR = new Creator<>() {
+ @Override
+ public ShortcutInfo createFromParcel(Parcel in) {
+ throw new IllegalArgumentException("STUB");
+ }
+
+ @Override
+ public ShortcutInfo[] newArray(int size) {
+ throw new IllegalArgumentException("STUB");
+ }
+ };
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ throw new IllegalArgumentException("STUB");
+ }
+
+ @Override
+ public int describeContents() {
+ throw new IllegalArgumentException("STUB");
+ }
+}
diff --git a/hiddenapi-stubs/src/main/java/android/os/Handler.java b/hiddenapi-stubs/src/main/java/android/os/Handler.java
new file mode 100644
index 00000000..dd632722
--- /dev/null
+++ b/hiddenapi-stubs/src/main/java/android/os/Handler.java
@@ -0,0 +1,4 @@
+package android.os;
+
+public class Handler {
+}
diff --git a/hiddenapi-stubs/src/main/java/android/os/IUserManager.java b/hiddenapi-stubs/src/main/java/android/os/IUserManager.java
index 7641c7cc..7026b0b9 100644
--- a/hiddenapi-stubs/src/main/java/android/os/IUserManager.java
+++ b/hiddenapi-stubs/src/main/java/android/os/IUserManager.java
@@ -19,6 +19,8 @@ public interface IUserManager extends IInterface {
UserInfo getProfileParent(int userId) throws RemoteException;
+ boolean isUserUnlockingOrUnlocked(int userId) throws RemoteException;
+
abstract class Stub extends Binder implements IUserManager {
public static IUserManager asInterface(IBinder obj) {
diff --git a/hiddenapi-stubs/src/main/java/android/os/Parcelable.java b/hiddenapi-stubs/src/main/java/android/os/Parcelable.java
new file mode 100644
index 00000000..5c716ee5
--- /dev/null
+++ b/hiddenapi-stubs/src/main/java/android/os/Parcelable.java
@@ -0,0 +1,10 @@
+package android.os;
+
+public interface Parcelable {
+ interface Creator{
+ public T createFromParcel(Parcel source);
+ public T[] newArray(int size);
+ }
+ void writeToParcel(Parcel dest, int flags);
+ int describeContents();
+}
diff --git a/hiddenapi-stubs/src/main/java/android/os/ResultReceiver.java b/hiddenapi-stubs/src/main/java/android/os/ResultReceiver.java
new file mode 100644
index 00000000..358ae780
--- /dev/null
+++ b/hiddenapi-stubs/src/main/java/android/os/ResultReceiver.java
@@ -0,0 +1,4 @@
+package android.os;
+
+public class ResultReceiver {
+}
diff --git a/hiddenapi-stubs/src/main/java/android/os/ShellCallback.java b/hiddenapi-stubs/src/main/java/android/os/ShellCallback.java
new file mode 100644
index 00000000..b8a5771f
--- /dev/null
+++ b/hiddenapi-stubs/src/main/java/android/os/ShellCallback.java
@@ -0,0 +1,25 @@
+package android.os;
+
+public class ShellCallback implements Parcelable {
+ public static final Parcelable.Creator CREATOR = new Creator<>() {
+ @Override
+ public ShellCallback createFromParcel(Parcel source) {
+ throw new IllegalArgumentException("STUB");
+ }
+
+ @Override
+ public ShellCallback[] newArray(int size) {
+ throw new IllegalArgumentException("STUB");
+ }
+ };
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ throw new IllegalArgumentException("STUB");
+ }
+
+ @Override
+ public int describeContents() {
+ throw new IllegalArgumentException("STUB");
+ }
+}
diff --git a/hiddenapi-stubs/src/main/java/android/os/ShellCommand.java b/hiddenapi-stubs/src/main/java/android/os/ShellCommand.java
new file mode 100644
index 00000000..56dac90c
--- /dev/null
+++ b/hiddenapi-stubs/src/main/java/android/os/ShellCommand.java
@@ -0,0 +1,33 @@
+package android.os;
+
+import java.io.FileDescriptor;
+import java.io.InputStream;
+import java.io.PrintWriter;
+
+public abstract class ShellCommand {
+ public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
+ String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
+ throw new IllegalArgumentException("STUB!");
+ }
+
+ public abstract int onCommand(String cmd);
+ public abstract void onHelp();
+
+ public String getNextOption(){
+ throw new IllegalArgumentException("STUB!");
+ }
+
+ public String getNextArgRequired() {
+ throw new IllegalArgumentException("STUB!");
+ }
+
+ public PrintWriter getErrPrintWriter() {
+ throw new IllegalArgumentException("STUB!");
+ }
+ public PrintWriter getOutPrintWriter() {
+ throw new IllegalArgumentException("STUB!");
+ }
+ public InputStream getRawInputStream() {
+ throw new IllegalArgumentException("STUB!");
+ }
+}
diff --git a/hiddenapi-stubs/src/main/java/android/os/UserHandle.java b/hiddenapi-stubs/src/main/java/android/os/UserHandle.java
index a85185d1..310fe246 100644
--- a/hiddenapi-stubs/src/main/java/android/os/UserHandle.java
+++ b/hiddenapi-stubs/src/main/java/android/os/UserHandle.java
@@ -1,5 +1,7 @@
package android.os;
+import android.annotation.NonNull;
+
public class UserHandle {
public UserHandle(int h) {
@@ -9,4 +11,7 @@ public class UserHandle {
public int getIdentifier() {
throw new RuntimeException("STUB");
}
+
+ public static final @NonNull
+ UserHandle ALL = null;
}
diff --git a/hiddenapi-stubs/src/main/java/android/webkit/WebViewDelegate.java b/hiddenapi-stubs/src/main/java/android/webkit/WebViewDelegate.java
new file mode 100644
index 00000000..3ad7cd29
--- /dev/null
+++ b/hiddenapi-stubs/src/main/java/android/webkit/WebViewDelegate.java
@@ -0,0 +1,4 @@
+package android.webkit;
+
+public class WebViewDelegate {
+}
diff --git a/hiddenapi-stubs/src/main/java/android/webkit/WebViewFactory.java b/hiddenapi-stubs/src/main/java/android/webkit/WebViewFactory.java
new file mode 100644
index 00000000..e266da66
--- /dev/null
+++ b/hiddenapi-stubs/src/main/java/android/webkit/WebViewFactory.java
@@ -0,0 +1,4 @@
+package android.webkit;
+
+public class WebViewFactory {
+}
diff --git a/hiddenapi-stubs/src/main/java/android/webkit/WebViewFactoryProvider.java b/hiddenapi-stubs/src/main/java/android/webkit/WebViewFactoryProvider.java
new file mode 100644
index 00000000..43b5fd5e
--- /dev/null
+++ b/hiddenapi-stubs/src/main/java/android/webkit/WebViewFactoryProvider.java
@@ -0,0 +1,4 @@
+package android.webkit;
+
+public class WebViewFactoryProvider {
+}
diff --git a/manager-service/src/main/aidl/org/lsposed/lspd/ILSPManagerService.aidl b/manager-service/src/main/aidl/org/lsposed/lspd/ILSPManagerService.aidl
index 4624d85c..3328452c 100644
--- a/manager-service/src/main/aidl/org/lsposed/lspd/ILSPManagerService.aidl
+++ b/manager-service/src/main/aidl/org/lsposed/lspd/ILSPManagerService.aidl
@@ -63,4 +63,6 @@ interface ILSPManagerService {
void setHiddenIcon(boolean hide) = 33;
Map getLogs() = 34;
+
+ void restartFor(in Intent intent) = 35;
}