1

I'm trying to understand a piece of code that uses Android Binders and Messages to perform IPC. I've read a few articles, papers and slides on binders, but I'm still confused and have a hard time understanding the code, since I didn't encounter any tutorials/examples of actually implementing binders in java code. I am listing down the sanitized/abbreviated code below as well as my understanding of what it does and where it gets confusing. I would appreciate if someone could help me understand this, especially pertaining to the flow of Parcels and Messages.

MyService.java

public class MyService extends Service
{
  private final IMyService.Stub mBinder;
  private Context _mContext;
  private MyServiceHandler mHandler;
  private HanderThread mHandlerThread;
  private IHelperService mHelperService;

  public MyService() {
    this.mBinder = new IMyService.Stub() {
      public void start_data(final String data) {
      try {
        if (MyService.this.mHelperService == null) {
          return;
        }
        Message msg = MyService.this.getHandler().obtainMessage(3, (Object)data);
        MyService.this.getHandler().sendMessage(msg);
      } catch (SecurityException ex) {
        // do some logging
      }
    };
  }

  private void startMessageProcess(final String message) {
    // Process the message argument
    final byte[] dataArray = this.getByteArray(message);
    byte[] returnData;
    try {
      returnData = this.mHelperService.foo(dataArray);
    } catch (Exception e) {
      // do some logging
    }
    Message msg = this.getHandler().obtainMessage(3, (Object)message);
    this.getHandler.sendMessage(msg);
  }

  private String getServerData(int id) {
    // builds a Uri based on id and posts it, retrieves String data from HTTP entity and returns it
  }

  private boolean postDataToServer(final byte[] arrayData) {
    // builds a Uri, post data to it, gets the status code, returns true if successful
  }

  private void sendData(final byte[] arrayData) {
    final Intent intent = new Intent();
    intent.setAction("com.my.service.intent.action.MY_RESULT");
    intent.putExtra("com.my.service.intent.extra.RESULT", 0);
    intent.putExtra("com.my.service.intent.extra.MY_DATA", arrayData);
    this._mContext.sendBroadcast(intent, "com.my.service.permission.MY_PERMISSION");
  }

  public Handler getHandler() {
    return this.mHandler;
  }

  public IBinder onBind(final Intent intent) {
    if (intent != null) {
      final String action = intent.getAction();
      if (action != null && action.equals("com.my.service.intent.action.BIND_MY_SERVICE")) {
        this._mContext = this.getApplicationContext();
        return (IBinder)this.mBinder;
      }
    }
    return null;
  }

  public void onCreate() {
    super.onCreate();
    try {
      final Class<?> serviceManager = Class.forName("android.os.ServiceManager");
      this.mHelperService = mHelperService.Stub.asInterface((IBinder)serviceManager.getMethod("getService", String.class).invoke(serviceManager, "helperservice"));
      if (this.mHelperService == null) {
          return;
      }
    }
    catch (ClassNotFoundException ex2) {
        Log.e("MyService", "Helper service ClassNotFoundException !!!");
    }
    catch (Exception ex) {
        Log.e("MyService", "onCreate() Exception: " + Log.getStackTraceString((Throwable)ex));
    }
    this.mHandlerThread = new HandlerThread("MyService");
    this.mHandlerThread.start();
    this.mHandler = new MyServiceHandler(this.mHandlerThread.getLooper());
  }

  public int onStartCommand(final Intent intent, final int flags, final int startId) {
    if (intent != null) {
      super.onStartCommand(intent, flags, startId);
    }
    return START_NOT_STICKY;
  }

  private final class MyService Handler extends Handler
  {
    public MyServiceHandler(final Looper looper) {
      super(looper);
    }
    public void handleMessage(final Message message) {
      if (message != null) {
        switch (message.what) {
          case 2: {
            final String data = (String)message.obj;
            if (data != null) {
              MyService.this.startMessageProcess(data);
              return
            }
            break;
          }
          case 3: {
            MyService.this.sendData((byte[])message.obj);
          }
        }
      }
    }
  }
}

IMyService.java

public interface IMyService extends IInterface
{
  void start_data(final String p0) throws RemoteException;

  public abstract static class Stub extends Binder implements IMyService
  {

    public Stub() {
      this.attachInterface((IInterface)this, "com.my.service.IMyService");
    }

    public static IMyService asInterface(final IBinder binder) {
      if (binder == null) {
        return null;
      }
      final IInterface queryLocalInterface = binder.queryLocalInterface("com.my.service.IMyService");
      if (queryLocalInterface != null && queryLocalInterface instanceof IMyService) {
        return (IMyService)queryLocalInterface;
      }
      return new Proxy(binder);
    }

    public IBinder asBinder() {
      return (IBinder)this;
    }

    public boolean onTransact(final int code, final Parcel data, final Parcel reply, final int flags) throws RemoteException {
      switch (code) {
        case 1: {
          data.enforceInterface("com.my.service.IMyService");
          this.start_data(data.readString());
          reply.writeNoException();
          return true;
        }
        case 1000000000: {
          reply.writeString("com.my.service.IMyService");
          return true;
        }
        default: {
          return super.onTransact(n, data, reply, flags);
        }
      }
    }

    private static class Proxy implements IMyService
    {
      private IBinder mRemote;

      Proxy(final IBinder mRemote) {
        this.mRemote = mRemote;
      }

      public IBinder asBinder() {
        return this.mRemote;
      }

      public String getInterfaceDescriptor() {
        return "com.my.service.IMyService";
      }

      @Override
      public void start_data(final String data) throws RemoteException {
        final Parcel data = Parcel.obtain();
        final Parcel reply = Parcel.obtain();
        try {
          data.writeInterfaceToken("com.my.service.IMyService");
          data.writeString(data);
          this.mRemote.transact(1, data, reply, 0);
          reply.readException();
        }
        finally {
          reply.recycle();
          data.recycle();
        }
      }
    }
  }
}

IHelperService.java

public interface IHelperService extends IInterface
{
  // a bunch of method declarations here

  public abstract static class Stub extends Binder implements IHelperService
  {
    public Stub() {
      this.attachInterface((IInterface)this, "android.service.helper.IHelperService");
    }

    public static IHelperService asInterface(final IBinder binder) {
      if (binder == null) {
        if (queryLocalInterface != null && queryLocalInterface instanceof IHelperService) {
          return (IHelperService)queryLocalInterface;
        }
        return new Proxy(binder);
      }
    }

    public IBinder asBinder() {
      return (IBinder)this;
    }

    public boolean onTransact(int code, final Parcel data, final Parcle reply, int flags) throws RemoteException {
      switch (code) {
        // does a bunch of bunch of stuff here based on the incoming code            
      }
    }
  }

  private static class Proxy implements IHelperService
  {
    private IBinder mRemote;

    Proxy(final IBinder mRemote) {
      this.mRemote = mRemote;
    }

    @Override
    // a bunch of method definitions here, everything that was declared above
  }      
}

I'm having a hard time tracing who sends what to who. For example, in the Proxy of IMyService, start_data calls transact(mRemote), but who is mRemote? Also, in the MyService() constructor as well as startMessageProcess, there are calls to sendMessage, who is it sending to?

Then, there are private methods in MyService that don't seem to be called locally, such as startMessageProcess and getServerData. Who else can call these methods if they're private?

Onik
  • 19,396
  • 14
  • 68
  • 91
user1118764
  • 9,255
  • 18
  • 61
  • 113
  • where do you have this code from? IMyService & IHelperService seem to be an AIDL generated... why dont you use some basic Binder example? – pskink Apr 21 '16 at 05:32
  • It's decompiled from a java jar file. Yup, I think it's AIDL generated as well. Given this, how do I find out the data flow? – user1118764 Apr 21 '16 at 06:15
  • as i said: start from a basic Binder example (`transact` / `onTransact` calls) – pskink Apr 21 '16 at 06:35
  • I've read a bit more on AIDL generated java files. Looking at the code, it looks like Both MyService and HelperService are services, i.e. the server side of the IPC, and there doesn't seem to be any client side activities in this app. MyService only exposes a single method start_data() to clients so they can call IMyService.start_data() once the service is bound. MyService.onCreate() also checks for existence of HelperService, and later calls its methods in startMessageProcess(). However, how do the message handlers and parcel transactions come into play? – user1118764 Apr 21 '16 at 07:17
  • do you want to understand this particular piece of code or Binders in general? – pskink Apr 21 '16 at 07:19
  • I'd like to understand this piece of code. – user1118764 Apr 21 '16 at 08:06
  • but this is machine generated code..., also there is a bug in `IMyService.Stub#start_data(final String data)` as `data` parameter is never used, instead some `arg` is used which is not defined anywhere... – pskink Apr 21 '16 at 08:12
  • Sorry, that's a typo, arg should be data. Is there any way to tell the data flow from AIDL generated code? – user1118764 Apr 21 '16 at 08:18
  • sure: the real IPC is done like this: the client (most likely activity) calls `Binder#transact` method, the server (most likely service) overrides `Binder#onTransact` method, the rest is serialization / de-serialization based on `code` passed to `transact` / `onTransact` calls – pskink Apr 21 '16 at 08:24
  • Thanks. Really appreciate your help so far. So the overridden onTransact method is in IMyService.java, if the code is 1, it runs start_data(), i.e. if a client activity calls IMyService.transact() with a code of 1, it will run start_data(). Now,, here's where I get confused. The start_data() method is defined in both MyService (in the constructor) and IMyService. Which one gets run? Also, in the latter, there is another mRemote.transact() call. Who is mRemote? – user1118764 Apr 21 '16 at 08:36
  • this is serialization / de-serialization i was talking about, the client calls start_data which call transact(1, ...) which in turn calls onTransact(1, ...) on the remote side followed by start_data real implementation – pskink Apr 21 '16 at 08:48
  • I see! I'm assuming all this serialization/de-serialization is handled by the AIDL to java code generator? Bear with me, I'm trying to know exactly what handles where. Correct me if I'm wrong, the client calls IMyService.start_data(), which goes to IMyService#start_data(). In this method, it sets the data interfaceToken and string, then calls transact(1,...). This calls IMyService#onTransact(1,...), which goes to the "case 1:" section, which calls start_data() in IMyService again? Something doesn't seem right, wouldn't this keep looping between start_data() and onTransact then? – user1118764 Apr 21 '16 at 09:01
  • `start_data` is defined in `Stub` and in `Proxy`, the first is abstract and has to be implemented with your code, the second just calls `transact(1...)`, the best idea is to open android studio, create a new aidl interface and debug the sample code – pskink Apr 21 '16 at 09:15
  • Thanks. So if I'm understanding you correctly, in IMyService, start_data is declared in the Stub and Proxy of IMyService.java, and actually defined/implemented in MyService.java. So, I don't really need to see IMyService.java to understand the flow, because all the actual implementation is done in MyService.java? – user1118764 Apr 22 '16 at 02:27
  • yes, this is exactly how it works, just follow [this](http://developer.android.com/guide/components/aidl.html), it shows step by step how to implement your service ([this](http://developer.android.com/guide/components/aidl.html#Implement) link shows that directly) – pskink Apr 22 '16 at 03:46

0 Answers0