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:
parent
695fe45c56
commit
59cc621d98
|
|
@ -51,8 +51,6 @@
|
|||
<activity
|
||||
android:name=".ui.activity.CrashReportActivity"
|
||||
android:process=":error" />
|
||||
|
||||
<receiver android:name=".receivers.ServiceReceiver" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(...);
|
||||
|
|
|
|||
|
|
@ -14,4 +14,6 @@ interface ILSPApplicationService {
|
|||
String getPrefsPath(String packageName);
|
||||
|
||||
Bundle requestRemotePreference(String packageName, int userId, IBinder callback);
|
||||
|
||||
ParcelFileDescriptor requestInjectedManagerBinder(out List<IBinder> binder);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 +
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,4 +44,7 @@ public final class ActivityThread {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
public static final class ActivityClientRecord {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
package android.app;
|
||||
|
||||
public class Notification {
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
package android.app;
|
||||
|
||||
public class NotificationChannel {
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package android.content;
|
||||
|
||||
public abstract class BroadcastReceiver {
|
||||
public abstract void onReceive(Context context, Intent intent);
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
package android.content.pm;
|
||||
|
||||
public class ApplicationInfo {
|
||||
public String credentialProtectedDataDir;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
package android.os;
|
||||
|
||||
public class Handler {
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
package android.os;
|
||||
|
||||
public class ResultReceiver {
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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!");
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
package android.webkit;
|
||||
|
||||
public class WebViewDelegate {
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
package android.webkit;
|
||||
|
||||
public class WebViewFactory {
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
package android.webkit;
|
||||
|
||||
public class WebViewFactoryProvider {
|
||||
}
|
||||
|
|
@ -63,4 +63,6 @@ interface ILSPManagerService {
|
|||
void setHiddenIcon(boolean hide) = 33;
|
||||
|
||||
Map<String,ParcelFileDescriptor> getLogs() = 34;
|
||||
|
||||
void restartFor(in Intent intent) = 35;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue