package com.oasisfeng.condom; import android.app.Application; import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.Binder; import android.os.IBinder; import android.os.Parcel; import android.os.RemoteException; import androidx.annotation.Nullable; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.Arrays; import java.util.Objects; /** * A simple service to mimic the real-world service to be wrapped by {@link com.oasisfeng.condom.CondomProcess CondomProcess}. * * Created by Oasis on 2017/10/2. */ public class TestService extends Service { interface Procedure { void run(final Context context); } static void invokeService(final IBinder binder, final Procedure procedure) { final Parcel data = Parcel.obtain(), reply = Parcel.obtain(); final Class<? extends Procedure> clazz = procedure.getClass(); data.writeString(clazz.getName()); final Constructor<?>[] constructors = clazz.getDeclaredConstructors(); if (constructors.length < 1) throw new IllegalArgumentException("Invalid lambda class: " + clazz); final Field[] fields = clazz.getDeclaredFields(); if (fields.length != constructors[0].getParameterTypes().length) throw new IllegalArgumentException("Lambda parameters mismatch: " + Arrays.deepToString(fields)); final Class<?> enclosing_class = clazz.getEnclosingClass(); for (final Field field : fields) { field.setAccessible(true); final Object value; try { value = field.get(procedure); } catch (final IllegalAccessException e) { throw new RuntimeException(e); } // Enclosing class is always carried by anonymous inner class, but useless here. final Class<?> type = Objects.requireNonNull(value).getClass(); if (type == enclosing_class || type == Context.class || type == Application.class) data.writeValue(null); else try { data.writeValue(value); } catch (final RuntimeException e) { throw new RuntimeException("Invalid lambda parameter type: " + value.getClass().getCanonicalName(), e); } } try { binder.transact(0, data, reply, 0); } catch (final RemoteException e) { throw new RuntimeException(e); // Avoid throwing RemoteException everywhere. } data.recycle(); final Throwable e = (Throwable) reply.readValue(TestService.class.getClassLoader()); if (e instanceof Error) throw (Error) e; if (e instanceof RuntimeException) throw (RuntimeException) e; if (e != null) throw new RuntimeException("Exception thrown by remote procedure", e); reply.recycle(); } @Override public @Nullable IBinder onBind(final Intent intent) { return new Binder() { @Override protected boolean onTransact(final int code, final Parcel data, final @Nullable Parcel reply, final int flags) { try { final Class<?> clazz = Class.forName(Objects.requireNonNull(data.readString())); final Constructor<?> constructor = clazz.getDeclaredConstructors()[0]; constructor.setAccessible(true); final Class<?>[] parameter_types = constructor.getParameterTypes(); final Object[] args = new Object[parameter_types.length]; for (int i = 0; i < args.length; i++) { if (parameter_types[i] == Context.class) args[i] = TestService.this; else if (parameter_types[i] == Application.class) args[i] = getApplication(); else args[i] = data.readValue(getClassLoader()); } final Procedure procedure = (Procedure) constructor.newInstance(args); procedure.run(TestService.this); if (reply != null) reply.writeValue(null); } catch (final Throwable t) { if (reply != null) reply.writeValue(t); } return true; } }; } }