From 83817fba3c4f87d583b6112c3fe313084eb48620 Mon Sep 17 00:00:00 2001 From: NkBe Date: Wed, 19 Nov 2025 20:38:03 +0800 Subject: [PATCH] feat: restore IntegrApplicationService and refactor RemoteService MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LSPApplication 現在會根據情況選擇使用 IntegrApplicationService 或 NeoLocalApplicationService(是否 useManager 和安裝包内是否有集成模組安裝包)。 重構 RemoteApplicationService,簡化連接流程,移除死亡監聽(NeoLocal用不到了)。 --- .../lspatch/loader/LSPApplication.java | 38 +++- .../service/IntegrApplicationService.java | 104 ++++++++++ .../service/RemoteApplicationService.java | 196 +++++++----------- 3 files changed, 212 insertions(+), 126 deletions(-) create mode 100644 patch-loader/src/main/java/org/lsposed/lspatch/service/IntegrApplicationService.java diff --git a/patch-loader/src/main/java/org/lsposed/lspatch/loader/LSPApplication.java b/patch-loader/src/main/java/org/lsposed/lspatch/loader/LSPApplication.java index afa1a54..9b5fc7f 100644 --- a/patch-loader/src/main/java/org/lsposed/lspatch/loader/LSPApplication.java +++ b/patch-loader/src/main/java/org/lsposed/lspatch/loader/LSPApplication.java @@ -20,6 +20,7 @@ import org.json.JSONArray; import org.json.JSONObject; import org.lsposed.lspatch.loader.util.FileUtils; import org.lsposed.lspatch.loader.util.XLog; +import org.lsposed.lspatch.service.IntegrApplicationService; import org.lsposed.lspatch.service.NeoLocalApplicationService; import org.lsposed.lspatch.service.RemoteApplicationService; 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; } + 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 { if (isIsolated()) { XLog.d(TAG, "Skip isolated process"); @@ -84,7 +94,7 @@ public class LSPApplication { } Log.d(TAG, "Initialize service client"); - ILSPApplicationService service; + ILSPApplicationService service = null; if (config.useManager) { try { @@ -93,22 +103,32 @@ public class LSPApplication { JSONArray moduleArr = new JSONArray(); for (Module module : m) { JSONObject moduleObj = new JSONObject(); - moduleObj.put("path",module.apkPath); - moduleObj.put("packageName",module.packageName); + moduleObj.put("path", module.apkPath); + moduleObj.put("packageName", module.packageName); moduleArr.put(moduleObj); } 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"); - }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); } - } 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.bootstrapXposed(); // WARN: Since it uses `XResource`, the following class should not be initialized diff --git a/patch-loader/src/main/java/org/lsposed/lspatch/service/IntegrApplicationService.java b/patch-loader/src/main/java/org/lsposed/lspatch/service/IntegrApplicationService.java new file mode 100644 index 0000000..a469c67 --- /dev/null +++ b/patch-loader/src/main/java/org/lsposed/lspatch/service/IntegrApplicationService.java @@ -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 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 getLegacyModulesList() throws RemoteException { + return modules; + } + + @Override + public List 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 binder) throws RemoteException { + return null; + } + + @Override + public IBinder asBinder() { + return this; + } + + @Override + public boolean isLogMuted() throws RemoteException { + return false; + } + +} \ No newline at end of file diff --git a/patch-loader/src/main/java/org/lsposed/lspatch/service/RemoteApplicationService.java b/patch-loader/src/main/java/org/lsposed/lspatch/service/RemoteApplicationService.java index 5c8962c..d3ac40d 100644 --- a/patch-loader/src/main/java/org/lsposed/lspatch/service/RemoteApplicationService.java +++ b/patch-loader/src/main/java/org/lsposed/lspatch/service/RemoteApplicationService.java @@ -19,9 +19,9 @@ import org.lsposed.lspatch.share.Constants; import org.lsposed.lspd.models.Module; import org.lsposed.lspd.service.ILSPApplicationService; -import java.io.Closeable; import java.io.File; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; @@ -29,143 +29,105 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; 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 MODULE_SERVICE = "org.lsposed.lspatch.manager.ModuleService"; + private static final int CONNECTION_TIMEOUT_SEC = 1; - private volatile ILSPApplicationService mService; - private final Context mContext; - private final ServiceConnection mConnection; - private HandlerThread mHandlerThread; + private volatile ILSPApplicationService service; @SuppressLint("DiscouragedPrivateApi") 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 { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - mContext.bindService(intent, Context.BIND_AUTO_CREATE, Executors.newSingleThreadExecutor(), mConnection); - } else { - // 為 <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); + Intent intent = new Intent() + .setComponent(new ComponentName(Constants.MANAGER_PACKAGE_NAME, MODULE_SERVICE)) + .putExtra("packageName", context.getPackageName()); - if (!success) { - // Attempt to unbind the service before throwing a timeout for cleanup - try { - mContext.unbindService(mConnection); - } catch (IllegalArgumentException | IllegalStateException ignored) { - // Ignored + CountDownLatch latch = new CountDownLatch(1); + + ServiceConnection conn = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder binder) { + 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"); } + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InterruptedException | TimeoutException e) { - var r = new RemoteException("Failed to get manager binder"); - r.initCause(e); - throw r; - } finally { + + RemoteException remoteException = new RemoteException("Failed to get manager binder"); + remoteException.initCause(e); + throw remoteException; } } - private ILSPApplicationService getServiceOrThrow() throws RemoteException { - ILSPApplicationService service = mService; - if (service == null) { - throw new RemoteException("Manager service is not connected or has died."); - } - return service; + @Override + public List getLegacyModulesList() throws RemoteException { + return service == null ? new ArrayList<>() : service.getLegacyModulesList(); + } + + @Override + public List 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 binder) { + return null; + } @Override public boolean isLogMuted() throws RemoteException { return false; } - - @Override - public List getLegacyModulesList() throws RemoteException { - return getServiceOrThrow().getLegacyModulesList(); - } - - @Override - public List 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 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; - } } \ No newline at end of file