README.md
1# Service IPC library
2
3This library provides a kind of IPC (inter-process communication) framework
4based on Android
5[bound service](https://developer.android.com/develop/background-work/services/bound-services)
6with [Messenger](https://developer.android.com/reference/android/os/Messenger).
7
8Following benefits are offered by the library to improve and simplify IPC
9development:
10
11- Enforce permission check for every API implementation to avoid security
12 vulnerability.
13- Allow modular API development for better code maintenance (no more huge
14 Service class).
15- Prevent common mistakes, e.g. Service context leaking, ServiceConnection
16 management.
17
18## Overview
19
20In this manner of IPC,
21[Service](https://developer.android.com/reference/android/app/Service) works
22with [Handler](https://developer.android.com/reference/android/os/Handler) to
23deal with different types of
24[Message](https://developer.android.com/reference/android/os/Message) objects.
25
26Under the hood, each API is represented as a `Message` object:
27
28- [what](https://developer.android.com/reference/android/os/Message#what):
29 used to identify API.
30- [data](https://developer.android.com/reference/android/os/Message#getData\(\)):
31 payload of the API parameters and result.
32
33This could be mapped to the `ApiHandler` interface abstraction exactly.
34Specifically, the API implementation needs to provide:
35
36- An unique id for the API.
37- How to marshall/unmarshall the request and response.
38- Whether the given request is permitted.
39
40## Threading model
41
42`MessengerService` starts a dedicated
43[HandlerThread](https://developer.android.com/reference/android/os/HandlerThread)
44to handle requests. `ApiHandler` implementation uses Kotlin `suspend`, which
45allows flexible threading model on top of the
46[Kotlin coroutines](https://kotlinlang.org/docs/coroutines-overview.html).
47
48## Usage
49
50The service provider should extend `MessengerService` and provide API
51implementations. In `AndroidManifest.xml`, declare `<service>` with permission,
52intent filter, etc. if needed.
53
54Meanwhile, the service client implements `MessengerServiceClient` with API
55descriptors to make requests.
56
57Here is an example:
58
59```kotlin
60import android.app.Application
61import android.content.Context
62import android.content.Intent
63import android.os.Bundle
64import kotlinx.coroutines.runBlocking
65
66class EchoService :
67 MessengerService(
68 listOf(EchoApiImpl),
69 PermissionChecker { _, _, _ -> true },
70 )
71
72class EchoServiceClient(context: Context) : MessengerServiceClient(context) {
73 override val serviceIntentFactory: () -> Intent
74 get() = { Intent("example.intent.action.ECHO") }
75
76 fun echo(data: String?): String? =
77 runBlocking { invoke(context.packageName, EchoApi, data).await() }
78}
79
80object EchoApi : ApiDescriptor<String?, String?> {
81 private val codec =
82 object : MessageCodec<String?> {
83 override fun encode(data: String?) =
84 Bundle(1).apply { putString("data", data) }
85
86 override fun decode(data: Bundle): String? = data.getString("data", null)
87 }
88
89 override val id: Int
90 get() = 1
91
92 override val requestCodec: MessageCodec<String?>
93 get() = codec
94
95 override val responseCodec: MessageCodec<String?>
96 get() = codec
97}
98
99// This is not needed by EchoServiceClient.
100object EchoApiImpl : ApiHandler<String?, String?>,
101 ApiDescriptor<String?, String?> by EchoApi {
102 override suspend fun invoke(
103 application: Application,
104 myUid: Int,
105 callingUid: Int,
106 request: String?,
107 ): String? = request
108
109 override fun hasPermission(
110 application: Application,
111 myUid: Int,
112 callingUid: Int,
113 request: String?,
114 ): Boolean = (request?.length ?: 0) <= 5
115}
116```
117