diff --git a/app/build.gradle b/app/build.gradle index e4a971bf..93604ef9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -144,7 +144,7 @@ dependencies { implementation 'me.zhanghai.android.fastscroll:library:1.1.5' implementation files('libs/WeatherView-2.0.3.aar') compileOnly project(":hiddenapi-stubs") - implementation project(":service") + implementation project(':manager-service') } configurations { diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 6c559c55..b03cfa10 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -19,4 +19,7 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -renamesourcefileattribute --keep class io.github.lsposed.manager.Constants { *; } \ No newline at end of file +-keep class io.github.lsposed.manager.Constants { *; } +-keepclassmembers class io.github.lsposed.manager.receivers.LSPosedManagerServiceClient { + private static android.os.IBinder binder; +} \ No newline at end of file diff --git a/app/src/main/java/io/github/lsposed/manager/App.java b/app/src/main/java/io/github/lsposed/manager/App.java index 70ee141e..9658dc51 100644 --- a/app/src/main/java/io/github/lsposed/manager/App.java +++ b/app/src/main/java/io/github/lsposed/manager/App.java @@ -42,7 +42,7 @@ import okhttp3.Cache; import okhttp3.OkHttpClient; import rikka.material.app.DayNightDelegate; -import static io.github.lsposed.manager.receivers.LSPosedServiceClient.testBinder; +import static io.github.lsposed.manager.receivers.LSPosedManagerServiceClient.testBinder; public class App extends Application { public static final String TAG = "LSPosedManager"; diff --git a/app/src/main/java/io/github/lsposed/manager/receivers/LSPosedServiceClient.java b/app/src/main/java/io/github/lsposed/manager/receivers/LSPosedManagerServiceClient.java similarity index 69% rename from app/src/main/java/io/github/lsposed/manager/receivers/LSPosedServiceClient.java rename to app/src/main/java/io/github/lsposed/manager/receivers/LSPosedManagerServiceClient.java index b0faebe6..2880e533 100644 --- a/app/src/main/java/io/github/lsposed/manager/receivers/LSPosedServiceClient.java +++ b/app/src/main/java/io/github/lsposed/manager/receivers/LSPosedManagerServiceClient.java @@ -1,20 +1,25 @@ package io.github.lsposed.manager.receivers; import android.content.pm.PackageInfo; +import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import java.util.List; +import io.github.lsposed.lspd.ILSPManagerService; import io.github.lsposed.manager.App; -import io.github.xposed.xposedservice.XposedService; -public class LSPosedServiceClient { +public class LSPosedManagerServiceClient { + + private static IBinder binder = null; + private static ILSPManagerService service = null; public static void testBinder() { - XposedService service = XposedService.getService(); + if (service == null && binder != null) { + service = ILSPManagerService.Stub.asInterface(binder); + } if (service == null) { - Log.e(App.TAG, "Version fail"); return; } int ver = -1; diff --git a/core/build.gradle b/core/build.gradle index 76c1b2f1..a68c23ad 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -63,6 +63,7 @@ dependencies { compileOnly 'androidx.annotation:annotation:1.1.0' implementation project(':interface') implementation project(':hiddenapi-bridge') + implementation project(':manager-service') } android { diff --git a/core/src/main/java/io/github/lsposed/lspd/core/Main.java b/core/src/main/java/io/github/lsposed/lspd/core/Main.java index 679ee0e9..3499d1cc 100644 --- a/core/src/main/java/io/github/lsposed/lspd/core/Main.java +++ b/core/src/main/java/io/github/lsposed/lspd/core/Main.java @@ -28,11 +28,12 @@ import android.ddm.DdmHandleAppName; import io.github.lsposed.common.KeepAll; import io.github.lsposed.lspd.service.LSPosedService; +import io.github.lsposed.lspd.service.Service; import io.github.lsposed.lspd.util.Utils; import java.util.concurrent.atomic.AtomicReference; -import static io.github.lsposed.lspd.service.LSPosedService.TAG; +import static io.github.lsposed.lspd.service.Service.TAG; @SuppressLint("DefaultLocale") public class Main implements KeepAll { @@ -135,6 +136,6 @@ public class Main implements KeepAll { waitSystemService(Context.USER_SERVICE); waitSystemService(Context.APP_OPS_SERVICE); - LSPosedService.start(); + Service.start(); } } diff --git a/core/src/main/java/io/github/lsposed/lspd/hooker/XposedInstallerHooker.java b/core/src/main/java/io/github/lsposed/lspd/hooker/XposedInstallerHooker.java index 5fff575f..7e12ad06 100644 --- a/core/src/main/java/io/github/lsposed/lspd/hooker/XposedInstallerHooker.java +++ b/core/src/main/java/io/github/lsposed/lspd/hooker/XposedInstallerHooker.java @@ -115,8 +115,8 @@ public class XposedInstallerHooker { return ConfigManager.getMiscPath(); } }); - Class serviceClass = XposedHelpers.findClass("io.github.xposed.xposedservice.XposedService", classLoader); - XposedHelpers.setStaticObjectField(serviceClass, "serviceBinder", BridgeService.requireBinder()); + Class serviceClass = XposedHelpers.findClass("io.github.lsposed.manager.receivers.LSPosedManagerServiceClient", classLoader); + XposedHelpers.setStaticObjectField(serviceClass, "binder", BridgeService.requireBinder()); Utils.logI("Hooked LSPosed Manager"); } catch (Throwable t) { diff --git a/core/src/main/java/io/github/lsposed/lspd/service/BridgeService.java b/core/src/main/java/io/github/lsposed/lspd/service/BridgeService.java index 389cdef9..feb87997 100644 --- a/core/src/main/java/io/github/lsposed/lspd/service/BridgeService.java +++ b/core/src/main/java/io/github/lsposed/lspd/service/BridgeService.java @@ -1,6 +1,5 @@ package io.github.lsposed.lspd.service; -import android.content.pm.PackageManager; import android.os.Binder; import android.os.IBinder; import android.os.Parcel; @@ -14,23 +13,29 @@ import androidx.annotation.Nullable; import java.lang.reflect.Field; import java.util.Map; -import io.github.lsposed.lspd.nativebridge.ConfigManager; +import io.github.lsposed.lspd.ILSPManagerService; import io.github.xposed.xposedservice.IXposedService; import static android.os.Binder.getCallingUid; -import static io.github.lsposed.lspd.service.LSPosedService.TAG; +import static io.github.lsposed.lspd.service.Service.TAG; public class BridgeService { private static final int TRANSACTION_CODE = ('_' << 24) | ('L' << 16) | ('S' << 8) | 'P'; private static final String DESCRIPTOR = "android.app.IActivityManager"; private static final String SERVICE_NAME = "activity"; - private static final int ACTION_SEND_BINDER = 1; - private static final int ACTION_GET_BINDER = ACTION_SEND_BINDER + 1; + enum ACTION { + ACTION_SEND_BINDER, + ACTION_SEND_MANAGER_BINDER, + ACTION_GET_BINDER, + } private static IBinder serviceBinder = null; private static IXposedService service = null; + private static IBinder managerBinder = null; + private static ILSPManagerService manager = null; + private static final IBinder.DeathRecipient BRIDGE_SERVICE_DEATH_RECIPIENT = () -> { Log.i(TAG, "service " + SERVICE_NAME + " is dead. "); @@ -52,12 +57,15 @@ public class BridgeService { Log.w(TAG, "clear ServiceManager: " + Log.getStackTraceString(e)); } - sendToBridge(true); + sendToBridge(ACTION.ACTION_SEND_BINDER, serviceBinder, true); + sendToBridge(ACTION.ACTION_SEND_MANAGER_BINDER, managerBinder, true); }; private static final IBinder.DeathRecipient LSPSERVICE_DEATH_RECIPIENT = () -> { serviceBinder = null; service = null; + managerBinder = null; + manager = null; Log.e(TAG, "service is dead"); }; @@ -70,9 +78,7 @@ public class BridgeService { private static Listener listener; - private static PackageManager pm = null; - - private static void sendToBridge(boolean isRestart) { + private static void sendToBridge(ACTION action, IBinder binder, boolean isRestart) { IBinder bridgeService; do { bridgeService = ServiceManager.getService(SERVICE_NAME); @@ -98,7 +104,7 @@ public class BridgeService { bridgeService.linkToDeath(BRIDGE_SERVICE_DEATH_RECIPIENT, 0); } catch (Throwable e) { Log.w(TAG, "linkToDeath " + Log.getStackTraceString(e)); - sendToBridge(false); + sendToBridge(action, binder, false); return; } @@ -109,9 +115,9 @@ public class BridgeService { for (int i = 0; i < 3; i++) { try { data.writeInterfaceToken(DESCRIPTOR); - data.writeInt(ACTION_SEND_BINDER); - Log.v(TAG, "binder " + serviceBinder.toString()); - data.writeStrongBinder(serviceBinder); + data.writeInt(action.ordinal()); + Log.v(TAG, "binder " + binder.toString()); + data.writeStrongBinder(binder); res = bridgeService.transact(TRANSACTION_CODE, data, reply, 0); reply.readException(); } catch (Throwable e) { @@ -136,23 +142,36 @@ public class BridgeService { } } - private static void receiveFromBridge(IBinder binder) { + private static void receiveFromBridge(ACTION action, IBinder binder) { if (binder == null) { Log.e(TAG, "received empty binder"); return; } - if (serviceBinder == null) { - PackageReceiver.register(); - } else { - serviceBinder.unlinkToDeath(LSPSERVICE_DEATH_RECIPIENT, 0); - } + if(action == ACTION.ACTION_SEND_MANAGER_BINDER) { + if (managerBinder != null) { + managerBinder.unlinkToDeath(LSPSERVICE_DEATH_RECIPIENT, 0); + } - serviceBinder = binder; - service = IXposedService.Stub.asInterface(serviceBinder); - try { - serviceBinder.linkToDeath(LSPSERVICE_DEATH_RECIPIENT, 0); - } catch (RemoteException ignored) { + managerBinder = binder; + manager = LSPManagerService.Stub.asInterface(managerBinder); + try { + managerBinder.linkToDeath(LSPSERVICE_DEATH_RECIPIENT, 0); + } catch (RemoteException ignored) { + } + } else if (action == ACTION.ACTION_SEND_BINDER) { + if (serviceBinder == null) { + PackageReceiver.register(); + } else { + serviceBinder.unlinkToDeath(LSPSERVICE_DEATH_RECIPIENT, 0); + } + + serviceBinder = binder; + service = IXposedService.Stub.asInterface(serviceBinder); + try { + serviceBinder.linkToDeath(LSPSERVICE_DEATH_RECIPIENT, 0); + } catch (RemoteException ignored) { + } } Log.i(TAG, "binder received"); @@ -162,13 +181,24 @@ public class BridgeService { BridgeService.listener = listener; BridgeService.service = service; BridgeService.serviceBinder = service.asBinder(); - sendToBridge(false); + sendToBridge(ACTION.ACTION_SEND_BINDER, serviceBinder, false); + } + + public static void send(LSPManagerService service, Listener listener) { + BridgeService.listener = listener; + BridgeService.manager = service; + BridgeService.managerBinder = service.asBinder(); + sendToBridge(ACTION.ACTION_SEND_MANAGER_BINDER, managerBinder, false); } public static IXposedService getService() { return service; } + public static ILSPManagerService getManager() { + return manager; + } + public static IBinder requireBinder() { IBinder binder = ServiceManager.getService(SERVICE_NAME); if (binder == null) return null; @@ -177,7 +207,7 @@ public class BridgeService { Parcel reply = Parcel.obtain(); try { data.writeInterfaceToken(DESCRIPTOR); - data.writeInt(ACTION_GET_BINDER); + data.writeInt(ACTION.ACTION_GET_BINDER.ordinal()); binder.transact(TRANSACTION_CODE, data, reply, 0); reply.readException(); IBinder received = reply.readStrongBinder(); @@ -197,13 +227,14 @@ public class BridgeService { public static boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) { data.enforceInterface(DESCRIPTOR); - int action = data.readInt(); + ACTION action = ACTION.values()[data.readInt()]; Log.d(TAG, "onTransact: action=" + action + ", callingUid=" + Binder.getCallingUid() + ", callingPid=" + Binder.getCallingPid()); switch (action) { - case ACTION_SEND_BINDER: { + case ACTION_SEND_BINDER: + case ACTION_SEND_MANAGER_BINDER: { if (Binder.getCallingUid() == 0) { - receiveFromBridge(data.readStrongBinder()); + receiveFromBridge(action, data.readStrongBinder()); if (reply != null) { reply.writeNoException(); } @@ -219,8 +250,8 @@ public class BridgeService { } if (reply != null) { reply.writeNoException(); - Log.d(TAG, "saved binder is " + serviceBinder.toString()); - reply.writeStrongBinder(serviceBinder); + Log.d(TAG, "saved binder is " + managerBinder.toString()); + reply.writeStrongBinder(managerBinder); } return true; } diff --git a/core/src/main/java/io/github/lsposed/lspd/service/LSPManagerService.java b/core/src/main/java/io/github/lsposed/lspd/service/LSPManagerService.java new file mode 100644 index 00000000..2ada73fe --- /dev/null +++ b/core/src/main/java/io/github/lsposed/lspd/service/LSPManagerService.java @@ -0,0 +1,47 @@ +package io.github.lsposed.lspd.service; + +import android.content.pm.PackageInfo; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + +import de.robv.android.xposed.XposedBridge; +import io.github.lsposed.lspd.ILSPManagerService; +import io.github.lsposed.lspd.utils.ParceledListSlice; + +import static io.github.lsposed.lspd.service.Service.TAG; + +public class LSPManagerService extends ILSPManagerService.Stub { + + public LSPManagerService() { + BridgeService.send(this, new BridgeService.Listener() { + @Override + public void onSystemServerRestarted() { + Log.w(TAG, "system restarted..."); + } + + @Override + public void onResponseFromBridgeService(boolean response) { + if (response) { + Log.i(TAG, "sent service to bridge"); + } else { + Log.w(TAG, "no response from bridge"); + } + } + }); + } + @Override + public IBinder asBinder() { + return this; + } + + @Override + public int getVersion() { + return XposedBridge.getXposedVersion(); + } + + @Override + public ParceledListSlice getInstalledPackagesFromAllUsers(int flags) throws RemoteException { + return PackageService.getInstalledPackagesFromAllUsers(flags); + } +} diff --git a/core/src/main/java/io/github/lsposed/lspd/service/LSPosedService.java b/core/src/main/java/io/github/lsposed/lspd/service/LSPosedService.java index 805a88ef..c91bdde3 100644 --- a/core/src/main/java/io/github/lsposed/lspd/service/LSPosedService.java +++ b/core/src/main/java/io/github/lsposed/lspd/service/LSPosedService.java @@ -1,29 +1,14 @@ package io.github.lsposed.lspd.service; -import android.content.pm.PackageInfo; import android.os.IBinder; -import android.os.Looper; -import android.os.RemoteException; import android.util.Log; import de.robv.android.xposed.XposedBridge; import io.github.xposed.xposedservice.IXposedService; -import io.github.xposed.xposedservice.utils.ParceledListSlice; + +import static io.github.lsposed.lspd.service.Service.TAG; public class LSPosedService extends IXposedService.Stub { - public static final String TAG = "LSPosedService"; - - // call by ourselves - public static void start() { - Log.i(TAG, "starting server..."); - - Looper.prepare(); - new LSPosedService(); - Looper.loop(); - - Log.i(TAG, "server exited"); - System.exit(0); - } public LSPosedService() { BridgeService.send(this, new BridgeService.Listener() { @@ -52,9 +37,4 @@ public class LSPosedService extends IXposedService.Stub { public int getVersion() { return XposedBridge.getXposedVersion(); } - - @Override - public ParceledListSlice getInstalledPackagesFromAllUsers(int flags) throws RemoteException { - return PackageService.getInstalledPackagesFromAllUsers(flags); - } } diff --git a/core/src/main/java/io/github/lsposed/lspd/service/PackageService.java b/core/src/main/java/io/github/lsposed/lspd/service/PackageService.java index eb26031c..b1e54d61 100644 --- a/core/src/main/java/io/github/lsposed/lspd/service/PackageService.java +++ b/core/src/main/java/io/github/lsposed/lspd/service/PackageService.java @@ -2,16 +2,14 @@ package io.github.lsposed.lspd.service; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; -import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; -import android.util.Log; import java.util.ArrayList; import io.github.lsposed.lspd.nativebridge.ConfigManager; -import io.github.xposed.xposedservice.utils.ParceledListSlice; +import io.github.lsposed.lspd.utils.ParceledListSlice; public class PackageService { private static IPackageManager pm = null; @@ -26,13 +24,13 @@ public class PackageService { } public static PackageInfo getPackageInfo(String packageName, int flags, int uid) throws RemoteException { - pm = getPackageManager(); + IPackageManager pm = getPackageManager(); if (pm == null) return null; return pm.getPackageInfo(packageName, flags, uid); } public static String[] getPackagesForUid(int uid) throws RemoteException { - pm = getPackageManager(); + IPackageManager pm = getPackageManager(); if (pm == null) return new String[0]; return pm.getPackagesForUid(uid); } diff --git a/core/src/main/java/io/github/lsposed/lspd/service/Service.java b/core/src/main/java/io/github/lsposed/lspd/service/Service.java new file mode 100644 index 00000000..eb03d7b7 --- /dev/null +++ b/core/src/main/java/io/github/lsposed/lspd/service/Service.java @@ -0,0 +1,21 @@ +package io.github.lsposed.lspd.service; + +import android.os.Looper; +import android.util.Log; + +public class Service { + public static final String TAG = "LSPosedService"; + // call by ourselves + public static void start() { + Log.i(TAG, "starting server..."); + + Looper.prepare(); + new LSPosedService(); + new LSPManagerService(); + Looper.loop(); + + Log.i(TAG, "server exited"); + System.exit(0); + } + +} diff --git a/manager-service/.gitignore b/manager-service/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/manager-service/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/manager-service/build.gradle b/manager-service/build.gradle new file mode 100644 index 00000000..6d7311ad --- /dev/null +++ b/manager-service/build.gradle @@ -0,0 +1,24 @@ +plugins { + id 'com.android.library' +} + +android { + compileSdkVersion 30 + buildToolsVersion "30.0.3" + + defaultConfig { + minSdkVersion 26 + targetSdkVersion 30 + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} diff --git a/manager-service/proguard-rules.pro b/manager-service/proguard-rules.pro new file mode 100644 index 00000000..f1b42451 --- /dev/null +++ b/manager-service/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/manager-service/src/main/AndroidManifest.xml b/manager-service/src/main/AndroidManifest.xml new file mode 100644 index 00000000..75e152c9 --- /dev/null +++ b/manager-service/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/manager-service/src/main/aidl/io/github/lsposed/lspd/ILSPManagerService.aidl b/manager-service/src/main/aidl/io/github/lsposed/lspd/ILSPManagerService.aidl new file mode 100644 index 00000000..6234a156 --- /dev/null +++ b/manager-service/src/main/aidl/io/github/lsposed/lspd/ILSPManagerService.aidl @@ -0,0 +1,8 @@ +package io.github.lsposed.lspd; + +import io.github.lsposed.lspd.utils.ParceledListSlice; + +interface ILSPManagerService { + int getVersion() = 1; + ParceledListSlice getInstalledPackagesFromAllUsers(int flags) = 2; +} diff --git a/manager-service/src/main/aidl/io/github/lsposed/lspd/utils/ParceledListSlice.aidl b/manager-service/src/main/aidl/io/github/lsposed/lspd/utils/ParceledListSlice.aidl new file mode 100644 index 00000000..0ddd97fb --- /dev/null +++ b/manager-service/src/main/aidl/io/github/lsposed/lspd/utils/ParceledListSlice.aidl @@ -0,0 +1,3 @@ +package io.github.lsposed.lspd.utils; + +parcelable ParceledListSlice; \ No newline at end of file diff --git a/manager-service/src/main/java/io/github/lsposed/lspd/utils/BaseParceledListSlice.java b/manager-service/src/main/java/io/github/lsposed/lspd/utils/BaseParceledListSlice.java new file mode 100644 index 00000000..0e50fc8f --- /dev/null +++ b/manager-service/src/main/java/io/github/lsposed/lspd/utils/BaseParceledListSlice.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.github.lsposed.lspd.utils; + +import android.os.Binder; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.RemoteException; +import android.util.Log; +import java.util.ArrayList; +import java.util.List; +/** + * Transfer a large list of Parcelable objects across an IPC. Splits into + * multiple transactions if needed. + * + * Caveat: for efficiency and security, all elements must be the same concrete type. + * In order to avoid writing the class name of each object, we must ensure that + * each object is the same type, or else unparceling then reparceling the data may yield + * a different result if the class name encoded in the Parcelable is a Base type. + * See b/17671747. + * + */ +abstract class BaseParceledListSlice implements Parcelable { + private static String TAG = "ParceledListSlice"; + private static boolean DEBUG = false; + /* + * TODO get this number from somewhere else. For now set it to a quarter of + * the 1MB limit. + */ + private static final int MAX_IPC_SIZE = 64 * 1024; + private final List mList; + private int mInlineCountLimit = Integer.MAX_VALUE; + + public BaseParceledListSlice(List list) { + mList = list; + } + + @SuppressWarnings("unchecked") + BaseParceledListSlice(Parcel p, ClassLoader loader) { + final int N = p.readInt(); + mList = new ArrayList(N); + if (DEBUG) Log.d(TAG, "Retrieving " + N + " items"); + if (N <= 0) { + return; + } + Parcelable.Creator creator = readParcelableCreator(p, loader); + Class listElementClass = null; + int i = 0; + while (i < N) { + if (p.readInt() == 0) { + break; + } + final T parcelable = readCreator(creator, p, loader); + if (listElementClass == null) { + listElementClass = parcelable.getClass(); + } else { + verifySameType(listElementClass, parcelable.getClass()); + } + mList.add(parcelable); + if (DEBUG) Log.d(TAG, "Read inline #" + i + ": " + mList.get(mList.size()-1)); + i++; + } + if (i >= N) { + return; + } + final IBinder retriever = p.readStrongBinder(); + while (i < N) { + if (DEBUG) Log.d(TAG, "Reading more @" + i + " of " + N + ": retriever=" + retriever); + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInt(i); + try { + retriever.transact(IBinder.FIRST_CALL_TRANSACTION, data, reply, 0); + } catch (RemoteException e) { + Log.w(TAG, "Failure retrieving array; only received " + i + " of " + N, e); + return; + } + while (i < N && reply.readInt() != 0) { + final T parcelable = readCreator(creator, reply, loader); + verifySameType(listElementClass, parcelable.getClass()); + mList.add(parcelable); + if (DEBUG) Log.d(TAG, "Read extra #" + i + ": " + mList.get(mList.size()-1)); + i++; + } + reply.recycle(); + data.recycle(); + } + } + private T readCreator(Parcelable.Creator creator, Parcel p, ClassLoader loader) { + if (creator instanceof Parcelable.ClassLoaderCreator) { + Parcelable.ClassLoaderCreator classLoaderCreator = + (Parcelable.ClassLoaderCreator) creator; + return (T) classLoaderCreator.createFromParcel(p, loader); + } + return (T) creator.createFromParcel(p); + } + private static void verifySameType(final Class expected, final Class actual) { + if (!actual.equals(expected)) { + throw new IllegalArgumentException("Can't unparcel type " + + (actual == null ? null : actual.getName()) + " in list of type " + + (expected == null ? null : expected.getName())); + } + } + public List getList() { + return mList; + } + /** + * Set a limit on the maximum number of entries in the array that will be included + * inline in the initial parcelling of this object. + */ + public void setInlineCountLimit(int maxCount) { + mInlineCountLimit = maxCount; + } + /** + * Write this to another Parcel. Note that this discards the internal Parcel + * and should not be used anymore. This is so we can pass this to a Binder + * where we won't have a chance to call recycle on this. + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + final int N = mList.size(); + final int callFlags = flags; + dest.writeInt(N); + if (DEBUG) Log.d(TAG, "Writing " + N + " items"); + if (N > 0) { + final Class listElementClass = mList.get(0).getClass(); + writeParcelableCreator(mList.get(0), dest); + int i = 0; + while (i < N && i < mInlineCountLimit && dest.dataSize() < MAX_IPC_SIZE) { + dest.writeInt(1); + final T parcelable = mList.get(i); + verifySameType(listElementClass, parcelable.getClass()); + writeElement(parcelable, dest, callFlags); + if (DEBUG) Log.d(TAG, "Wrote inline #" + i + ": " + mList.get(i)); + i++; + } + if (i < N) { + dest.writeInt(0); + Binder retriever = new Binder() { + @Override + protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) + throws RemoteException { + if (code != FIRST_CALL_TRANSACTION) { + return super.onTransact(code, data, reply, flags); + } + int i = data.readInt(); + if (DEBUG) Log.d(TAG, "Writing more @" + i + " of " + N); + while (i < N && reply.dataSize() < MAX_IPC_SIZE) { + reply.writeInt(1); + final T parcelable = mList.get(i); + verifySameType(listElementClass, parcelable.getClass()); + writeElement(parcelable, reply, callFlags); + if (DEBUG) Log.d(TAG, "Wrote extra #" + i + ": " + mList.get(i)); + i++; + } + if (i < N) { + if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N); + reply.writeInt(0); + } + return true; + } + }; + if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N + ": retriever=" + retriever); + dest.writeStrongBinder(retriever); + } + } + } + protected abstract void writeElement(T parcelable, Parcel reply, int callFlags); + protected abstract void writeParcelableCreator(T parcelable, Parcel dest); + protected abstract Parcelable.Creator readParcelableCreator(Parcel from, ClassLoader loader); +} diff --git a/manager-service/src/main/java/io/github/lsposed/lspd/utils/ParceledListSlice.java b/manager-service/src/main/java/io/github/lsposed/lspd/utils/ParceledListSlice.java new file mode 100644 index 00000000..50e36e69 --- /dev/null +++ b/manager-service/src/main/java/io/github/lsposed/lspd/utils/ParceledListSlice.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.github.lsposed.lspd.utils; + +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.os.BadParcelableException; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + +/** + * Transfer a large list of Parcelable objects across an IPC. Splits into + * multiple transactions if needed. + * + * @see BaseParceledListSlice + */ +public class ParceledListSlice extends BaseParceledListSlice { + + private static final String TAG = "ParceledListSlice"; + + // Cache of previously looked up CREATOR.createFromParcel() methods for + // particular classes. Keys are the names of the classes, values are + // Method objects. + private static final HashMap>> + CREATORS = new HashMap<>(); + + static { + putCreator(PackageInfo.class.getName(), PackageInfo.CREATOR); + putCreator(ApplicationInfo.class.getName(), ApplicationInfo.CREATOR); + } + + public static void putCreator(String name, Parcelable.Creator creator) { + HashMap> map = CREATORS.get(null); + if (map == null) { + map = new HashMap<>(); + CREATORS.put(null, map); + } + map.put(name, creator); + } + + public ParceledListSlice(List list) { + super(list); + } + + private ParceledListSlice(Parcel in, ClassLoader loader) { + super(in, loader); + } + + public static ParceledListSlice emptyList() { + return new ParceledListSlice(Collections.emptyList()); + } + + @Override + public int describeContents() { + int contents = 0; + final List list = getList(); + for (int i = 0; i < list.size(); i++) { + contents |= list.get(i).describeContents(); + } + return contents; + } + + @Override + protected void writeElement(T parcelable, Parcel dest, int callFlags) { + parcelable.writeToParcel(dest, callFlags); + } + + @Override + protected void writeParcelableCreator(T parcelable, Parcel dest) { + String name = parcelable.getClass().getName(); + dest.writeString(name); + } + + @Override + protected Parcelable.Creator readParcelableCreator(Parcel from, ClassLoader loader) { + String name = from.readString(); + if (name == null) { + return null; + } + Parcelable.Creator creator; + synchronized (CREATORS) { + HashMap> map = CREATORS.get(loader); + if (map == null) { + map = new HashMap<>(); + CREATORS.put(loader, map); + } + creator = map.get(name); + if (creator == null) { + try { + // If loader == null, explicitly emulate Class.forName(String) "caller + // classloader" behavior. + ClassLoader parcelableClassLoader = + (loader == null ? getClass().getClassLoader() : loader); + // Avoid initializing the Parcelable class until we know it implements + // Parcelable and has the necessary CREATOR field. http://b/1171613. + Class parcelableClass = Class.forName(name, false /* initialize */, + parcelableClassLoader); + if (!Parcelable.class.isAssignableFrom(parcelableClass)) { + throw new BadParcelableException("Parcelable protocol requires subclassing " + + "from Parcelable on class " + name); + } + Field f = parcelableClass.getField("CREATOR"); + if ((f.getModifiers() & Modifier.STATIC) == 0) { + throw new BadParcelableException("Parcelable protocol requires " + + "the CREATOR object to be static on class " + name); + } + Class creatorType = f.getType(); + if (!Parcelable.Creator.class.isAssignableFrom(creatorType)) { + // Fail before calling Field.get(), not after, to avoid initializing + // parcelableClass unnecessarily. + throw new BadParcelableException("Parcelable protocol requires a " + + "Parcelable.Creator object called " + + "CREATOR on class " + name); + } + creator = (Parcelable.Creator) f.get(null); + } catch (IllegalAccessException e) { + Log.e(TAG, "Illegal access when unmarshalling: " + name, e); + throw new BadParcelableException( + "IllegalAccessException when unmarshalling: " + name); + } catch (ClassNotFoundException e) { + Log.e(TAG, "Class not found when unmarshalling: " + name, e); + throw new BadParcelableException( + "ClassNotFoundException when unmarshalling: " + name); + } catch (NoSuchFieldException e) { + throw new BadParcelableException("Parcelable protocol requires a " + + "Parcelable.Creator object called " + + "CREATOR on class " + name); + } + if (creator == null) { + throw new BadParcelableException("Parcelable protocol requires a " + + "non-null Parcelable.Creator object called " + + "CREATOR on class " + name); + } + + map.put(name, creator); + } + } + + return creator; + } + + public static final Parcelable.ClassLoaderCreator CREATOR = + new Parcelable.ClassLoaderCreator() { + public ParceledListSlice createFromParcel(Parcel in) { + return new ParceledListSlice(in, null); + } + + @Override + public ParceledListSlice createFromParcel(Parcel in, ClassLoader loader) { + return new ParceledListSlice(in, loader); + } + + @Override + public ParceledListSlice[] newArray(int size) { + return new ParceledListSlice[size]; + } + }; +} diff --git a/settings.gradle b/settings.gradle index 3c263db6..9b4b0b2f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,5 +1,5 @@ rootProject.name = "LSPosed" -include ':core', ':hiddenapi-stubs', ':sandhook-hooklib', ':sandhook-annotation', ':app', ':key-selector', ':service', ':interface', ':hiddenapi-bridge' +include ':core', ':hiddenapi-stubs', ':sandhook-hooklib', ':sandhook-annotation', ':app', ':key-selector', ':service', ':interface', ':hiddenapi-bridge', ':manager-service' def service_root = "service" project(':interface').projectDir = file("$service_root${File.separator}interface")