# Service IPC library

This library provides a kind of IPC (inter-process communication) framework
based on Android
[bound service](https://developer.android.com/develop/background-work/services/bound-services)
with [Messenger](https://developer.android.com/reference/android/os/Messenger).

Following benefits are offered by the library to improve and simplify IPC
development:

-   Enforce permission check for every API implementation to avoid security
    vulnerability.
-   Allow modular API development for better code maintenance (no more huge
    Service class).
-   Prevent common mistakes, e.g. Service context leaking, ServiceConnection
    management.

## Overview

In this manner of IPC,
[Service](https://developer.android.com/reference/android/app/Service) works
with [Handler](https://developer.android.com/reference/android/os/Handler) to
deal with different types of
[Message](https://developer.android.com/reference/android/os/Message) objects.

Under the hood, each API is represented as a `Message` object:

-   [what](https://developer.android.com/reference/android/os/Message#what):
    used to identify API.
-   [data](https://developer.android.com/reference/android/os/Message#getData\(\)):
    payload of the API parameters and result.

This could be mapped to the `ApiHandler` interface abstraction exactly.
Specifically, the API implementation needs to provide:

-   An unique id for the API.
-   How to marshall/unmarshall the request and response.
-   Whether the given request is permitted.

## Threading model

`MessengerService` starts a dedicated
[HandlerThread](https://developer.android.com/reference/android/os/HandlerThread)
to handle requests. `ApiHandler` implementation uses Kotlin `suspend`, which
allows flexible threading model on top of the
[Kotlin coroutines](https://kotlinlang.org/docs/coroutines-overview.html).

## Caveats

IPC request data is transmitted with `Bundle` (see `MessageCodec`),

-   Bundle keys should not be changed to avoid breaking backward compatibility.
-   If strong backward compatibility is required, consider using serialization
    framework (e.g. Protobuf) and marshall/unmarshall with byte array.
-   When parcelable is used (e.g. `Bundle.putParcelable`), the class name must
    be the same across apps. Thus add `@Keep` annotation to the parcelable class
    if Proguard is enabled.

## Usage

The service provider should extend `MessengerService` and provide API
implementations. In `AndroidManifest.xml`, declare `<service>` with permission,
intent filter, etc. if needed.

Meanwhile, the service client implements `MessengerServiceClient` with API
descriptors to make requests.

Here is an example:

```kotlin
import android.app.Application
import android.content.Context
import android.content.Intent
import android.os.Bundle

class EchoService : MessengerService(listOf(EchoApiImpl()), PermissionChecker { _, _, _ -> true })

class EchoServiceClient(context: Context) : MessengerServiceClient(context) {
  private val echoApi = EchoApi()

  override val serviceIntentFactory = { Intent("example.intent.action.ECHO") }

  suspend fun echo(data: String?): String? = invoke(context.packageName, echoApi, data).await()
}

open class EchoApi : ApiDescriptor<String?, String?> {
  private val codec =
    object : MessageCodec<String?> {
      override fun encode(data: String?) = Bundle(1).apply { putString("data", data) }

      override fun decode(data: Bundle): String? = data.getString("data", null)
    }

  override val id: Int
    get() = 1

  override val requestCodec: MessageCodec<String?>
    get() = codec

  override val responseCodec: MessageCodec<String?>
    get() = codec
}

// This is not needed by EchoServiceClient.
class EchoApiImpl : EchoApi(), ApiHandler<String?, String?> {
  override suspend fun invoke(
    application: Application,
    callingPid: Int,
    callingUid: Int,
    request: String?,
  ): String? = request

  override fun hasPermission(
    application: Application,
    callingPid: Int,
    callingUid: Int,
    request: String?,
  ): Boolean = (request?.length ?: 0) <= 5
}
```
