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.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 {
|
||||
|
|
@ -100,14 +110,24 @@ public class LSPApplication {
|
|||
SharedPreferences shared = context.getSharedPreferences("npatch", Context.MODE_PRIVATE);
|
||||
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)");
|
||||
service = new NeoLocalApplicationService(context);
|
||||
|
||||
} 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, "Manager is disabled, using remote service (NLAS)");
|
||||
Log.i(TAG, "Using NeoLocal Service (Cached Config)");
|
||||
service = new NeoLocalApplicationService(context);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Startup.initXposed(false, ActivityThread.currentProcessName(), context.getApplicationInfo().dataDir, service);
|
||||
Startup.bootstrapXposed();
|
||||
|
|
|
|||
|
|
@ -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.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()
|
||||
try {
|
||||
Intent intent = new Intent()
|
||||
.setComponent(new ComponentName(Constants.MANAGER_PACKAGE_NAME, MODULE_SERVICE))
|
||||
.putExtra("packageName", mContext.getPackageName());
|
||||
var latch = new CountDownLatch(1);
|
||||
mConnection = new ServiceConnection() {
|
||||
.putExtra("packageName", context.getPackageName());
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
ServiceConnection conn = 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;
|
||||
}
|
||||
service = Stub.asInterface(binder);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
Log.e(TAG, "Manager service died");
|
||||
mService = null;
|
||||
service = null;
|
||||
}
|
||||
};
|
||||
|
||||
Log.i(TAG, "Request manager binder");
|
||||
mHandlerThread = null; // Initialize member
|
||||
try {
|
||||
Log.i(TAG, "Requesting manager binder...");
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
mContext.bindService(intent, Context.BIND_AUTO_CREATE, Executors.newSingleThreadExecutor(), mConnection);
|
||||
context.bindService(intent, Context.BIND_AUTO_CREATE, Executors.newSingleThreadExecutor(), conn);
|
||||
} 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);
|
||||
HandlerThread handlerThread = new HandlerThread("RemoteApplicationService");
|
||||
handlerThread.start();
|
||||
Handler handler = new Handler(handlerThread.getLooper());
|
||||
|
||||
if (!success) {
|
||||
// Attempt to unbind the service before throwing a timeout for cleanup
|
||||
try {
|
||||
mContext.unbindService(mConnection);
|
||||
} catch (IllegalArgumentException | IllegalStateException ignored) {
|
||||
// Ignored
|
||||
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.");
|
||||
@Override
|
||||
public List<Module> getLegacyModulesList() throws RemoteException {
|
||||
return service == null ? new ArrayList<>() : service.getLegacyModulesList();
|
||||
}
|
||||
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
|
||||
public boolean isLogMuted() throws RemoteException {
|
||||
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