Tuesday, December 29, 2009

Adding a system server to the Android framework

Following our other posts about booting and the system server, here are some more details of how to add a service to the system server.

It should be noted that extending the system server and other framework features is not recommended, as the code requires to be ported to future releases of the framework.
The most common reason for adding a system server would be for supporting proprietary hardware.

Design considerations
There are a few questions to be answered before implementing a system server, such as threads needed, application interfaces, and hardware interfaces. It's no easy task specifying your server design, but considering the following will hopefully help you out:
1. How frequently will the server run? If the server is only needed occasionaly, it might very well run within the system server context, even though the most common way is give it its own thread. A typical infrequent server is the Headset observer, which will only run when a headset is connected or removed.
2. How is the hardware accessed? Standard hardware could be accessible through device file access and file observers, but the best solution is no doubt to implement a HAL library. The HAL makes it easier to port your server to other hardware, and also makes it possible to run some functionality in native threads if needed.
3. Application interfacing? It is sometimes possible to get away with using intents and implementing your server as a receiver, but for anything but the simplest requests, you will no doubt have to use aidl.
4. Extending the framework? If you want to make your new interface visible to the applications, you will have to update the api description and build your own sdk. (That is easily done with "make update-api", followed by "make sdk".) However, if you only want the your nifty features to be accessible from your own proprietary applications, you should make use of the javadoc @hide-option for your interface.
5. Is there another way? Adding your own server adds migration work for new releases of the framework. To minimise files added, it's easy to be tempted to alter existing services instead of adding your own. That is fine to do, but keep in mind that if some functionality changes, third-party applications may not work anymore. Again, if your server will only be accessed from a proprietary application and not publicly available, consider adding the code to the application instead.

Code sample
Following is an example of a proprietary service, that knows how to set a value. For simplicity, the code is just added to the framework. For a production implementation, the code should go into the vendor directory.

Specifying the interface.
This example uses aidl, so the first step is to add an interface definition file:
frameworks/base/core/java/android/os/IEneaService.aidl
package android.os;

interface IEneaService {
  /**
  * {@hide}
  */
  void setValue(int val);
}

The interface file will need to be added to the build system:
frameworks/base/Android.mk
Add the following around line 165 (the end of the list of SRC_FILES):
core/java/android/os/IEneaService.aidl \


Implementing the server
The service spawns a worker thread that will do all the work, as part of the system server process. Since the service is created by the system server, it will need to be located somewhere where the system server can find it.
frameworks/base/services/java/com/android/server/EneaService.java
package com.android.server;

import android.content.Context;
import android.os.Handler;
import android.os.IEneaService;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.util.Log;

public class EneaService extends IEneaService.Stub {
  private static final String TAG = "EneaService";
  private EneaWorkerThread mWorker;
  private EneaWorkerHandler mHandler;
  private Context mContext;

  public EneaService(Context context) {
    super();
    mContext = context;
    mWorker = new EneaWorkerThread("EneaServiceWorker");
    mWorker.start();
    Log.i(TAG, "Spawned worker thread");
  }

  public void setValue(int val)
  {
    Log.i(TAG, "setValue " + val);
    Message msg = Message.obtain();
    msg.what = EneaWorkerHandler.MESSAGE_SET;
    msg.arg1 = val;
    mHandler.sendMessage(msg);
  }

  private class EneaWorkerThread extends Thread{
    public EneaWorkerThread(String name) {
      super(name);
    }

    public void run() {
      Looper.prepare();
      mHandler = new EneaWorkerHandler();
      Looper.loop();
    }
  }

  private class EneaWorkerHandler extends Handler {
    private static final int MESSAGE_SET = 0;

    @Override
    public void handleMessage(Message msg) {
      try {
        if (msg.what == MESSAGE_SET) {
          Log.i(TAG, "set message received: " + msg.arg1);
        }
      } catch (Exception e) {
        // Log, don't crash!
        Log.e(TAG, "Exception in EneaWorkerHandler.handleMessage:", e);
      }
    }
  }
}

You may want to add to the log printouts the thread id or name just to visualise which thread the code is executing in.

All that's left to do now is to type make to build the repository, and then start up the emulator. Using logcat, you will find the message saying that the thread has been spawned.


Test program
You probably want to test your service as well. For this you can create a "Hello World" activity using the project wizard. Place the project in the vendor/enea directory.

In the main activity class of your test program, add the following lines to onCreate()
IEneaService em = IEneaService.Stub.asInterface(ServiceManager.getService("EneaService"));
try {
  em.setValue(7); // Any value would do, really.
} catch (Exception e) {
  e.printStackTrace();
}

You will also need to import android.os.IEneaService and of course add an apropriate Android.mk to build your test program.

Start your test program and look in the log. You should now find the "set value"-messages.

Cheers
Robert

13 comments:

  1. Wonderful tutorial.

    A perfect treat for new year..!

    ReplyDelete
  2. I am trying add the framework in a vendor directory, but I cannot figure out how to organize the files and what the Android.mk files should look like. Could you give an example of how to do this?

    Could you also give an example of what an "appropriate Android.mk" would look like for the test program?

    Thanks,
    Andrew

    ReplyDelete
  3. hey...ServiceManager is not exposed in the api..build fails...did you make any other change

    ReplyDelete
  4. was not able to build the test app....can you help out

    error is :

    cannot resolve the imports aidl file and servicemanager

    ReplyDelete
  5. Yes Service Manager is not exposed. How do we work-around? I am developing with Eclipse. Please help.

    ReplyDelete
  6. Hello Immmling,
    The code sample will only work when building the platform, not an app. Note the packages being used.
    I would guess that you have Eclipse set up for app development using the SDK, thus no internal classes and methods are visible.

    Regards,
    Robert

    ReplyDelete
  7. What (phone shell tools) will reveal widgets that have employed hide?

    ReplyDelete
  8. (for the purpose of disabling unwanted "features")

    ReplyDelete
  9. don't we need to register this service with system server(.java)??

    How "ServiceManager.getService("EneaService")" gonna work...

    I'm bit confused....

    ReplyDelete
  10. This comment has been removed by the author.

    ReplyDelete
  11. I figured it out.
    Go to - frameworks/base/services/java/com/android/server/SystemServer.java
    Go to Line
    if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
    For me its line 239 so it should be some where around it.
    Add the following code.


    try {
    Slog.i(TAG, "EneaService");
    ServiceManager.addService("EneaService", new EneaService(context));
    }
    catch (Throwable e)
    {
    Slog.e(TAG, "Failure starting IEneaService Service", e);
    }

    Cheers- BK

    ReplyDelete
  12. does anyone know why we add this code that Bhanu mentioned in that if clause? i think the if clause is for bluetooth but our service has nothing to do with bluetooth.

    ReplyDelete
  13. how can i run this system service without help of any application?
    means after boot up, this system service starts running.

    how can i do it??
    please mail me at piyush110393@gmail.com

    ReplyDelete