Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to call an Android remote service (IPC) from a Widget / local service?

I'm trying to remote control a live wallpaper from a widget. They're in the same APK, but obviously different processes. Calling an "activity" of the live wallpaper is of little use to me since it is a different process. The widget has simple buttons that, when pressed,

So what (I think) I need is IPC and AIDL.

First I created the AIDL on the wallpaper side, which worked fine. It has three methods with no extra parameters. But when I added the clientside to the widget, I got an error telling me that I cannot bind to that remote interface because the widget is already a BroadcastListener. I tried getting button handling in without needing the Widget to be a BroadcastListener, but that seems to be impossible.

Well no problem, right? I just created a service within the widget that binds to the remote interface, because while the widget is a BroadcastListener, the service is not, and everything should be fine.

Or so I thought.

Well, I'm getting the widget's buttons to trigger the widget service. Binding to the remote service yields me the following warning:

Unable to start service Intent (act=com.blabla.IRemoteService): not found.

I am using getApplicationContext() within the service of the widget to bind to the remote stuff. I do have the widget service in the manifest, but I don't have the remote service in there. When I do put it in there, I get a nonspecific InstantiationException.

In the Widget's Service onStart() I am doing this:

getApplicationContext().bindService(new Intent(IWallpaperRemote.class.getName()), 
  mConnection, Context.BIND_AUTO_CREATE);

I also have...

private ServiceConnection mConnection = new ServiceConnection() {
    public void onServiceConnected(ComponentName className,
            IBinder service) {
        mService = IWallpaperRemote.Stub.asInterface(service);
        isBound = true;
        Log.i("WidgetServiceConnection", "binding to service succeeded");
    }

    public void onServiceDisconnected(ComponentName className) {
        mService = null;
        isBound = false;
        Log.i("WidgetServiceConnection", "binding to service lost!");
    }
};

My question is this: Has anyone ever successfully done a remote call from a widget into another application? Considering I am talking about a live wallpaper here, and the fact that I'm not interested in calling an activity within the widget process but cause function calls within the live wallpaper, what options do I have other than IPC, if any?

And if IPC is the way to go here, what am I doing wrong?

like image 484
Toumal Avatar asked Jan 11 '11 17:01

Toumal


1 Answers

I found the answer to my own question. To make things easier for others, here's the solution:

When doing a remote service, one has to write the AIDL which will be compiled into a sort of stub interface, the implementation of that interface (i.e. the code that is executed when someone calls the remote methods), and a class that extends "Service" which returns the implementation class in the onBind() method. (A normal local service would return null in that method)

Now what I had not understood is that you MUST have a service definition in the manifest - WITH INTENT FILTER!

Let's say your AIDL is called IRemoteService.aidl, then you have a class called RemoteService which looks like this:

public class RemoteService extends Service {
  public IBinder onBind(Intent intent) {
    Log.i("RemoteService", "onBind() called");
    return new RemoteServiceImpl();
  }
  /**
   * The IRemoteInterface is defined through IDL
   */
  public class RemoteServiceImpl extends IRemoteService.Stub {
    public void remoteDetonateBirthdayCake() throws RemoteException {
        //your code here
    }
  };
}

In your android manifest, you want this:

<service android:name="RemoteService"> 
    <intent-filter>
        <action android:name="com.sofurry.favorites.IRemoteService"></action>
</intent-filter>
</service>

Note the service name: It's "RemoteService", not "IRemoteService" or even "RemoteServiceImpl". You need the name of the class that extends "Service", whose onBind method we overrode.

To complete the thing, here's the code on the client side -and yes this code also works from within another service, for example one you started from your widget ;)

IRemoteService mService;
RemoteServiceConnection mConnection = new RemoteServiceConnection();
getApplicationContext().bindService(new Intent(IRemoteService.class.getName()), mConnection, Context.BIND_AUTO_CREATE);

...where RemoteServiceConnection can be an inner class like so:

class RemoteServiceConnection implements ServiceConnection {
    public void onServiceConnected(ComponentName className, 
        IBinder service ) {
        mService = IRemoteService.Stub.asInterface(service);
        isBound = true;
    }

    public void onServiceDisconnected(ComponentName className) {
        mService = null;
        isBound = false;
    }
};

And now, you're free to call..

mService.remoteDetonateBirthdayCake();

In summary: Be sure to have a service stanza in the android manifest, set "name" to the class that returns the actual implementation in its onBind() method, and you must also have an intent filter with an action definiton that points to the AIDL interface.

Hint: If you are calling remote services from an app inside a different APK, add a "category" element to the intent filter too, and set it to DEFAULT.

like image 157
Toumal Avatar answered Oct 01 '22 16:10

Toumal