feat: restore IntegrApplicationService and refactor RemoteService
LSPApplication 現在會根據情況選擇使用 IntegrApplicationService 或 NeoLocalApplicationService(是否 useManager 和安裝包内是否有集成模組安裝包)。 重構 RemoteApplicationService,簡化連接流程,移除死亡監聽(NeoLocal用不到了)。
This commit is contained in:
parent
9588a87814
commit
83817fba3c
|
|
@ -20,6 +20,7 @@ import org.json.JSONArray;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import org.lsposed.lspatch.loader.util.FileUtils;
|
import org.lsposed.lspatch.loader.util.FileUtils;
|
||||||
import org.lsposed.lspatch.loader.util.XLog;
|
import org.lsposed.lspatch.loader.util.XLog;
|
||||||
|
import org.lsposed.lspatch.service.IntegrApplicationService;
|
||||||
import org.lsposed.lspatch.service.NeoLocalApplicationService;
|
import org.lsposed.lspatch.service.NeoLocalApplicationService;
|
||||||
import org.lsposed.lspatch.service.RemoteApplicationService;
|
import org.lsposed.lspatch.service.RemoteApplicationService;
|
||||||
import org.lsposed.lspatch.share.PatchConfig;
|
import org.lsposed.lspatch.share.PatchConfig;
|
||||||
|
|
@ -71,6 +72,15 @@ public class LSPApplication {
|
||||||
return (android.os.Process.myUid() % PER_USER_RANGE) >= FIRST_APP_ZYGOTE_ISOLATED_UID;
|
return (android.os.Process.myUid() % PER_USER_RANGE) >= FIRST_APP_ZYGOTE_ISOLATED_UID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean hasEmbeddedModules(Context context) {
|
||||||
|
try {
|
||||||
|
String[] list = context.getAssets().list("lspatch/modules");
|
||||||
|
return list != null && list.length > 0;
|
||||||
|
} catch (IOException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void onLoad() throws RemoteException, IOException {
|
public static void onLoad() throws RemoteException, IOException {
|
||||||
if (isIsolated()) {
|
if (isIsolated()) {
|
||||||
XLog.d(TAG, "Skip isolated process");
|
XLog.d(TAG, "Skip isolated process");
|
||||||
|
|
@ -84,7 +94,7 @@ public class LSPApplication {
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d(TAG, "Initialize service client");
|
Log.d(TAG, "Initialize service client");
|
||||||
ILSPApplicationService service;
|
ILSPApplicationService service = null;
|
||||||
|
|
||||||
if (config.useManager) {
|
if (config.useManager) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -93,22 +103,32 @@ public class LSPApplication {
|
||||||
JSONArray moduleArr = new JSONArray();
|
JSONArray moduleArr = new JSONArray();
|
||||||
for (Module module : m) {
|
for (Module module : m) {
|
||||||
JSONObject moduleObj = new JSONObject();
|
JSONObject moduleObj = new JSONObject();
|
||||||
moduleObj.put("path",module.apkPath);
|
moduleObj.put("path", module.apkPath);
|
||||||
moduleObj.put("packageName",module.packageName);
|
moduleObj.put("packageName", module.packageName);
|
||||||
moduleArr.put(moduleObj);
|
moduleArr.put(moduleObj);
|
||||||
}
|
}
|
||||||
SharedPreferences shared = context.getSharedPreferences("npatch", Context.MODE_PRIVATE);
|
SharedPreferences shared = context.getSharedPreferences("npatch", Context.MODE_PRIVATE);
|
||||||
shared.edit().putString("modules",moduleArr.toString()).apply();
|
shared.edit().putString("modules", moduleArr.toString()).apply();
|
||||||
Log.i(TAG, "Success update module scope from Manager");
|
Log.i(TAG, "Success update module scope from Manager");
|
||||||
}catch (Exception e){
|
|
||||||
Log.e(TAG, "Failed to connect to manager, fallback to fixed local service (NLAS)");
|
} catch (Throwable e) {
|
||||||
|
Log.w(TAG, "Failed to connect to manager: " + e.getMessage());
|
||||||
|
service = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (service == null) {
|
||||||
|
|
||||||
|
if (hasEmbeddedModules(context)) {
|
||||||
|
Log.i(TAG, "Using Integrated Service (Embedded Modules Found)");
|
||||||
|
service = new IntegrApplicationService(context);
|
||||||
|
} else {
|
||||||
|
Log.i(TAG, "Using NeoLocal Service (Cached Config)");
|
||||||
service = new NeoLocalApplicationService(context);
|
service = new NeoLocalApplicationService(context);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Log.i(TAG, "Manager is disabled, using remote service (NLAS)");
|
|
||||||
service = new NeoLocalApplicationService(context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Startup.initXposed(false, ActivityThread.currentProcessName(), context.getApplicationInfo().dataDir, service);
|
Startup.initXposed(false, ActivityThread.currentProcessName(), context.getApplicationInfo().dataDir, service);
|
||||||
Startup.bootstrapXposed();
|
Startup.bootstrapXposed();
|
||||||
// WARN: Since it uses `XResource`, the following class should not be initialized
|
// WARN: Since it uses `XResource`, the following class should not be initialized
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
package org.lsposed.lspatch.service;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.ParcelFileDescriptor;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.lsposed.lspatch.loader.util.FileUtils;
|
||||||
|
import org.lsposed.lspatch.share.Constants;
|
||||||
|
import org.lsposed.lspatch.util.ModuleLoader;
|
||||||
|
import org.lsposed.lspd.models.Module;
|
||||||
|
import org.lsposed.lspd.service.ILSPApplicationService;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipFile;
|
||||||
|
|
||||||
|
public class IntegrApplicationService extends ILSPApplicationService.Stub {
|
||||||
|
|
||||||
|
private static final String TAG = "NPatch";
|
||||||
|
|
||||||
|
private final List<Module> modules = new ArrayList<>();
|
||||||
|
|
||||||
|
public IntegrApplicationService(Context context) {
|
||||||
|
try {
|
||||||
|
String[] assetsList = context.getAssets().list("lspatch/modules");
|
||||||
|
if (assetsList == null || assetsList.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var name : assetsList) {
|
||||||
|
if (name == null || name.length() <= 4) continue;
|
||||||
|
|
||||||
|
String packageName = name.substring(0, name.length() - 4);
|
||||||
|
String modulePath = context.getCacheDir() + "/npatch/" + packageName + "/";
|
||||||
|
String cacheApkPath;
|
||||||
|
|
||||||
|
try (ZipFile sourceFile = new ZipFile(context.getPackageResourcePath())) {
|
||||||
|
ZipEntry entry = sourceFile.getEntry(Constants.EMBEDDED_MODULES_ASSET_PATH + name);
|
||||||
|
if (entry == null) {
|
||||||
|
Log.w(TAG, "Skipping module (entry not found in APK): " + name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
cacheApkPath = modulePath + entry.getCrc() + ".apk";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Files.exists(Paths.get(cacheApkPath))) {
|
||||||
|
Log.i(TAG, "Extracting embedded module: " + packageName);
|
||||||
|
FileUtils.deleteFolderIfExists(Paths.get(modulePath));
|
||||||
|
Files.createDirectories(Paths.get(modulePath));
|
||||||
|
try (var is = context.getAssets().open("lspatch/modules/" + name)) {
|
||||||
|
Files.copy(is, Paths.get(cacheApkPath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var module = new Module();
|
||||||
|
module.apkPath = cacheApkPath;
|
||||||
|
module.packageName = packageName;
|
||||||
|
module.file = ModuleLoader.loadModule(cacheApkPath);
|
||||||
|
modules.add(module);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Error when initializing IntegrApplicationServiceClient", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Module> getLegacyModulesList() throws RemoteException {
|
||||||
|
return modules;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Module> getModulesList() throws RemoteException {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPrefsPath(String packageName) throws RemoteException {
|
||||||
|
return "/data/data/" + packageName + "/shared_prefs/";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ParcelFileDescriptor requestInjectedManagerBinder(List<IBinder> binder) throws RemoteException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder asBinder() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLogMuted() throws RemoteException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -19,9 +19,9 @@ import org.lsposed.lspatch.share.Constants;
|
||||||
import org.lsposed.lspd.models.Module;
|
import org.lsposed.lspd.models.Module;
|
||||||
import org.lsposed.lspd.service.ILSPApplicationService;
|
import org.lsposed.lspd.service.ILSPApplicationService;
|
||||||
|
|
||||||
import java.io.Closeable;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
@ -29,143 +29,105 @@ import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
public class RemoteApplicationService implements ILSPApplicationService, IBinder.DeathRecipient, Closeable {
|
public class RemoteApplicationService implements ILSPApplicationService {
|
||||||
|
|
||||||
private static final String TAG = "NPatch";
|
private static final String TAG = "NPatch";
|
||||||
private static final String MODULE_SERVICE = "org.lsposed.lspatch.manager.ModuleService";
|
private static final String MODULE_SERVICE = "org.lsposed.lspatch.manager.ModuleService";
|
||||||
|
private static final int CONNECTION_TIMEOUT_SEC = 1;
|
||||||
|
|
||||||
private volatile ILSPApplicationService mService;
|
private volatile ILSPApplicationService service;
|
||||||
private final Context mContext;
|
|
||||||
private final ServiceConnection mConnection;
|
|
||||||
private HandlerThread mHandlerThread;
|
|
||||||
|
|
||||||
@SuppressLint("DiscouragedPrivateApi")
|
@SuppressLint("DiscouragedPrivateApi")
|
||||||
public RemoteApplicationService(Context context) throws RemoteException {
|
public RemoteApplicationService(Context context) throws RemoteException {
|
||||||
this.mContext = context.getApplicationContext();
|
|
||||||
|
|
||||||
var intent = new Intent()
|
|
||||||
.setComponent(new ComponentName(Constants.MANAGER_PACKAGE_NAME, MODULE_SERVICE))
|
|
||||||
.putExtra("packageName", mContext.getPackageName());
|
|
||||||
var latch = new CountDownLatch(1);
|
|
||||||
mConnection = new ServiceConnection() {
|
|
||||||
@Override
|
|
||||||
public void onServiceConnected(ComponentName name, IBinder binder) {
|
|
||||||
Log.i(TAG, "Manager binder received");
|
|
||||||
mService = ILSPApplicationService.Stub.asInterface(binder);
|
|
||||||
try {
|
|
||||||
// 註冊 Binder 死亡通知
|
|
||||||
binder.linkToDeath(RemoteApplicationService.this, 0);
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
Log.e(TAG, "Failed to link to death", e);
|
|
||||||
mService = null;
|
|
||||||
}
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onServiceDisconnected(ComponentName name) {
|
|
||||||
Log.e(TAG, "Manager service died");
|
|
||||||
mService = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Log.i(TAG, "Request manager binder");
|
|
||||||
mHandlerThread = null; // Initialize member
|
|
||||||
try {
|
try {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
Intent intent = new Intent()
|
||||||
mContext.bindService(intent, Context.BIND_AUTO_CREATE, Executors.newSingleThreadExecutor(), mConnection);
|
.setComponent(new ComponentName(Constants.MANAGER_PACKAGE_NAME, MODULE_SERVICE))
|
||||||
} else {
|
.putExtra("packageName", context.getPackageName());
|
||||||
// 為 <29 創建一個臨時的 HandlerThread
|
|
||||||
mHandlerThread = new HandlerThread("RemoteApplicationService");
|
|
||||||
mHandlerThread.start();
|
|
||||||
var handler = new Handler(mHandlerThread.getLooper());
|
|
||||||
var contextImplClass = context.getClass();
|
|
||||||
var getUserMethod = contextImplClass.getMethod("getUser");
|
|
||||||
var bindServiceAsUserMethod = contextImplClass.getDeclaredMethod(
|
|
||||||
"bindServiceAsUser", Intent.class, ServiceConnection.class, int.class, Handler.class, UserHandle.class);
|
|
||||||
var userHandle = (UserHandle) getUserMethod.invoke(context);
|
|
||||||
bindServiceAsUserMethod.invoke(context, intent, mConnection, Context.BIND_AUTO_CREATE, handler, userHandle);
|
|
||||||
}
|
|
||||||
boolean success = latch.await(3, TimeUnit.SECONDS);
|
|
||||||
|
|
||||||
if (!success) {
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
// Attempt to unbind the service before throwing a timeout for cleanup
|
|
||||||
try {
|
ServiceConnection conn = new ServiceConnection() {
|
||||||
mContext.unbindService(mConnection);
|
@Override
|
||||||
} catch (IllegalArgumentException | IllegalStateException ignored) {
|
public void onServiceConnected(ComponentName name, IBinder binder) {
|
||||||
// Ignored
|
Log.i(TAG, "Manager binder received");
|
||||||
|
service = Stub.asInterface(binder);
|
||||||
|
latch.countDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceDisconnected(ComponentName name) {
|
||||||
|
Log.e(TAG, "Manager service died");
|
||||||
|
service = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Log.i(TAG, "Requesting manager binder...");
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
context.bindService(intent, Context.BIND_AUTO_CREATE, Executors.newSingleThreadExecutor(), conn);
|
||||||
|
} else {
|
||||||
|
HandlerThread handlerThread = new HandlerThread("RemoteApplicationService");
|
||||||
|
handlerThread.start();
|
||||||
|
Handler handler = new Handler(handlerThread.getLooper());
|
||||||
|
|
||||||
|
Class<?> contextImplClass = context.getClass();
|
||||||
|
Method getUserMethod = contextImplClass.getMethod("getUser");
|
||||||
|
UserHandle userHandle = (UserHandle) getUserMethod.invoke(context);
|
||||||
|
|
||||||
|
Method bindServiceAsUserMethod = contextImplClass.getDeclaredMethod(
|
||||||
|
"bindServiceAsUser",
|
||||||
|
Intent.class,
|
||||||
|
ServiceConnection.class,
|
||||||
|
int.class,
|
||||||
|
Handler.class,
|
||||||
|
UserHandle.class
|
||||||
|
);
|
||||||
|
|
||||||
|
bindServiceAsUserMethod.invoke(context, intent, conn, Context.BIND_AUTO_CREATE, handler, userHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean success = latch.await(CONNECTION_TIMEOUT_SEC, TimeUnit.SECONDS);
|
||||||
|
if (!success) {
|
||||||
throw new TimeoutException("Bind service timeout");
|
throw new TimeoutException("Bind service timeout");
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException |
|
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException |
|
||||||
InterruptedException | TimeoutException e) {
|
InterruptedException | TimeoutException e) {
|
||||||
var r = new RemoteException("Failed to get manager binder");
|
|
||||||
r.initCause(e);
|
RemoteException remoteException = new RemoteException("Failed to get manager binder");
|
||||||
throw r;
|
remoteException.initCause(e);
|
||||||
} finally {
|
throw remoteException;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ILSPApplicationService getServiceOrThrow() throws RemoteException {
|
@Override
|
||||||
ILSPApplicationService service = mService;
|
public List<Module> getLegacyModulesList() throws RemoteException {
|
||||||
if (service == null) {
|
return service == null ? new ArrayList<>() : service.getLegacyModulesList();
|
||||||
throw new RemoteException("Manager service is not connected or has died.");
|
}
|
||||||
}
|
|
||||||
return service;
|
@Override
|
||||||
|
public List<Module> getModulesList() throws RemoteException {
|
||||||
|
return service == null ? new ArrayList<>() : service.getModulesList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPrefsPath(String packageName) {
|
||||||
|
return new File(Environment.getDataDirectory(), "data/" + packageName + "/shared_prefs/")
|
||||||
|
.getAbsolutePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder asBinder() {
|
||||||
|
return service == null ? null : service.asBinder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ParcelFileDescriptor requestInjectedManagerBinder(List<IBinder> binder) {
|
||||||
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isLogMuted() throws RemoteException {
|
public boolean isLogMuted() throws RemoteException {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Module> getLegacyModulesList() throws RemoteException {
|
|
||||||
return getServiceOrThrow().getLegacyModulesList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Module> getModulesList() throws RemoteException {
|
|
||||||
return getServiceOrThrow().getModulesList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getPrefsPath(String packageName) throws RemoteException {
|
|
||||||
return getServiceOrThrow().getPrefsPath(packageName);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public IBinder asBinder() {
|
|
||||||
return mService == null ? null : mService.asBinder();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ParcelFileDescriptor requestInjectedManagerBinder(List<IBinder> binder) throws RemoteException {
|
|
||||||
// return getServiceOrThrow().requestInjectedManagerBinder(binder);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void binderDied() {
|
|
||||||
Log.e(TAG, "Manager service binder has died.");
|
|
||||||
if (mService != null) {
|
|
||||||
mService.asBinder().unlinkToDeath(this, 0);
|
|
||||||
}
|
|
||||||
mService = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
if (mService != null) {
|
|
||||||
mService.asBinder().unlinkToDeath(this, 0);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
// 解綁服務
|
|
||||||
mContext.unbindService(mConnection);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
Log.w(TAG, "Service was not registered or already unbound: " + e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
mService = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue