Android Binder Tutorial [Part Four]: Obtaining and using a Native Service.

Introduction

In this chapter, we will try to understand how a native service is obtained and used. We will focus on the MediaPlayerService and call its methods. 
We will take the usecase of MIRACAST/WiFiDisplay to begin our analysis.
 WifiDisplayController.java is the java interface for all Applications to start or listen to a remote session and the begining point of our analysis.


In the initial setup , The RemoteDisplay.java is used to listen to active incoming connections for Miracast[WifiDisplay]. This in turn through the JNI interface calls the nativeListen in android_media_RemoteDisplay.cpp.



Here we begin our journey of interacting with the MediaPlayerService and using its methods.

Here we first obtain a BpServiceManager object via defaultServiceManager(), then call its getService method and construct a BpMeidaPlayerService from its return. The previous analysis of interface_cast<IMediaPlayerService>, which calls the asInterface() function, expands as follows.

Android::sp<IMediaPlayerService> IMediaPlayerService::asInterface(                    
        Const  android::sp<android::IBinder>& obj)                      
{                                                                      
    Android::sp<IMediaPlayerService> intr;                                    
    If  (obj != NULL) {                                                  
Intr         =  static_cast <IMediaPlayerService*> (                             
            Obj->queryLocalInterface(                                  
                    IMediaPlayerService::descriptor).get());                  
        If  (intr == NULL) {                                            
 Intr             =  new BpMediaPlayerService(obj);                             
        }                                                               
    }                                                                  
    Return  intr;                                                       
}  

The parameter obj is the return of the above call sm->getService(String16("media.player")), Let us first look at getService.



The getService method calls the checkService method five times to obtain the MediaPlayerService to prevent the MediaPlayerService from registering before the client calls. 
In checkService(), We first write the strict mode and "android.os.IServiceManager" to Parcel and then write "media.player". Finally, call remote()->transact to send the data. We analyzed earlier, where remote() is BpBinder(0) and eventually calls the transact method of IPCThreadState. 
In the transact method, the data in the above data Parcel is first written into outParcel and then talkWithDriver() is called to send the data to the binder driver.




The data sent to the binder driver is as follows.



The binder driver first calls binder_thread_write to process the write request. In the binder_thread_write function, the BC_TRANSACTION is called and the binder_transaction method is called.




Above is just a snapshot for reference , Please check binder.c [binder_driver] in the AOSP code [too big to be snapped and put here].

Analyzing the binder_transaction code in driver we see that target_node, target_proc, target_list, and target_wait are all processes that the ServiceManager is in. 
We then create a new binder_transaction data structure, t, and set to_proc and to_thread to ServiceManager.
We set transaction code[0 t->code] is equal to CHECK_SERVICE_TRANSACTION. Then apply binder_buffer, and copy the data pointed to by the buffer pointer in the tr [Transaction Reply] to the binder_buffer. 
We set the from_parent of t to the current thread's transaction_stack and add t to the current thread's transaction_stack. 
Finally, t is added to the todo list of the thread where the ServiceManager is located, and tcomplete is added to the current thread's todo list.
BR_TRANSACTION_COMPLETE instructions are filled and copied to the user space. 
We then look at the ServiceManager process CHECK_SERVICE_TRANSACTION where we first call binder_parse to parse the command, and then call svcmgr_handler to handle the CHECK_SERVICE_TRANSACTION command.

Finally do_find_service is used to find the service requested.


We first find media.player's svcinfo via find_svc and return its ptr pointer. By registering the knowledge of the Service, we know that the ptr pointer here actually points to the value of the handle id of the previously registered Service. Then we call bio_put_ref(reply, ptr) to write the obtained ptr to reply.



Here we first find the previously registered binder_ref structure through the handle id, and then through the binder_ref node variable we can find the binder_node structure.
In the binder_node there is only the MediaplayerService object we registered. Here we first determine ref->node->proc == target_proc i.e if the registration process is the same as the process that currently obtains the MediaPlayerService.
If so, rewrite fp->type to BINDER_TYPE_BINDER and set fp->binders to binder- >getWeakRefs(),fp->cookie is equal to binder->local itself. 
If it is not in the same process (this is the most happening case, When some other process wants to use the service ), We first call 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 .

 PLEASE SEE binder_transaction() in the driver code for more details.

After this the BC_REPLY is formed and sent to the caller. This arrives as a Parcel and it is unmarshalled for the reply to be read.

This reply is obtained by readStrongBinder() as below.





As we know
when in different processes, type is equal to BINDER_TYPE_HANDLE.
when in the same process, type is equal to BINDER_TYPE_BINDER. 
In our current example it is a inter-process call that is not in the same process.
Let's take a look at Process's getStrongProxyForHandle method.



If there was a method called MediaPlayerService in front then there will be a handle_entry which will be returned by lookupHandleLocked().
If this is the first call. We will construct a new handle_entry, and its binder is set to NULL, and then return. 
In ourcase the handle id here is not NULL so a BpBinder(handle) object is constructed and returned.
Return to the initial IMediaPlayerService asInterface method.
  




The obj here is BpBinder(handle). Obviously its queryLocalInterface method returns NULL, so here we construct a BpMediaPlayerService(BpBinder(handle)) and return it. So the service in the nativeListen function is equal to BpMediaPlayerService(BpBinder(handle)).


Calling Native Service

Calling the service->listenForRemoteDisplay method in front is equivalent to calling BpMediaPlayerService(BpBinder(handle))->listenForRemoteDisplay().
Let's first look at the listenForRemoteDisplay method of BpMediaPlayerService.
  


So we again do the following.
  1. write the strict mode and "android.media.IMediaPlayerService" string to Parcel
  2. write the two parameters to data. 
  3. Calling the remote()->transact method is actually calling the BpBinder(handle)->transact() method.



The mHandle here is the handle id and the cmd code is LISTEN_FOR_REMOTE_DISPLAY. Then we call the transact method of IPCThreadState.
We will skip this as we have seen that this will call the binder_transaction() method as earlier.

The code here is roughly the same as the procedure for registering a Sevice which we saw in previous tutorials.




IPCThreadState obtains the above data through the talkWithDriver() method and calls executeCommand to process the BR_TRANSACTION command.




Here we first construct a Parcel object through the buffer of the binder_transaction_data, then save the mCallingPid and mCallingUid and set them to the new sender_pid and sender_uid. 
Then we get the MediaPlayerService object stored in the tr.cookie and call its transact method, where transact is implemented in BBinder.


The implementation of onTransact in BnMediaPlayerService is shown below.



We construct a BpRemoteDisplayClient object by reading the value of the handle id of RemoteDisplayClient in data, and then read out the iface value. Then we call the listenForRemoteDisplay() method of MediaPlayerService. 

We write the RemoteDisplay() object created above to the reply Parcel and return to executeCommand to call the sendReply method to return the RemoteDisplay object to the calling process.


We call writeTransactionData() here to write the data in reply to mOut. Then we call waitForResponse () to send the data to the binder driver. 
The two parameters here are NULL, which means that there is no need to wait for BR_REPLY. After receiving BR_TRANSACTION_COMPLETE, it will exit directly. 

FOR THE REPLY PATH WE USE THE SAME SEQUENCE AS DESCRIBED ABOVE [I am too tired to write / repeat the same].



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