2626import org .objenesis .strategy .StdInstantiatorStrategy ;
2727
2828import java .io .IOException ;
29+ import java .io .Serial ;
30+ import java .io .Serializable ;
2931import java .util .Objects ;
3032import java .util .concurrent .CompletableFuture ;
3133import java .util .concurrent .ExecutionException ;
@@ -159,7 +161,24 @@ private static ClassResolver getAppClassResolver() {
159161 */
160162 @ NonNull
161163 public static <T > Future <T > submit (@ NonNull RootCallable <T > callable ) {
162- return sExecutor .submit (() -> executeSync (callable ));
164+ return submit (callable , RootArgs .EMPTY );
165+ }
166+
167+ /**
168+ * Submits {@code callable} together with {@code args} to the root process and returns a
169+ * {@link Future} that resolves with the callable's return value.
170+ *
171+ * <p>The supplied {@code args} are serialized alongside the callable and are available
172+ * inside the root process via {@link RootOptions#getArgs()}.
173+ *
174+ * @param callable The closure to execute in the root process.
175+ * @param args Custom arguments to pass to the callable; must be Kryo-serializable.
176+ * @param <T> Return type of the callable.
177+ * @return A {@link Future} representing the pending result.
178+ */
179+ @ NonNull
180+ public static <T > Future <T > submit (@ NonNull RootCallable <T > callable , @ NonNull RootArgs args ) {
181+ return sExecutor .submit (() -> executeSync (callable , args ));
163182 }
164183
165184 /**
@@ -175,8 +194,26 @@ public static <T> Future<T> submit(@NonNull RootCallable<T> callable) {
175194 @ Nullable
176195 public static <T > T executeBlocking (@ NonNull RootCallable <T > callable )
177196 throws IOException , InterruptedException {
197+ return executeBlocking (callable , RootArgs .EMPTY );
198+ }
199+
200+ /**
201+ * Submits {@code callable} together with {@code args} to the root process and blocks the
202+ * calling thread until the result is available. Must NOT be called on the main thread.
203+ *
204+ * @param callable The closure to execute in the root process.
205+ * @param args Custom arguments available in the root process via
206+ * {@link RootOptions#getArgs()}.
207+ * @param <T> Return type of the callable.
208+ * @return The value returned by the callable.
209+ * @throws IOException If IPC serialisation, transport, or the remote call fails.
210+ * @throws InterruptedException If the calling thread is interrupted while waiting.
211+ */
212+ @ Nullable
213+ public static <T > T executeBlocking (@ NonNull RootCallable <T > callable , @ NonNull RootArgs args )
214+ throws IOException , InterruptedException {
178215 try {
179- return submit (callable ).get ();
216+ return submit (callable , args ).get ();
180217 } catch (ExecutionException e ) {
181218 Throwable cause = e .getCause ();
182219 if (cause instanceof IOException ) throw (IOException ) cause ;
@@ -202,9 +239,34 @@ public static <T> T executeBlocking(
202239 @ NonNull RootCallable <T > callable ,
203240 long timeout ,
204241 @ NonNull TimeUnit unit
242+ ) throws IOException , InterruptedException , TimeoutException {
243+ return executeBlocking (callable , RootArgs .EMPTY , timeout , unit );
244+ }
245+
246+ /**
247+ * Submits {@code callable} together with {@code args} to the root process and blocks the
248+ * calling thread for at most {@code timeout} {@code unit}s.
249+ *
250+ * @param callable The closure to execute in the root process.
251+ * @param args Custom arguments available in the root process via
252+ * {@link RootOptions#getArgs()}.
253+ * @param timeout Maximum time to wait.
254+ * @param unit Time unit for {@code timeout}.
255+ * @param <T> Return type of the callable.
256+ * @return The value returned by the callable.
257+ * @throws IOException If IPC serialisation, transport, or the remote call fails.
258+ * @throws InterruptedException If the calling thread is interrupted while waiting.
259+ * @throws TimeoutException If the operation does not complete within the timeout.
260+ */
261+ @ Nullable
262+ public static <T > T executeBlocking (
263+ @ NonNull RootCallable <T > callable ,
264+ @ NonNull RootArgs args ,
265+ long timeout ,
266+ @ NonNull TimeUnit unit
205267 ) throws IOException , InterruptedException , TimeoutException {
206268 try {
207- return submit (callable ).get (timeout , unit );
269+ return submit (callable , args ).get (timeout , unit );
208270 } catch (ExecutionException e ) {
209271 Throwable cause = e .getCause ();
210272 if (cause instanceof IOException ) throw (IOException ) cause ;
@@ -219,9 +281,21 @@ public static <T> T executeBlocking(
219281 *
220282 * @throws IOException On any serialization, IPC, or type-mismatch error.
221283 */
222- @ SuppressWarnings ("unchecked" )
223284 @ Nullable
224285 static <T > T executeSync (@ NonNull RootCallable <T > callable ) throws IOException {
286+ return executeSync (callable , RootArgs .EMPTY );
287+ }
288+
289+ /**
290+ * Core synchronous IPC routine with custom arguments. Wraps {@code callable} and
291+ * {@code args} in a {@link CallableEnvelope}, serializes the envelope into a pipe,
292+ * hands the read-end to the root service, then reads and deserializes the result.
293+ *
294+ * @throws IOException On any serialization, IPC, or type-mismatch error.
295+ */
296+ @ SuppressWarnings ("unchecked" )
297+ @ Nullable
298+ static <T > T executeSync (@ NonNull RootCallable <T > callable , @ NonNull RootArgs args ) throws IOException {
225299 IRootThread svc ;
226300 try {
227301 svc = sRootServiceFuture .get ();
@@ -245,7 +319,7 @@ static <T> T executeSync(@NonNull RootCallable<T> callable) throws IOException {
245319 new ParcelFileDescriptor .AutoCloseOutputStream (callableWrite );
246320 Output output = new Output (fos )) {
247321 KryoManager kryo = buildKryo (null );
248- kryo .writeClassAndObject (output , callable );
322+ kryo .writeClassAndObject (output , new CallableEnvelope ( callable , args ) );
249323 output .flush ();
250324 }
251325
@@ -387,7 +461,23 @@ public Parcelable read(Kryo kryo, Input input, Class<? extends Parcelable> type)
387461 }
388462
389463 public static final Companion Companion = new Companion ();
464+
390465 public static final class Companion {
391- private Companion () {}
466+ private Companion () {
467+ }
468+ }
469+
470+ /**
471+ * Serialization envelope that bundles a {@link RootCallable} with its {@link RootArgs}
472+ * so both travel through the same Kryo-serialized pipe in a single pass.
473+ */
474+ record CallableEnvelope (RootCallable <?> callable , RootArgs args ) implements Serializable {
475+ @ Serial
476+ private static final long serialVersionUID = 1L ;
477+
478+ CallableEnvelope (@ NonNull RootCallable <?> callable , @ NonNull RootArgs args ) {
479+ this .callable = callable ;
480+ this .args = args ;
481+ }
392482 }
393483}
0 commit comments