diff --git a/patch-loader/src/main/java/org/lsposed/lspatch/service/NeoLocalApplicationService.java b/patch-loader/src/main/java/org/lsposed/lspatch/service/NeoLocalApplicationService.java index 85cdb76..f4f72e3 100644 --- a/patch-loader/src/main/java/org/lsposed/lspatch/service/NeoLocalApplicationService.java +++ b/patch-loader/src/main/java/org/lsposed/lspatch/service/NeoLocalApplicationService.java @@ -3,13 +3,15 @@ package org.lsposed.lspatch.service; import android.content.Context; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.os.Environment; import android.os.IBinder; -import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import android.os.ParcelFileDescriptor; import android.util.Log; import org.json.JSONArray; +import org.json.JSONException; import org.json.JSONObject; import org.lsposed.lspatch.util.ModuleLoader; import org.lsposed.lspd.models.Module; @@ -23,6 +25,7 @@ import java.util.List; public class NeoLocalApplicationService extends ILSPApplicationService.Stub { private static final String TAG = "NPatch"; private final List cachedModule; + public NeoLocalApplicationService(Context context) { cachedModule = Collections.synchronizedList(new ArrayList<>()); loadModulesFromSharedPreferences(context); @@ -31,33 +34,51 @@ public class NeoLocalApplicationService extends ILSPApplicationService.Stub { private void loadModulesFromSharedPreferences(Context context) { var shared = context.getSharedPreferences("npatch", Context.MODE_PRIVATE); try { - String modulesJsonString = shared.getString("modules", "[]"); - Log.i(TAG, "using local application service with modules:" + modulesJsonString); + var modulesJsonString = shared.getString("modules", "[]"); + Log.i(TAG, "Loading modules from local SharedPreferences..."); if (modulesJsonString.equals("{}")) { modulesJsonString = "[]"; } var mArr = new JSONArray(modulesJsonString); + if (mArr.length() > 0) { + Log.i(TAG, "Found " + mArr.length() + " modules."); + } + for (int i = 0; i < mArr.length(); i++) { var mObj = mArr.getJSONObject(i); var m = new Module(); - m.apkPath = mObj.getString("path"); - m.packageName = mObj.getString("packageName"); - if (m.apkPath == null || !new File(m.apkPath).exists()) { + m.packageName = mObj.optString("packageName", null); + var apkPath = mObj.optString("path", null); + + if (m.packageName == null) { + Log.w(TAG, "Module at index " + i + " has no package name, skipping."); + continue; + } + + // 如果路徑為 null 或文件不存在,嘗試從 PackageManager 恢復 + if (apkPath == null || !new File(apkPath).exists()) { Log.w(TAG, "Module:" + m.packageName + " path not available, attempting reset."); try { - ApplicationInfo info = context.getPackageManager().getApplicationInfo(m.packageName, 0); + var info = context.getPackageManager().getApplicationInfo(m.packageName, 0); m.apkPath = info.sourceDir; Log.i(TAG, "Module:" + m.packageName + " path reset to " + m.apkPath); } catch (Exception e) { Log.e(TAG, "Failed to get ApplicationInfo for module: " + m.packageName, e); continue; } + } else { + m.apkPath = apkPath; + } + + if (m.apkPath != null) { + m.file = ModuleLoader.loadModule(m.apkPath); + cachedModule.add(m); + } else { + Log.w(TAG, "Could not load module " + m.packageName + ": final path is null."); } - m.file = ModuleLoader.loadModule(m.apkPath); - cachedModule.add(m); } } catch (Throwable e) { Log.e(TAG, "Error loading modules from SharedPreferences.", e); @@ -76,7 +97,7 @@ public class NeoLocalApplicationService extends ILSPApplicationService.Stub { @Override public String getPrefsPath(String packageName) throws RemoteException { - return new File(Environment.getDataDirectory(), "data/" + packageName + "/shared_prefs/").getAbsolutePath(); + return "/data/data/" + packageName + "/shared_prefs/"; } @Override 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 a2d0796..5c8962c 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,6 +19,7 @@ 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.util.ArrayList; @@ -28,55 +29,69 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -public class RemoteApplicationService implements ILSPApplicationService { +public class RemoteApplicationService implements ILSPApplicationService, IBinder.DeathRecipient, Closeable { private static final String TAG = "NPatch"; private static final String MODULE_SERVICE = "org.lsposed.lspatch.manager.ModuleService"; - private volatile ILSPApplicationService service; + private volatile ILSPApplicationService mService; + private final Context mContext; + private final ServiceConnection mConnection; + private HandlerThread mHandlerThread; @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", context.getPackageName()); - // TODO: Authentication + .putExtra("packageName", mContext.getPackageName()); var latch = new CountDownLatch(1); - var conn = new ServiceConnection() { + mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder binder) { Log.i(TAG, "Manager binder received"); - service = ILSPApplicationService.Stub.asInterface(binder); + 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"); - service = null; + mService = null; } }; + Log.i(TAG, "Request manager binder"); + mHandlerThread = null; // Initialize member try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - context.bindService(intent, Context.BIND_AUTO_CREATE, Executors.newSingleThreadExecutor(), conn); + mContext.bindService(intent, Context.BIND_AUTO_CREATE, Executors.newSingleThreadExecutor(), mConnection); } else { - var handlerThread = new HandlerThread("RemoteApplicationService"); - handlerThread.start(); - var handler = new Handler(handlerThread.getLooper()); + // 為 <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, conn, Context.BIND_AUTO_CREATE, handler, userHandle); + bindServiceAsUserMethod.invoke(context, intent, mConnection, Context.BIND_AUTO_CREATE, handler, userHandle); } boolean success = latch.await(3, TimeUnit.SECONDS); if (!success) { // Attempt to unbind the service before throwing a timeout for cleanup try { - context.unbindService(conn); + mContext.unbindService(mConnection); } catch (IllegalArgumentException | IllegalStateException ignored) { // Ignored } @@ -87,9 +102,18 @@ public class RemoteApplicationService implements ILSPApplicationService { var r = new RemoteException("Failed to get manager binder"); r.initCause(e); throw r; + } finally { } } + 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 boolean isLogMuted() throws RemoteException { return false; @@ -97,30 +121,51 @@ public class RemoteApplicationService implements ILSPApplicationService { @Override public List getLegacyModulesList() throws RemoteException { - return service == null ? new ArrayList<>() : service.getLegacyModulesList(); + return getServiceOrThrow().getLegacyModulesList(); } @Override public List getModulesList() throws RemoteException { - return service == null ? new ArrayList<>() : service.getModulesList(); + return getServiceOrThrow().getModulesList(); } @Override public String getPrefsPath(String packageName) throws RemoteException { - if (service != null) { - return service.getPrefsPath(packageName); - } - Log.e(TAG, "Manager service null, 無法取得遠端首選項路徑."); - throw new RemoteException("Manager service is unavailable for getPrefsPath."); + return getServiceOrThrow().getPrefsPath(packageName); } @Override public IBinder asBinder() { - return service == null ? null : service.asBinder(); + return mService == null ? null : mService.asBinder(); } @Override - public ParcelFileDescriptor requestInjectedManagerBinder(List binder) { + 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