How to realize IPC

Bound services

A bound service is the server in a client-server interface. It allows components (such as activities) to bind to the service, send requests, receive responses, and perform interprocess communication (IPC).

How to create Bound Service

When creating a service that provides binding, you must provide an IBinder that provides the programming interface that clients can use to interact with the service.

How to define an IBinder

Using AIDL

Android Interface Definition Language (AIDL) decomposes objects into primitives that the operating system can understand and marshals them across processes to perform IPC.

How to create a bounded service using AIDL

  1. Create the .aidl file

    This file defines the programming interface with method signatures.

  2. Implement the interface

    The Android SDK tools generate an interface in the Java programming language, based on your .aidl file. This interface has an inner abstract class named Stub that extends Binder and implements methods from your AIDL interface. You must extend the Stub class and implement the methods.

  3. Expose the interface to clients

    Implement a Service and override onBind() to return your implementation of the Stub class.

General Steps

  1. create aidl file
  2. create service
  3. implement IBinder for service
  4. instantiate service in client (activity)

AccountManager

https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/accounts

Theory Example: IPC between service and accountManager
Define Bound service MyAccountTypeService implement onBind
provide IBinder return MyAccountAuthenticator.getIBinder();
use AIDL define IBinder
create the .aidl file IAccountAuthenticator.aidl
IAccountAuthenticatorResponse.aidl
Implement the interface private class Transport extends IAccountAuthenticator.Stub line:152
Expose the interface to clients return MyAccountAuthenticator.getIBinder();

Work flow

AccountManager : getAuthToken() -> new AmsTask() ->

mService.getAuthToken()

mService is IAccountManager created by system

the real invoke is AccountManagerService (extends IAccountManager.Stub)

AccountManagerService : getAuthToken() -> new Session() -> bind to mAuthenticator related service -> mAuthenticator.getAuthToken()

*mAuthenticatoir is IAccountAuthenticator assaigned In Session(extend IAccountAuthenticatorResponse.Stub and implement ServiceConnection): onServiceConnected() method

`mAuthenticator = IAccountAuthenticator.Stub.asInterface(service);`

AbstractAccountAuthenticator : getAuthToken() -> AbstractAccountAuthenticator.this.getAuthToken()

here invoke our proper method

AccountAuthenticator : intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response)

pass AccountAuthenticatorResponse by intent

return bundle

AccountAuthenticatorResponse implements Parcelable contains IAccountAuthenticatorResponse

Session : onResult() -> result contain KEY_INTENT -> response.onResult()

response is IAccountManagerResponse.Stub declare in AccountManager

Response : onResult() -> mActivity.startActivity(intent)

ShareMyAccountActivity : getIntent() -> AccountAuthenticatorResponse.onResult() -> mAccountAuthenticatorResponse.onResult()

mAccountAuthenticatorResponse is IAccountAuthenticatorResponse is assaigned in AccountManagerService.newRequestAccountAccessIntent()

How AccountAuthenticatorResponse work ?

  • define a IAccountAuthenticatorResponse aidl file
1
2
3
4
5
6
7
8
9
10
11
12
/**
* The interface used to return responses from an {@link IAccountAuthenticator}
* @hide
*/
oneway interface IAccountAuthenticatorResponse {
@UnsupportedAppUsage
void onResult(in Bundle value);
@UnsupportedAppUsage
void onRequestContinued();
@UnsupportedAppUsage
void onError(int errorCode, String errorMessage);
}
  • implement this interface
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
private abstract class Session extends IAccountAuthenticatorResponse.Stub
implements IBinder.DeathRecipient, ServiceConnection {
IAccountManagerResponse mResponse;
...
IAccountAuthenticator mAuthenticator = null;
...
void bind() {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "initiating bind to authenticator type " + mAccountType);
}
if (!bindToAuthenticator(mAccountType)) {
Log.d(TAG, "bind attempt failed for " + toDebugString());
onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "bind failure");
}
}
...
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mAuthenticator = IAccountAuthenticator.Stub.asInterface(service);
try {
run();
} catch (RemoteException e) {
onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
"remote exception");
}
}
...
@Override
public void onResult(Bundle result) {
...
IAccountManagerResponse response;
response = mResponse
response.onResult();
...
}
}
  • accountManager -> AmTask -> AccountManagerService -> Session( instantiate IAccountAuthenticatorResponse ) -> bind to authenticator service -> call mAuthenticator.getAuthToken(pass IAccountAuthenticatorResponse)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Override
public void getAuthToken(
IAccountManagerResponse response,
final Account account,
final String authTokenType,
final boolean notifyOnAuthFailure,
final boolean expectActivityLaunch,
final Bundle loginOptions) {
...
new Session(
accounts,
response,
account.type,
expectActivityLaunch,
false /* stripAuthTokenFromResult */,
account.name,
false /* authDetailsRequired */) {

@Override
public void run() throws RemoteException {
// If the caller doesn't have permission then create and return the
// "grant permission" intent instead of the "getAuthToken" intent.
if (!permissionGranted) {
mAuthenticator.getAuthTokenLabel(this, authTokenType);
} else {
mAuthenticator.getAuthToken(this, account, authTokenType, loginOptions);
}
}
...
}.bind();
...
}
  • define an object to get passed IAccountAuthenticatorResponse and use callback send back result
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* Object used to communicate responses back to the AccountManager
*/
public class AccountAuthenticatorResponse implements Parcelable {
...
private IAccountAuthenticatorResponse mAccountAuthenticatorResponse;
...
public void onResult(Bundle result) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
result.keySet(); // force it to be unparcelled
Log.v(TAG, "AccountAuthenticatorResponse.onResult: "
+ AccountManager.sanitizeResult(result));
}
try {
mAccountAuthenticatorResponse.onResult(result);
} catch (RemoteException e) {
// this should never happen
}
}
...
}
  • AbstactAccountAuthenticator get this response and instantiate it again pass to our proper authenticator
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public abstract class AbstractAccountAuthenticator {
private class Transport extends IAccountAuthenticator.Stub {
...
@Override
public void getAuthToken(IAccountAuthenticatorResponse response,
Account account, String authTokenType, Bundle loginOptions)
throws RemoteException {
...
final Bundle result = AbstractAccountAuthenticator.this.getAuthToken(
new AccountAuthenticatorResponse(response), account,
authTokenType, loginOptions);
...
if (result != null) {
response.onResult(result);
}
...
}
...
}
  • MyAccountAuthenticator pass this response to ShareMyAccountActivity by intent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class MyAccountAuthenticator extends AbstractAccountAuthenticator
{
@Override
public Bundle getAuthToken(final AccountAuthenticatorResponse response,
final Account account,
final String authTokenType,
final Bundle options) {
...
final Intent intent = ShareMyAccountActivity
.getShareMyAccountActivity(context);
...
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
final Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
...
}
}
  • ShareMyAccountActivity after user confirm give back result
1
2
3
4
5
6
7
8
9
10
11
12
public class ShareMyAccountActivityInteractorImpl
implements ShareMyAccountActivityContract.Interactor {
...

@Override
public void shareAccess(AccountAuthenticatorResponse accountAuthenticatorResponse,
String userName, String accountType, String refreshToken) {
...
accountAuthenticatorResponse.onResult(result);
...
}
}

Conclusion

IAccountAuthenticatorResponse is used in two parts.

  1. As Session : implement and instantiate and bind in Service, work like Binder for Bound Service

once service pass it to authenticator by parameter, it will be encapsulated by AccountAuthenticatorResponse

  1. As AccountAuthenticatorResponse : it can be passed to activity by intent. If its methodes are called, the IAccountAuthenticatorResponse’s callback fonctions also works.

If we take example by its second usage, even without service, we can pass our custom callback to activity.

reference: https://developer.android.com/guide/components/aidl#PassingObjects