Parasitic manager into a system app (#1103)

* add activity controller

* Hook `TRANSACTION_setActivityController`

* Hook setActivityController

* Inject into settings

* Check categories for manager

* Inject manager

* Hook webview

* Should not load modules for manager

* Remove shouldBlock

* remove pendding when server died

* Add shortcut automatically

* Load resources from manager

* Fix shortcut sleep wait

* Make R8 happy

* Fix when am is proxy

* lspd should run with euid=1000 after system server started

* Add shortcut from daemon

* Set injected package into gradle build script

* Try to inject into shell instead of settings

* Fix shortcut of shell and add icon

* Fix proguard

* hook handleReceiver

* Properly solve conflict

* Update shortcut everytime unlock

* Clear log

* Fix build

* chageng to use process${variantCapped}Resources

* Remove auto install manager

* Fix crash on some devices

* Fix new intent

* Make app toast when restart fails

* Fallback to recreate

* refine code

* Use fakecontext to set shortcut

* Fix compatibility with Android 12

* Fix `credentialProtectedDataDir`

* Fix webview

* Fix recreate

* Fix autofill

* Fix webview cache context

* Switch to inject into settings

* Send notification from daemon

* Change notification title
This commit is contained in:
LoveSy 2021-09-16 21:36:04 +08:00 committed by GitHub
parent 695fe45c56
commit 59cc621d98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 1588 additions and 424 deletions

View File

@ -51,8 +51,6 @@
<activity
android:name=".ui.activity.CrashReportActivity"
android:process=":error" />
<receiver android:name=".receivers.ServiceReceiver" />
</application>
</manifest>

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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);
}
}

View File

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

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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());
}
}

View File

@ -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<BaseExtension>("android")
val outSrcDir = file("$buildDir/generated/source/signInfo/${variantLowered}")
@ -191,7 +227,7 @@ androidComponents.onVariants { v ->
filter<ReplaceTokens>("tokens" to tokens)
filter<FixCrLfFilter>("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")
}

View File

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

View File

@ -14,4 +14,6 @@ interface ILSPApplicationService {
String getPrefsPath(String packageName);
Bundle requestRemotePreference(String packageName, int userId, IBinder callback);
ParcelFileDescriptor requestInjectedManagerBinder(out List<IBinder> binder);
}

View File

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

View File

@ -266,6 +266,12 @@ inline auto JNI_GetStaticObjectField(JNIEnv *env, const Class &clazz, jfieldID f
return JNI_SafeInvoke(env, &JNIEnv::GetStaticObjectField, clazz, fieldId);
}
template<ScopeOrClass Class>
[[maybe_unused]]
inline auto JNI_GetStaticIntField(JNIEnv *env, const Class &clazz, jfieldID fieldId) {
return JNI_SafeInvoke(env, &JNIEnv::GetStaticIntField, clazz, fieldId);
}
template<ScopeOrClass Class>
[[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>(args)...);
}
template<ScopeOrClass Class, typename ...Args>
[[maybe_unused]]
inline auto JNI_CallStaticBooleanMethod(JNIEnv *env, const Class &clazz, Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallStaticBooleanMethod, clazz, std::forward<Args>(args)...);
}
template<ScopeOrRaw<jarray> Array>
[[maybe_unused]]
inline auto JNI_GetArrayLength(JNIEnv *env, const Array &array) {

View File

@ -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<jint[]>(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,

View File

@ -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_, "<init>", "()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<void (*)(JNINativeInterface *)>(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<jobject> 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

View File

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

View File

@ -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<IBinder> binder) {
try {
return service.requestInjectedManagerBinder(binder);
} catch (RemoteException | NullPointerException ignored) {
}
return null;
}

View File

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

View File

@ -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 +

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<String, ParcelFileDescriptor> 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);
}
}

View File

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

View File

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

View File

@ -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<String> fetchProcesses(PackageInfo pkgInfo) {
HashSet<String> 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<ResolveInfo> 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<PackageInstaller> 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<PackageInstaller> 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;
}
}

View File

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

View File

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

View File

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

View File

@ -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<Intent>) 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<WebViewFactoryProvider>) 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<IBinder> 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;
}
}
}

View File

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

View File

@ -44,4 +44,7 @@ public final class ActivityThread {
return null;
}
}
public static final class ActivityClientRecord {
}
}

View File

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

View File

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

View File

@ -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<NotificationChannel> channelsList) throws RemoteException;
abstract class Stub extends Binder implements INotificationManager {
public static INotificationManager asInterface(IBinder obj) {
throw new UnsupportedOperationException();
}
}
}

View File

@ -0,0 +1,4 @@
package android.app;
public class Notification {
}

View File

@ -0,0 +1,4 @@
package android.app;
public class NotificationChannel {
}

View File

@ -0,0 +1,5 @@
package android.content;
public abstract class BroadcastReceiver {
public abstract void onReceive(Context context, Intent intent);
}

View File

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

View File

@ -1,4 +1,5 @@
package android.content.pm;
public class ApplicationInfo {
public String credentialProtectedDataDir;
}

View File

@ -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<ShortcutInfo> getShortcuts(String packageName, int matchFlags, int userId) throws RemoteException;
ParceledListSlice<ShortcutInfo> getPinnedShortcuts(String packageName, int userId) throws RemoteException;
boolean requestPinShortcut(String packageName, ShortcutInfo shortcut,
IntentSender resultIntent, int userId) throws RemoteException;
boolean updateShortcuts(String packageName, ParceledListSlice<ShortcutInfo> shortcuts, int userId) throws RemoteException;
abstract class Stub extends Binder implements IShortcutService {
public static IShortcutService asInterface(IBinder obj) {
throw new RuntimeException("STUB");
}
}
}

View File

@ -1,4 +1,10 @@
package android.content.pm;
import java.util.List;
public class ParceledListSlice<T> extends BaseParceledListSlice<T> {
public ParceledListSlice(List<T> list) {
throw new IllegalArgumentException("STUB");
}
}

View File

@ -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<ShortcutInfo> 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");
}
}

View File

@ -0,0 +1,4 @@
package android.os;
public class Handler {
}

View File

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

View File

@ -0,0 +1,10 @@
package android.os;
public interface Parcelable {
interface Creator<T>{
public T createFromParcel(Parcel source);
public T[] newArray(int size);
}
void writeToParcel(Parcel dest, int flags);
int describeContents();
}

View File

@ -0,0 +1,4 @@
package android.os;
public class ResultReceiver {
}

View File

@ -0,0 +1,25 @@
package android.os;
public class ShellCallback implements Parcelable {
public static final Parcelable.Creator<ShellCallback> 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");
}
}

View File

@ -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!");
}
}

View File

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

View File

@ -0,0 +1,4 @@
package android.webkit;
public class WebViewDelegate {
}

View File

@ -0,0 +1,4 @@
package android.webkit;
public class WebViewFactory {
}

View File

@ -0,0 +1,4 @@
package android.webkit;
public class WebViewFactoryProvider {
}

View File

@ -63,4 +63,6 @@ interface ILSPManagerService {
void setHiddenIcon(boolean hide) = 33;
Map<String,ParcelFileDescriptor> getLogs() = 34;
void restartFor(in Intent intent) = 35;
}