1
0
mirror of https://github.com/google/comprehensive-rust.git synced 2025-03-21 14:46:37 +02:00

Binder/AIDL content updates (#1618)

This commit is contained in:
Nicole L 2024-02-09 15:11:10 -08:00 committed by GitHub
parent 4d0f23e51a
commit f5f2c6b925
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 612 additions and 111 deletions

View File

@ -212,12 +212,21 @@
- [Binary](android/build-rules/binary.md)
- [Library](android/build-rules/library.md)
- [AIDL](android/aidl.md)
- [Interface](android/aidl/interface.md)
- [Implementation](android/aidl/implementation.md)
- [Server](android/aidl/server.md)
- [Deploy](android/aidl/deploy.md)
- [Client](android/aidl/client.md)
- [Changing API](android/aidl/changing.md)
- [Birthday Service Tutorial](android/aidl/birthday-service.md)
- [Interface](android/aidl/example-service/interface.md)
- [Service API](android/aidl/example-service/service-bindings.md)
- [Service](android/aidl/example-service/service.md)
- [Server](android/aidl/example-service/server.md)
- [Deploy](android/aidl/example-service/deploy.md)
- [Client](android/aidl/example-service/client.md)
- [Changing API](android/aidl/example-service/changing-definition.md)
- [Updating Implementations](android/aidl/example-service/changing-implementation.md)
- [AIDL Types](android/aidl/types.md)
- [Primitive Types](android/aidl/types/primitives.md)
- [Array Types](android/aidl/types/arrays.md)
- [Sending Objects](android/aidl/types/objects.md)
- [Parcelables](android/aidl/types/parcelables.md)
- [Sending Files](android/aidl/types/file-descriptor.md)
- [Logging](android/logging.md)
- [Interoperability](android/interoperability.md)
- [With C](android/interoperability/with-c.md)

View File

@ -0,0 +1,5 @@
# Birthday Service Tutorial
To illustrate how to use Rust with Binder, we're going to walk through the
process of creating a Binder interface. We're then going to both implement the
described service and write client code that talks to that service.

View File

@ -0,0 +1,6 @@
package com.example.birthdayservice;
parcelable BirthdayInfo {
String name;
int years;
}

View File

@ -0,0 +1,21 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ANCHOR: IBirthdayInfoProvider
package com.example.birthdayservice;
interface IBirthdayInfoProvider {
String name();
int years();
}

View File

@ -12,11 +12,33 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// ANCHOR: IBirthdayService
package com.example.birthdayservice;
import com.example.birthdayservice.IBirthdayInfoProvider;
import com.example.birthdayservice.BirthdayInfo;
// ANCHOR: IBirthdayService
/** Birthday service interface. */
interface IBirthdayService {
/** Generate a Happy Birthday message. */
String wishHappyBirthday(String name, int years);
// ANCHOR_END: IBirthdayService
// ANCHOR: with_info
/** The same thing, but with a parcelable. */
String wishWithInfo(in BirthdayInfo info);
// ANCHOR_END: with_info
// ANCHOR: with_info_provider
/** The same thing, but using a binder object. */
String wishWithProvider(IBirthdayInfoProvider provider);
/** The same thing, but using `IBinder`. */
String wishWithErasedProvider(IBinder provider);
// ANCHOR_END: with_info_provider
// ANCHOR: with_file
/** The same thing, but loads info from a file. */
String wishFromFile(in ParcelFileDescriptor infoFile);
// ANCHOR_END: with_file
}

View File

@ -12,21 +12,22 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// ANCHOR: main
//! Birthday service.
use com_example_birthdayservice::aidl::com::example::birthdayservice::BirthdayInfo::BirthdayInfo;
use com_example_birthdayservice::aidl::com::example::birthdayservice::IBirthdayInfoProvider::{
BnBirthdayInfoProvider, IBirthdayInfoProvider,
};
use com_example_birthdayservice::aidl::com::example::birthdayservice::IBirthdayService::IBirthdayService;
use com_example_birthdayservice::binder;
use com_example_birthdayservice::binder::{self, BinderFeatures, ParcelFileDescriptor};
use std::error::Error;
use std::fs::File;
use std::io::prelude::*;
// ANCHOR: main
const SERVICE_IDENTIFIER: &str = "birthdayservice";
/// Connect to the BirthdayService.
pub fn connect() -> Result<binder::Strong<dyn IBirthdayService>, binder::StatusCode>
{
binder::get_interface(SERVICE_IDENTIFIER)
}
/// Call the birthday service.
fn main() -> Result<(), binder::Status> {
fn main() -> Result<(), Box<dyn Error>> {
let name = std::env::args().nth(1).unwrap_or_else(|| String::from("Bob"));
let years = std::env::args()
.nth(2)
@ -34,8 +35,64 @@ fn main() -> Result<(), binder::Status> {
.unwrap_or(42);
binder::ProcessState::start_thread_pool();
let service = connect().expect("Failed to connect to BirthdayService");
let service = binder::get_interface::<dyn IBirthdayService>(SERVICE_IDENTIFIER)
.map_err(|_| "Failed to connect to BirthdayService")?;
// Call the service.
let msg = service.wishHappyBirthday(&name, years)?;
println!("{msg}");
// ANCHOR_END: main
// ANCHOR: wish_with_info
service.wishWithInfo(&BirthdayInfo { name: name.clone(), years })?;
// ANCHOR_END: wish_with_info
// ANCHOR: wish_with_provider
// Create a binder object for the `IBirthdayInfoProvider` interface.
let provider = BnBirthdayInfoProvider::new_binder(
InfoProvider { name: name.clone(), age: years as u8 },
BinderFeatures::default(),
);
// Send the binder object to the service.
service.wishWithProvider(&provider)?;
// Perform the same operation but passing the provider as an `SpIBinder`.
service.wishWithErasedProvider(&provider.as_binder())?;
// ANCHOR_END: wish_with_provider
// ANCHOR: wish_with_file
// Open a file and put the birthday info in it.
let mut file = File::create("/data/local/tmp/birthday.info").unwrap();
writeln!(file, "{name}")?;
writeln!(file, "{years}")?;
// Create a `ParcelFileDescriptor` from the file and send it.
let file = ParcelFileDescriptor::new(file);
service.wishFromFile(&file)?;
// ANCHOR_END: wish_with_file
Ok(())
}
// ANCHOR: InfoProvider
/// Rust struct implementing the `IBirthdayInfoProvider` interface.
struct InfoProvider {
name: String,
age: u8,
}
impl binder::Interface for InfoProvider {}
impl IBirthdayInfoProvider for InfoProvider {
fn name(&self) -> binder::Result<String> {
Ok(self.name.clone())
}
fn years(&self) -> binder::Result<i32> {
Ok(self.age as i32)
}
}
// ANCHOR_END: InfoProvider

View File

@ -12,11 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// ANCHOR: IBirthdayService
//! Implementation of the `IBirthdayService` AIDL interface.
use com_example_birthdayservice::aidl::com::example::birthdayservice::IBirthdayInfoProvider::IBirthdayInfoProvider;
use com_example_birthdayservice::aidl::com::example::birthdayservice::IBirthdayService::IBirthdayService;
use com_example_birthdayservice::binder;
use com_example_birthdayservice::aidl::com::example::birthdayservice::BirthdayInfo::BirthdayInfo;
use com_example_birthdayservice::binder::{self, ParcelFileDescriptor, SpIBinder, Strong};
use std::fs::File;
use std::io::Read;
// ANCHOR: IBirthdayService
/// The `IBirthdayService` implementation.
pub struct BirthdayService;
@ -26,4 +30,63 @@ impl IBirthdayService for BirthdayService {
fn wishHappyBirthday(&self, name: &str, years: i32) -> binder::Result<String> {
Ok(format!("Happy Birthday {name}, congratulations with the {years} years!"))
}
// ANCHOR_END: IBirthdayService
fn wishWithInfo(&self, info: &BirthdayInfo) -> binder::Result<String> {
Ok(format!(
"Happy Birthday {}, congratulations with the {} years!",
info.name, info.years,
))
}
fn wishWithProvider(
&self,
provider: &Strong<dyn IBirthdayInfoProvider>,
) -> binder::Result<String> {
Ok(format!(
"Happy Birthday {}, congratulations with the {} years!",
provider.name()?,
provider.years()?,
))
}
fn wishWithErasedProvider(
&self,
provider: &SpIBinder,
) -> binder::Result<String> {
// Convert the `SpIBinder` to a concrete interface.
let provider =
provider.clone().into_interface::<dyn IBirthdayInfoProvider>()?;
Ok(format!(
"Happy Birthday {}, congratulations with the {} years!",
provider.name()?,
provider.years()?,
))
}
// ANCHOR: wishFromFile
fn wishFromFile(
&self,
info_file: &ParcelFileDescriptor,
) -> binder::Result<String> {
// Convert the file descriptor to a `File`. `ParcelFileDescriptor` wraps
// an `OwnedFd`, which can be cloned and then used to create a `File`
// object.
let mut info_file = info_file
.as_ref()
.try_clone()
.map(File::from)
.expect("Invalid file handle");
let mut contents = String::new();
info_file.read_to_string(&mut contents).unwrap();
let mut lines = contents.lines();
let name = lines.next().unwrap();
let years: i32 = lines.next().unwrap().parse().unwrap();
Ok(format!("Happy Birthday {name}, congratulations with the {years} years!"))
}
// ANCHOR_END: wishFromFile
}

View File

@ -1,14 +0,0 @@
# Changing API
Let us extend the API with more functionality: we want to let clients specify a
list of lines for the birthday card:
```java
package com.example.birthdayservice;
/** Birthday service interface. */
interface IBirthdayService {
/** Generate a Happy Birthday message. */
String wishHappyBirthday(String name, int years, in String[] text);
}
```

View File

@ -1,27 +0,0 @@
# AIDL Client
Finally, we can create a Rust client for our new service.
_birthday_service/src/client.rs_:
```rust,ignore
{{#include birthday_service/src/client.rs:main}}
```
_birthday_service/Android.bp_:
```javascript
{{#include birthday_service/Android.bp:birthday_client}}
```
Notice that the client does not depend on `libbirthdayservice`.
Build, push, and run the client on your device:
```shell
{{#include ../build_all.sh:birthday_client}}
```
```text
Happy Birthday Charlie, congratulations with the 60 years!
```

View File

@ -0,0 +1,38 @@
# Changing API
Let us extend the API with more functionality: we want to let clients specify a
list of lines for the birthday card:
```java
package com.example.birthdayservice;
/** Birthday service interface. */
interface IBirthdayService {
/** Generate a Happy Birthday message. */
String wishHappyBirthday(String name, int years, in String[] text);
}
```
This results in an updated trait definition for `IBirthdayService`:
```rust,ignore
trait IBirthdayService {
fn wishHappyBirthday(
&self,
name: &str,
years: i32,
text: &[String],
) -> binder::Result<String>;
}
```
<details>
- Note how the `String[]` in the AIDL definition is translated as a `&[String]`
in Rust, i.e. that idiomatic Rust types are used in the generated bindings
wherever possible:
- `in` array arguments are translated to slices.
- `out` and `inout` args are translated to `&mut Vec<T>`.
- Return values are translated to returning a `Vec<T>`.
</details>

View File

@ -0,0 +1,46 @@
# Updating Client and Service
Update the client and server code to account for the new API.
_birthday_service/src/lib.rs_:
```rust,ignore
impl IBirthdayService for BirthdayService {
fn wishHappyBirthday(
&self,
name: &str,
years: i32,
text: &[String],
) -> binder::Result<String> {
let mut msg = format!(
"Happy Birthday {name}, congratulations with the {years} years!",
);
for line in text {
msg.push('\n');
msg.push_str(line);
}
Ok(msg)
}
}
```
_birthday_service/src/client.rs_:
```rust,ignore
let msg = service.wishHappyBirthday(
&name,
years,
&[
String::from("Habby birfday to yuuuuu"),
String::from("And also: many more"),
],
)?;
```
<details>
- TODO: Move code snippets into project files where they'll actually be built?
</details>

View File

@ -0,0 +1,48 @@
# AIDL Client
Finally, we can create a Rust client for our new service.
_birthday_service/src/client.rs_:
```rust,ignore
use com_example_birthdayservice::aidl::com::example::birthdayservice::IBirthdayService::IBirthdayService;
use com_example_birthdayservice::binder;
{{#include ../birthday_service/src/client.rs:main}}
}
```
_birthday_service/Android.bp_:
```javascript
{{#include ../birthday_service/Android.bp:birthday_client}}
```
Notice that the client does not depend on `libbirthdayservice`.
Build, push, and run the client on your device:
```shell
{{#include ../../build_all.sh:birthday_client}}
```
```text
Happy Birthday Charlie, congratulations with the 60 years!
```
<details>
- `Strong<dyn IBirthdayService>` is the trait object representing the service
that the client has connected to.
- `Strong` is a custom smart pointer type for Binder. It handles both an
in-process ref count for the service trait object, and the global Binder ref
count that tracks how many processes have a reference to the object.
- Note that the trait object that the client uses to talk to the service uses
the exact same trait that the server implements. For a given Binder
interface, there is a single Rust trait generated that both client and
server use.
- Use the same service identifier used when registering the service. This should
ideally be defined in a common crate that both the client and server can
depend on.
</details>

View File

@ -3,13 +3,13 @@
We can now build, push, and start the service:
```shell
{{#include ../build_all.sh:birthday_server}}
{{#include ../../build_all.sh:birthday_server}}
```
In another terminal, check that the service runs:
```shell
{{#include ../build_all.sh:service_check_birthday_server}}
{{#include ../../build_all.sh:service_check_birthday_server}}
```
```text
@ -19,7 +19,7 @@ Service birthdayservice: found
You can also call the service with `service call`:
```shell
{{#include ../build_all.sh:service_call_birthday_server}}
{{#include ../../build_all.sh:service_call_birthday_server}}
```
```text

View File

@ -0,0 +1 @@
# Implementation

View File

@ -0,0 +1,25 @@
# AIDL Interfaces
You declare the API of your service using an AIDL interface:
_birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl_:
```java
{{#include ../birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl:IBirthdayService}}
}
```
_birthday_service/aidl/Android.bp_:
```javascript
{{#include ../birthday_service/aidl/Android.bp}}
```
<details>
- Note that the directory structure under the `aidl/` directory needs to match
the package name used in the AIDL file, i.e. the package is
`com.example.birthdayservice` and the file is at
`aidl/com/example/IBirthdayService.aidl`.
</details>

View File

@ -0,0 +1,36 @@
# AIDL Server
Finally, we can create a server which exposes the service:
_birthday_service/src/server.rs_:
```rust,ignore
{{#include ../birthday_service/src/server.rs:main}}
```
_birthday_service/Android.bp_:
```javascript
{{#include ../birthday_service/Android.bp:birthday_server}}
```
<details>
The process for taking a user-defined service implementation (in this case the
`BirthdayService` type, which implements the `IBirthdayService`) and starting it
as a Binder service has multiple steps, and may appear more complicated than
students are used to if they've used Binder from C++ or another language.
Explain to students why each step is necessary.
1. Create an instance of your service type (`BirthdayService`).
1. Wrap the service object in corresponding `Bn*` type (`BnBirthdayService` in
this case). This type is generated by Binder and provides the common Binder
functionality that would be provided by the `BnBinder` base class in C++. We
don't have inheritance in Rust, so instead we use composition, putting our
`BirthdayService` within the generated `BnBinderService`.
1. Call `add_service`, giving it a service identifier and your service object
(the `BnBirthdayService` object in the example).
1. Call `join_thread_pool` to add the current thread to Binder's thread pool and
start listening for connections.
</details>

View File

@ -0,0 +1,33 @@
# Generated Service API
Binder generates a trait corresponding to the interface definition. trait to
talk to the service.
_birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl_:
```java
{{#include ../birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl:IBirthdayService}}
}
```
_Generated trait_:
```rust,ignore
trait IBirthdayService {
fn wishHappyBirthday(&self, name: &str, years: i32) -> binder::Result<String>;
}
```
Your service will need to implement this trait, and your client will use this
trait to talk to the service.
<details>
- The generated bindings can be found at
`out/soong/.intermediates/<path to module>/`.
- Point out how the generated function signature, specifically the argument and
return types, correspond the interface definition.
- `String` for an argument results in a different Rust type than `String` as a
return type.
</details>

View File

@ -0,0 +1,28 @@
# Service Implementation
We can now implement the AIDL service:
_birthday_service/src/lib.rs_:
```rust,ignore
use com_example_birthdayservice::aidl::com::example::birthdayservice::IBirthdayService::IBirthdayService;
use com_example_birthdayservice::binder;
{{#include ../birthday_service/src/lib.rs:IBirthdayService}}
}
```
_birthday_service/Android.bp_:
```javascript
{{#include ../birthday_service/Android.bp:libbirthdayservice}}
```
<details>
- Point out the path to the generated `IBirthdayService` trait, and explain why
each of the segments is necessary.
- TODO: What does the `binder::Interface` trait do? Are there methods to
override? Where source?
</details>

View File

@ -1,15 +0,0 @@
# Service Implementation
We can now implement the AIDL service:
_birthday_service/src/lib.rs_:
```rust,ignore
{{#include birthday_service/src/lib.rs:IBirthdayService}}
```
_birthday_service/Android.bp_:
```javascript
{{#include birthday_service/Android.bp:libbirthdayservice}}
```

View File

@ -1,18 +0,0 @@
# AIDL Interfaces
You declare the API of your service using an AIDL interface:
_birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl_:
```java
{{#include birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl:IBirthdayService}}
```
_birthday_service/aidl/Android.bp_:
```javascript
{{#include birthday_service/aidl/Android.bp}}
```
Add `vendor_available: true` if your AIDL file is used by a binary in the vendor
partition.

View File

@ -1,15 +0,0 @@
# AIDL Server
Finally, we can create a server which exposes the service:
_birthday_service/src/server.rs_:
```rust,ignore
{{#include birthday_service/src/server.rs:main}}
```
_birthday_service/Android.bp_:
```javascript
{{#include birthday_service/Android.bp:birthday_server}}
```

View File

@ -0,0 +1,9 @@
# Working With AIDL Types
AIDL types translate into the appropriate idiomatic Rust type:
- Primitive types map (mostly) to idiomatic Rust types.
- Collection types like slices, `Vec`s and string types are supported.
- References to AIDL objects and file handles can be sent between clients and
services.
- File handles and parcelables are fully supported.

View File

@ -0,0 +1,20 @@
# Array Types
The array types (`T[]`, `byte[]`, and `List<T>`) get translated to the
appropriate Rust array type depending on how they are used in the function
signature:
| Position | Rust Type |
| ---------------------- | ------------- |
| `in` argument | `&[T]` |
| `out`/`inout` argument | `&mut Vec<T>` |
| Return | `Vec<T>` |
<details>
- In Android 13 or higher, fixed-size arrays are supported, i.e. `T[N]` becomes
`[T; N]`. Fixed-size arrays can have multiple dimensions (e.g. int[3][4]). In
the Java backend, fixed-size arrays are represented as array types.
- Arrays in parcelable fields always get translated to `Vec<T>`.
</details>

View File

@ -0,0 +1,40 @@
# Sending Files
Files can be sent between Binder clients/servers using the
`ParcelFileDescriptor` type:
**birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl**:
```java
interface IBirthdayService {
{{#include ../birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl:with_file}}
}
```
**birthday_service/src/client.rs**:
```rust,ignore
fn main() {
binder::ProcessState::start_thread_pool();
let service = connect().expect("Failed to connect to BirthdayService");
{{#include ../birthday_service/src/client.rs:wish_with_file}}
}
```
**birthday_service/src/lib.rs**:
```rust,ignore
impl IBirthdayService for BirthdayService {
{{#include ../birthday_service/src/lib.rs:wishFromFile}}
}
```
<details>
- `ParcelFileDescriptor` wraps an `OwnedFd`, and so can be created from a `File`
(or any other type that wraps an `OwnedFd`), and can be used to create a new
`File` handle on the other side.
- Other types of file descriptors can be wrapped and sent, e.g. TCP, UDP, and
UNIX sockets.
</details>

View File

@ -0,0 +1,39 @@
# Sending Objects
AIDL objects can be sent either as a concrete AIDL type or as the type-erased
`IBinder` interface:
**birthday_service/aidl/com/example/birthdayservice/IBirthdayInfoProvider.aidl**:
```java
{{#include ../birthday_service/aidl/com/example/birthdayservice/IBirthdayInfoProvider.aidl:IBirthdayInfoProvider}}
```
**birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl**:
```java
import com.example.birthdayservice.IBirthdayInfoProvider;
interface IBirthdayService {
{{#include ../birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl:with_info_provider}}
}
```
**birthday_service/src/client.rs**:
```rust,ignore
{{#include ../birthday_service/src/client.rs:InfoProvider}}
fn main() {
binder::ProcessState::start_thread_pool();
let service = connect().expect("Failed to connect to BirthdayService");
{{#include ../birthday_service/src/client.rs:wish_with_provider}}
}
```
<details>
- Note the usage of `BnBirthdayInfoProvider`. This serves the same purpose as
`BnBirthdayService` that we saw previously.
</details>

View File

@ -0,0 +1,30 @@
# Parcelables
Binder for Rust supports sending parcelables directly:
**birthday_service/aidl/com/example/birthdayservice/BirthdayInfo.aidl**:
```java
{{#include ../birthday_service/aidl/com/example/birthdayservice/BirthdayInfo.aidl}}
```
**birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl**:
```java
import com.example.birthdayservice.BirthdayInfo;
interface IBirthdayService {
{{#include ../birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl:with_info}}
}
```
**birthday_service/src/client.rs**:
```rust,ignore
fn main() {
binder::ProcessState::start_thread_pool();
let service = connect().expect("Failed to connect to BirthdayService");
{{#include ../birthday_service/src/client.rs:wish_with_info}}
}
```

View File

@ -0,0 +1,14 @@
# Primitive Types
Primitive types map (mostly) idiomatically:
| AIDL Type | Rust Type | Note |
| --------- | --------- | ----------------------------------- |
| `boolean` | `bool` | |
| `byte` | `i8` | Note that bytes are signed. |
| `char` | `u16` | Note the usage of `u16`, NOT `u32`. |
| `int` | `i32` | |
| `long` | `i64` | |
| `float` | `f32` | |
| `double` | `f64` | |
| `String` | `String` | |