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 .
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.
- write the strict mode and "android.media.IMediaPlayerService" string to Parcel
- write the two parameters to data.
- 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
Post a Comment