0

I have a Service which is already bound by an external App via AIDL.

However, there are some service requests which require to start an Activity. Since I cannot call startActivityForResult from within a Service I decided to Bind my local Activities to the service as well.

(PseudoCode) looks like this:

class MyService extends Service{
    public IBinder onBind(Intent intent){
        if (intent.hasExtra("LocalBindingRequest")){
            return getLocalBinder();
        else {
           return getAidlBinder();
        }
    }
}

class ExternalApp extends Activity{
    void someFunc(){
        Intent i = new Intent(new ComponentName("com.my.pkg", "com.my.pkg.MyService");
        bindService(i, myServiceConnection, Context.BIND_AUTO_CREATE);
    }
}

class InternalApp extends Activity{
    MyService mService;

    void someFunc(){
        Intent i = new Intent(new ComponentName("com.my.pkg", "com.my.pkg.MyService")
           .putExtra("LocalBindingRequest", true);
        bindService(i, myServiceConnection, Context.BIND_AUTO_CREATE);
    }

    public void onServiceConnected(ComponentName cn, IBinder service){
        InternalBinder ib = (LocalBinder)service;
        mService = ib.getService();

    }
}

Flow is like this:

  • ExternalApp binds to AidlBinder
  • ExternalApp calls Function which requires Service to start an Activity
  • Service starts Activity
  • Internal Activity tries to Bind
  • I get an Exception (appearantly without hitting a breakpoint in onBind or onServiceConnected)

java.lan.ClassCastException: AidlService cannot be cast to InternalBinder


Isn't it possible for a Service to return a different Binder?

If not, what can I do, to propagate a Result back to MyService which is already bound?

Rafael T
  • 15,401
  • 15
  • 83
  • 144

2 Answers2

2

Ok I should have read the docs stating in onBind(Intent)

Intent: The Intent that was used to bind to this service, as given to Context.bindService. Note that any extras that were included with the Intent at that point will not be seen here.

Thats why I was given the Aidl Service. The fix would be:

class InternalApp extends Activity{
    MyService mService;

    void someFunc(){
        Intent i = new Intent(new ComponentName("com.my.pkg", "com.my.pkg.MyService");
        i.setAction("LocalBindingRequest");
        bindService(i, myServiceConnection, Context.BIND_AUTO_CREATE);
    }

    public void onServiceConnected(ComponentName cn, IBinder service){
        InternalBinder ib = (LocalBinder)service;
        mService = ib.getService();

    }
}


class MyService extends Service{
    public IBinder onBind(Intent intent){
        if ("LocalBindingRequest".equals(intent.getAction()){
            return getLocalBinder();
        else {
           return getAidlBinder();
        }
    }
}

And we can have separate Binders for each binding request

Rafael T
  • 15,401
  • 15
  • 83
  • 144
0

Question was already answered but I'd like to provide an example of how to accomplish this with Messenger's binder instead of AIDL.

It's possible to make the Binder you return in the Service's onBind() adaptable and dynamic to whether the client that connected to the service was a component from another process/app (in which case we'd want to return to them the Messenger's binder to establish IPC), or if the client that connected to the service was a component from within our own process/app (in which case we'd want to return a custom Binder object that has a reference to the service).

All we need to do is to specify an action with the intents that we use to bind the service from either client, and in the service, we filter for that action, and return a Binder depending on what the intent's action is.

First we declare MyService in the manifest with the following intent filters:

<service
    android:name=".MyService"
    android:exported="true">
    <intent-filter>
        <action android:name="foreignProcess" />
        <action android:name="localComponent" />
    </intent-filter>
</service>

Then in MyService, we declare, we make sure to return different Binders in onBind() based on the intent's action:

class MyService: Service() {

    private var clientMessenger: Messenger? = null

    private val incomingHandler = object : Handler(Looper.getMainLooper()) {
        override fun handleMessage(msgFromClient: Message) {
            super.handleMessage(msgFromClient)
            clientMessenger = msgFromClient.replyTo
            val receivedBundle = msgFromClient.data
         }
    }

    // public method that we can call from Activities/Fragments, etc that bind to our service.
    fun doStuff() {
    }

    // depending on the action of the intent that was used to bind to the service, we return the appropriate Binder.
    override fun onBind(intent: Intent?): IBinder {
        return when (intent?.action) {
            "localComponent" -> MyBinder()
            "foreignProcess" -> Messenger(incomingHandler).binder
            else -> Messenger(incomingHandler).binder
        }
    }

    inner class MyBinder : Binder() {
        val service: MyService
            get() = this@MyService
    }
}

Now in the component (like an Activity or Fragment) that exists within our own app (i.e. exists in the same process with the service), we create an intent with the action "localComponent" and use that to bind to the service:

fun bindToServiceThroughMyLocalComponent(context: Context) {
    val conn = object: ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
            val myService = (binder as MyService.MyBinder).service
            myService.doStuff()
        }
        override fun onServiceDisconnected(name: ComponentName?) {}
    }

    val intent = Intent(context, MyService::class.java).apply {
        setAction("localComponent")
    }

    context.bindService(intent, conn, Context.BIND_AUTO_CREATE)
}

And in the client app (which exists in a different process), we create an intent with the action "foreignProcess" and use it to bind to the service:

fun bindToServiceThroughForeignProcess(context: Context) {
    val incomingHandler = object: Handler(Looper.getMainLooper()) {
        override fun handleMessage(msgFromService: Message) {
            super.handleMessage(msgFromService)
            val receivedBundle = msgFromService.data
        }
    }

    val conn = object: ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
            val clientMessenger = Messenger(incomingHandler)
            val clientMessage = Message.obtain(null, 0, clientMessenger)
            val bundle = Bundle()
            bundle.putString("message", "hello world")
            clientMessage.data = bundle
            clientMessage.replyTo = clientMessenger // pass our clientMessenger to the clientMessage to establish IPC
            Messenger(binder).send(clientMessage)
        }
        override fun onServiceDisconnected(name: ComponentName?) {}
    }
    val intent = Intent("foreignProcess")
    context.bindService(intent, conn, Context.BIND_AUTO_CREATE)
}

Now our Service's onBind() returns different Binders depending on what the different clients specified in their intent's actions.

M.Ed
  • 969
  • 10
  • 12