Android Binder Tutorial [Part Six]: Obtaining and using Java service.

Introduction.



We previously introduced the process of registering Java Service. In this tutorial we look at how the application obtains and makes calls to the Java Service interface.

AIDL.
In the Java Services, the AIDL tool is often used. The explanation of AIDL in the google official document is: " AIDL (Android Interface Definition Language)" is similar to other IDLs you might have worked with.  It allows you to define the programming interface that both .The client and service agree upon in order to communicate with each other using interprocess communication (IPC).
That is, AIDL is an interface description language used to define interfaces that are mutually agreed upon by client and service. It sounds more circumstantial and directly looks at the general contents of IWifiManger.aidl.


 This defines a series of function interfaces. An AIDL, in addition to Java primitive data (int, long, char, boolean, etc.), it also supports String, CharSequence, list, and map. 
In the conversion of the AIDL tool, you can generate a Java file from the above aidl file. Its contents are as follows.

################################################################
public interface IWifiManager extends android.os.IInterface { 
    public static abstract class Stub extends android.os.Binder implements 
            android.net.wifi.IWifiManager { 
        private static final java.lang.String DESCRIPTOR = "android.net.wifi.IWifiManager"; 
 
        public Stub() { 
            this.attachInterface(this, DESCRIPTOR); 
        } 
 
        public static android.net.wifi.IWifiManager asInterface( 
                android.os.IBinder obj) { 
            if ((obj == null)) { 
                return null; 
            } 
            android.os.IInterface iin = (android.os.IInterface) obj 
                    .queryLocalInterface(DESCRIPTOR); 
            if (((iin != null) && (iin instanceof android.net.wifi.IWifiManager))) { 
                return ((android.net.wifi.IWifiManager) iin); 
            } 
            return new android.net.wifi.IWifiManager.Stub.Proxy(obj); 
        } 
 
        public android.os.IBinder asBinder() { 
            return this; 
        } 
 
        @Override 
        public boolean onTransact(int code, android.os.Parcel data, 
                android.os.Parcel reply, int flags) 
                throws android.os.RemoteException { 
            switch (code) { 
         
            } 
            } 
            return super.onTransact(code, data, reply, flags);  
        } 
 
        private static class Proxy implements android.net.wifi.IWifiManager { 
            private android.os.IBinder mRemote; 
 
            Proxy(android.os.IBinder remote) { 
                mRemote = remote; 
            } 
 
            public android.os.IBinder asBinder() { 
                return mRemote; 
            } 
 
            public java.lang.String getInterfaceDescriptor() { 
                return DESCRIPTOR; 
            } 
        } 
 #########################################################

IWifiManager.java mainly contains three classes viz:
IWifiManager class,
IWifiManager.Stub class,
IWifiManager.Stub.Proxy class.
WifiServiceImpl is inherited from IWifiManager.Stub class. 
We have seen the relationship between it before, the following look at the general method to obtain the WifiService.





So, If we were to re-understand the above. From SystemServiceRegistry.java.


This calls the getService() method of ServiceManager. WIFI_SERVICE is the "wifi" string.



sCache is used to cache all used Java Service. It has ActivityManagerService copied in the fork process. We will look at sCache again when we analyze ActivityManagerService.

Here we assume that sCache does not have the "wifi" service we want. Then it calls the getService method of getIServiceManager(). 

In the previous analysis of getIServiceManager() we have seen it actually returns ServiceManagerNative (BinderNative), so it calls the ServiceManagerNative getService method here.





We first get two Parcel objects, then write data to Strict mode and "android.os.IServiceManager", and then write the service name you want to get, that is "wifi". 
Then call mRemote.transact() method, the implementation of this in android_util_Binder.cpp.
We already analyzed in the previous tutorial, here we directly call BpBinder (0) transact method, the GET_SERVICE_TRANSACTION command sent to the binder driver.
Let's see how the Java layer reads the desired Servcie from Parcel.



In the previous tutorial of the Native Service, we said that the binder driver first determines ref->node->proc == target_proc, if that is true (the registered process is the same as the process that obtains the MediaPlayerService). Then it rewrite fp-> Type is BINDER_TYPE_BINDER, and fp->binder is set to binder->getWeakRefs(), and fp->cookie is equal to binder->local itself. 

If it is not in the same process (this is the most usual case of IPC), It first calls binder_get_ref_for_node() to allocate a new binder_ref structure for the process that gets MediaPlayerService, where the binder id value may not be the previously registered binder id value. 
Let us first look at Parcel's readStrongBinder method of the Native layer.



There are two cases here.

First, the process of registering the WifiService and the process of acquiring the WifiService are the same (this is often the case, because the registered WifiService is in the systemserver, and the systemserver will register many other services. These services will call each other). 
At this time the type is BINDER_TYPE_BINDER, and then through static_cast the pointer stored in the cookie is directly converted to the previously registered JavaBBinder().

Second, the process of registering WifiSerivce and the process of obtaining WifiService is not the same; here call getStrongProxyForHandle to return a BpBinder (handle id). 
Back to the android_os_Parcel_readStrongBinder() method above and we call javaObjectForIBinder() to do a layer encapsulation of the IBinder object.



The function was analyzed in the previous tutorial.
Here a BinderProxy object is constructed and the current IBinder (BpBinder) is bound to BinderProxy. Then it returns the BinderProxy object to the process that obtained the Service. Then calls IWifiManager service = IWifiManager.Stub.asInterface(b), which is implemented as follows.


public static android.net.wifi.IWifiManager asInterface( 
        android.os.IBinder obj) { 
    if ((obj == null)) { 
        return null; 
    } 
    android.os.IInterface iin = (android.os.IInterface) obj 
            .queryLocalInterface(DESCRIPTOR); 
    if (((iin != null) && (iin instanceof android.net.wifi.IWifiManager))) { 
        return ((android.net.wifi.IWifiManager) iin); 
    } 
    return new android.net.wifi.IWifiManager.Stub.Proxy(obj); 
}  

Here obj is a BinderProxy object whose queryLocalInterface defaults to NULL, so here an IWifiManager.Stub.Proxy is constructed and returned. Finally, a WifiManager object is constructed for the application layer through IWifiManager.Stub.Proxy.


Java Service call

Let's take a look at a simple API (setWifiApEnabled) usage in WifiManager:


The mService here is the IWifiManager.Stub.Proxy object constructed above, Let us see its setWifiApEnabled method.
public boolean setWifiApEnabled( 
        android.net.wifi.WifiConfiguration wifiConfig, 
        boolean enable) throws android.os.RemoteException { 
    android.os.Parcel _data = android.os.Parcel.obtain(); 
    android.os.Parcel _reply = android.os.Parcel.obtain(); 
    boolean _result; 
    try { 
        _data.writeInterfaceToken(DESCRIPTOR); 
        if ((wifiConfig != null)) { 
            _data.writeInt(1); 
            wifiConfig.writeToParcel(_data, 0); 
        } else { 
            _data.writeInt(0); 
        } 
        _data.writeInt(((enable) ? (1) : (0))); 
        mRemote.transact(Stub.TRANSACTION_setWifiApEnabled, _data, 
                _reply, 0); 
        _reply.readException(); 
        _result = (0 != _reply.readInt()); 
    } finally { 
        _reply.recycle(); 
        _data.recycle(); 
    } 
    return _result; 

The step first is to get two Parcel objects, one for storing data sent to the WifiService and one for getting the WifiService reply. 
Write strict mode and "android.net.wifi.IWifiManager" to data and write two more parameters. 
Finally call the mRemote.transact method. We know that mRemote is the previously constructed BinderProxy object, so let us look at its transact method, which is implemented in android_util_Binder.cpp.


There are two situations to discuss here.
One is in the same process, where the target is JavaBBinder; if in different processes, the target here is BpBinder. 
First let us look at the case of the same process, we call the JavaTransactor's transact method, because JavaBBinder is inherited from the BBinder, so directly to the Transact method of the BBinder.


Directly call the onTransact method, of course, the onTransact method is re-written after the JavaBBinder. 
When called in the same process, target is BpBinder, so BpBinder's transact method is called, and finally send the command to the JavaBBinder (the WifiService's wrapper) that is registered in the binder driver by ioctl.
It will still call the onTransact method of JavaBBinder. A cross-process data transfer process.

Here is the main call to Binder.java execTransact to handle the request.




This directly calls the onTransact method of WIfiService. Its implementation is in IWifiManager.Stub.

#############
public boolean onTransact(int code, android.os.Parcel data, 
        android.os.Parcel reply, int flags) 
        throws android.os.RemoteException { 
    switch (code) { 
 
    case TRANSACTION_setWifiApEnabled: { 
        data.enforceInterface(DESCRIPTOR); 
        android.net.wifi.WifiConfiguration _arg0; 
        if ((0 != data.readInt())) { 
            _arg0 = android.net.wifi.WifiConfiguration.CREATOR 
                    .createFromParcel(data); 
        } else { 
            _arg0 = null; 
        } 
        boolean _arg1; 
        _arg1 = (0 != data.readInt()); 
        boolean _result = this.setWifiApEnabled(_arg0, _arg1); 
        reply.writeNoException(); 
        reply.writeInt(((_result) ? (1) : (0))); 
        return true; 
    } 
################

Finally the actual implementation of setWifiApEnabled() is called.



This concludes our six part tutorial on basic Android Binder usage.


Comments

Popular posts from this blog

Android Audio Tutorial [Part Three] : AudioFlinger Introduction and Initialization

Android External Storage Support: Volume Daemon (vold) Architecture

Android Audio Tutorial [Part One] : Introduction