Skip to content
Adnan Begovic edited this page Jun 1, 2016 · 2 revisions

Setting up to Contribute

Once you've completed a full build for your device, start a branch under vendor/cmsdk/ via

repo start local-dev-branch vendor/cmsdk

This local-dev-branch will be your source control via git, you can stash, add, commit, rebase locally prior to uploading your changes.

Reference the CyanogenMod wiki's build guides for your device for a brief introduction to how to set up your build environment.

Reference the CyanogenMod gerrit guide for a brief introduction on setting up your gerrit account, and an overview of retrieving or submitting changes up for review.

Contribution Guidelines

Prior to contributing, reference the links below for an introduction on best practices.

Understanding the structure

The CMSDK is built from the core binder service and manager relationship within Android.

Android's ServiceManager acts as a informational director and index of every core binder service within the OS. The internal map of services can be used to retrieve IBinder reference of a system service which was kicked off during the init of SystemServer.

What's beneficial of Android's ServiceManager implementation, which itself was a consequence of avoiding the 64k dex limit, is that you have the ability to start services external to the core framework jar. This is accomplished via reflection in the SystemServiceManager#startService.

Overview

Implementation like the WIFI_SERVICE which extends the base class CMSystemService, utilize this service start to publish an internal service back to ServiceManager as Binder Services via SystemService#publishBinderService.

This allows any 3rd party to retrieve the binder service via ServiceManager#getService, and interact with it as they please while the OS handles the system services lifecycle.

The CMSDK follows a similar model to the WIFI_SERVICE, where the external service specific to the org.cyanogenmod.platform jar are started by the system server. These services are overlaid during compile time as a string-array resource.

For further details on Android's IPC framework, see this wonderful presentation by Jim Huang.

Including your System Binder Service

The CM SDK was set up to be as simple as possible when needing to include new functionality. The approach should be one where interface AIDL's are shared between the sdk available to 3rd parties and the platform library.

First, create a Service which extends CMSystemService within the library project cm/lib/main/java/ under the org.cyanogenmod.platform.internal namespace.

    public DummyExampleService extends CMSystemService {
        public DummyExampleService(Context context) {
            super(context);
        }
    }

Next, contribute the specific namespace for the service to the CM vendor and provide the sepolicy changes to make sure your service is the right context.

This service will be the one that's started via CMSystemServer, however it will not be the service which ends up being published as a binder service. For that you'll have to define a stub implementation.

A simple example for an interface to be shared between processes is shown below. This interface AIDL should be created within the cyanogenmod (sdk/src/java/cyanogenmod) namespace as it will be shared with the SDK as well as the platform library.

    /** @hide */
    interface IDummyManager {
        void say(String something);
    }

Now, declare an internal binder service within your CMSystemService which extends the interface AIDL stub and override your declared binder methods.

    public DummyExampleService extends CMSystemService {
        public DummyExampleService(Context context) {
            super(context);
        }

        @Override
        public void onStart() {
            publishBinderService("dummyservice", mService);
        }

        private final IBinder mService = new IDummyManager.Stub() {
            @Override
            public void say(String something) {
                System.out.println("Say " + something);
            );
        };
    }

Remember to publish your internal binder service via #publishBinderService, with a unique key which we can fetch the service with later on.

Now that your internal service within the platform library knows how to react to say(String something), we need to create the abstraction necessary for a 3rd party application to be able to utilize the defined interface manager.

So within the cyanogenmod.* namespace, create a Manager class for your interface AIDL.

    import cyanogenmod.app.IDummyManager;
    
    public class DummyManager {
        private static IDummyManager sService;
        private static DummyManager sDummyManagerInstance;

        private DummyManager(Context context) {
            Context appContext = context.getApplicationContext();
            if (appContext != null) {
                mContext = appContext;
            } else {
                mContext = context;
            }
            sService = getService();
        }

        public static DummyManager getInstance(Context context) {
            if (sDummyManagerInstance == null) {
                sDummyManagerInstance = new DummyManager(context);
            }
            return sDummyManagerInstance;
        }

        public void saySomething(String something) {
            if (sService == null) {
                //Not connected
                return;
            }

            try {
                sService.say(something);
            } catch (RemoteException e) {
                //Unable to get service, fail
            }
        }

        /** @hide */
        public IDummyManager getService() {
            if (sService != null) {
                return sService;
            }
            IBinder b = ServiceManager.getService("dummyservice");
            sService = IDummyManager.Stub.asInterface(b);
            return sService;
        }
    }

Now, recompile and push the platform library via mmmp -B vendor/cmsdk

Reboot your device, run adb shell service list to verify that your service is available from the OS.

Compile the sdk library, and include it in an android application project, you should now be able to access the system service.

Handling Custom Objects and IPC calls

Since we need to maintain compatibility with respect to variances in CMSDK vs CM Framework versions, and we can't rely on runtime stubs like AOSP, we've adopted a Concierge concept for any parcels which are sent over IPC. The Concierge provides a header that is fairly straightforward and is generously borrowed from the implementation in DashClock.

A quick example for a Dummy object is shown below:

public class DummyObject implements Parcelable {

    private String someMember;

    /**
     * Unflatten the DummyObject from a parcel.
     */
    public DummyObject(Parcel parcel) {
        // Read parcelable version via the Concierge
        ParcelInfo parcelInfo = Concierge.receiveParcel(parcel);
        int parcelableVersion = parcelInfo.getParcelVersion();

        // Pattern here is that all new members should be added to the end of
        // the writeToParcel method. Then we step through each version, until the latest
        // API release to help unravel this parcel
        if (parcelableVersion >= Build.CM_VERSION_CODES.APRICOT) {
            if (parcel.readInt() != 0) {
                this.someMember = parcel.readString();
            }
        }

        // Complete parcel info for the concierge
        parcelInfo.complete();
    }

   @Override
    public void writeToParcel(Parcel out, int flags) {
        // Tell the concierge to prepare the parcel
        ParcelInfo parcelInfo = Concierge.prepareParcel(out);

        // ==== APRICOT =====
        if (someMember != null) {
            out.writeInt(1);
            out.writeString(someMember);
        } else {
            out.writeInt(0);
        }

        // Complete the parcel info for the concierge
        parcelInfo.complete();
    }
}

Providing Tests and viewing code coverage

To see best practices for providing tests and viewing code coverage, see the Testing Guidelines wiki: Testing guidelines

Updating & Verifying the API

The CyanogenMod build system now contains means of verifying the current API vs the release via the internal checkapi mechanism in the Android tree.

To run checkapi for the CyanogenMod framework & sdk:

make checkapi-cm

This will compare the generated api text from the current stubs in $OUT against the latest release api text in prebuilts/cmsdk. If there is any variance, the verification will fail.

If you do however want to modify the underlying API and method signatures for the platform, then you need to run the command to update the CyanogenMod framework api:

make update-cm-api

This will generate cm_current.txt, which is a api text file based off the stubs in the $OUT directory. Once you've made these changes, and generated the api text, you need to then further prepare them for release.

Contributing docs

Documentation updates have mostly been automated. While on the current branch in the CM SDK project, from the root android build directory, run a make for documentation on the sdk.

Run make org.cyanogenmod.platform.sdk-docs

Next, switch the active branch to gh-pages in the CM SDK project, copy in the new docs from $OUT and create a new commit. Upload the change to gerrit for review underneath the remote gh-pages branch.