AIDL/Binder codelab example.
This creates a rust service implementation with a fuzzer and a service
that can be added to the system partition and run at boot.
The client is a test case.
Test: mma && atest hello-world-client-test
Test: adb shell /data/fuzz/x86_64/hello-world-fuzzer/hello-world-fuzzer
Bug: none
Change-Id: I0a3a3692ede16a37cf34b4da0ef7ada580fcd397
diff --git a/codelab/README.md b/codelab/README.md
new file mode 100644
index 0000000..9100105
--- /dev/null
+++ b/codelab/README.md
@@ -0,0 +1,280 @@
+# Android Platform Binder Codelab
+
+go/android-binder-codelab
+
+This Android Platform Developer codelab explores the use of Binder in the
+Android Platform. go/android-codelab is a prerequisite for this work. In this
+codelab, you create an AIDL interface along with client and server processes
+that communicate with each other over Binder.
+
+## Goals
+
+There are four goals for this codelab:
+
+1. Introduce you to Binder, Android's core IPC mechanism
+2. Use AIDL to define a Binder interface for a simple client/server interaction
+3. Use Binder's Service Management infrastructure to make the service available
+ to clients
+4. Get and use the service with basic error handling
+
+## What to expect
+
+### Estimated time
+
+* Hands-off time ~4 hours
+* Hands-on time ~1 hours
+
+### Prerequisites
+
+**Before** taking this codelab, complete go/android-codelab first. If external,
+follow https://source.android.com/docs/setup/start.
+
+You will need an Android repo with the ability to make changes, build, deploy,
+test, and upload the changes. All of this is covered in go/android-codelab.
+
+See the slides at go/onboarding-binder for a summary of key concepts that will
+help you understand the steps in this codelab.
+
+## Codelab
+
+Most of this code can be found in `system/tools/aidl/codelab`, but the example
+service isn't installed on real devices. The build code to install the service
+on the device and the sepolicy changes are described in this `README.md` but not
+submitted.
+
+### Create the new AIDL interface
+
+Choose a location for the AIDL interface definition.
+
+* `hardware/interfaces` - for
+ [HAL](https://source.android.com/docs/core/architecture/hal) interfaces that
+ device-specific processes implement and the Android Platform depends on as a
+ client
+* `frameworks/hardware/interfaces` - for interfaces that the Android Framework
+ implements for device-specific clients
+* `system/hardware/interfaces` - for interfaces that Android system process
+ implement for device-specific clients
+* Any other directory for interfaces that are used between processes that are
+ installed on the same
+ [partitions](https://source.android.com/docs/core/architecture/partitions)
+
+Create a new `aidl_interface` module in a new or existing `Android.bp` file.
+
+Note: go/android.bp has all of the available Soong modules and their supported
+fields.
+
+```soong
+// File: `codelab/aidl/Android.bp`
+aidl_interface {
+ // Package name for the interface. This is used when generating the libraries
+ // that the services and clients will depend on.
+ name: "hello.world",
+ // The source `.aidl` files that define this interface. It is recommended to
+ // create them in a directory next to this Android.bp file with a
+ // directory structure that follows the package name. If you want to change
+ // this, use `local_include_dir` to point to the new directory.
+ srcs: [
+ "hello/world/*.aidl",
+ ],
+ // For simplicity we start with an unstable interface. See
+ // https://source.android.com/docs/core/architecture/aidl/stable-aidl
+ // for details on stable interfaces.
+ unstable: true,
+ // This field controls which libraries are generated at build time and their
+ // backend-specific configurations. AIDL supports different languages.
+ backend: {
+ ndk: {
+ enabled: true,
+ }
+ rust: {
+ enabled: true,
+ }
+ }
+}
+```
+
+Create your first AIDL file.
+
+```java
+// File: `codelab/aidl/hello/world/IHello.aidl`
+// The package name is recommended to match the `name` in the `aidl_interface`
+package hello.world;
+
+interface IHello {
+ // Have the service log a message for us
+ void LogMessage(String message);
+ // Get a "hello world" message from the service
+ String getMessage();
+}
+```
+
+Build the generated AIDL libraries.
+
+```shell
+m hello.world-ndk hello.world-rust
+```
+
+Note: `m hello.world` without the backend suffix will not build anything.
+
+### Create the service
+
+Android encourages Rust for native services so let's build a Rust service!
+
+Binder has easy-to-use libraries to facilitate fuzzing of binder services that
+require the service being defined in its own library. With that in mind, we
+create the library that implements the interface first.
+
+#### Interface implementation
+
+Create a separate library for the interface implementation so it can be used
+for the service and for the fuzzer.
+
+* See [codelab/service/Android.bp](codelab/service/Android.bp)
+
+* See [codelab/service/hello_service.rs](codelab/service/hello_service.rs)
+
+#### Service binary
+
+Create the service that registers itself with servicemanager and joins the
+binder threadpool.
+
+* See [codelab/service/Android.bp](codelab/service/Android.bp)
+
+* See [codelab/service/service_main.rs](codelab/service/service_main.rs)
+
+An init.rc file is required for the process to be started on a device.
+
+* See
+ [codelab/service/hello-world-service-test.rc](codelab/service/hello-world-service-test.rc)
+
+#### Sepolicy for the service
+
+Associate the service binary with a selinux context so init can start it.
+```
+// File: system/sepolicy/private/file_contexts
+/system/bin/hello-world-service-test u:object_r:hello_exec:s0
+```
+
+Add a file for the service to define its types, associate the binary to the
+types, and give permissions to the service..
+```
+// File: system/sepolicy/private/hello.te
+
+// Permissions required for the process to start
+type hello, domain;
+typeattribute hello coredomain;
+type hello_exec, system_file_type, exec_type, file_type;
+init_daemon_domain(hello)
+
+// Permissions to be a binder service and talk to service_manager
+binder_service(hello)
+binder_use(hello)
+binder_call(hello, binderservicedomain)
+
+// Give permissions to this service to register itself with service_manager
+allow hello hello_service:service_manager { add find };
+```
+
+Declare this service as a service_manager_type
+```
+// File: system/sepolicy/public/service.te
+type hello_service, service_manager_type;
+```
+
+Associate the AIDL interface/instance with this service
+```
+// File: system/sepolicy/private/service_contexts
+hello.world.IHello/default u:object_r:hello_service:s0
+```
+
+#### Fuzzer
+
+Create the fuzzer and use `fuzz_service` to do all of the hard work!
+
+* See [codelab/service/Android.bp](codelab/service/Android.bp)
+
+* See [codelab/service/service_fuzzer.rs](codelab/service/service_fuzzer.rs)
+
+Associate the fuzzer with the interface by adding the following to
+`system/sepolicy/build/soong/service_fuzzer_bindings.go`:
+```
+"hello.world.IHello/default": []string{"hello-world-fuzzer"},
+```
+This step is required by the build system once we add the sepolicy for the
+service in the previous step.
+
+### Create the client
+
+#### Sepolicy for the client
+
+We skip these details by creating the client in a `cc_test`.
+
+The clients need permission to `find` the service through servicemanager:
+
+`allow <client> hello_service:servicemanager find;`
+
+The clients need permission to `call` the service, pass the binders to other
+processes, and use any file descriptors returned through the interface. The
+`binder_call` macro handles all this for us.
+
+`binder_call(<client>, hello_service);`
+
+### Test the changes
+
+Add the service to the device. Since this is a system service it needs to be
+added to the system partition. We add the following to
+`build/make/target/product/base_system.mk`.
+
+```
+PRODUCT_PACKAGES += hello-world-service-test
+```
+
+Build the device.
+
+Launch the device.
+
+Verify the service is in the `dumpsys -l` output.
+
+`atest hello-world-client-test` and verify it passes!
+
+## Wrap up
+
+Congratulations! You are now familiar with some core concepts of Binder and AIDL
+in Android.
+When you think of IPC in Android, think of Binder.
+When you think of Binder, think of AIDL.
+
+See https://source.android.com/docs/core/architecture/aidl for related AIDL and
+Binder documentation.
+Check out https://source.android.com/docs/core/architecture/aidl/aidl-hals for
+details that are more specific to Hardware Abstraction Layer (HAL) services.
+
+### Supporting documentation
+
+* go/onboarding-binder
+* go/binder-ipc
+* go/stable-aidl
+* go/aidl-backends
+* go/aidl (for app developers with Java/Kotlin)
+
+See all of the other AIDL related pages in our external documentation:
+https://source.android.com/docs/core/architecture/aidl.
+
+### Feedback
+
+If you are external please see
+https://source.android.com/docs/setup/contribute/report-bugs to find out how to
+create a Bug for questions, suggestions, or reporting bugs.
+
+Please write to android-idl-discuss@google.com.
+
+Anyone is free to create and upload CLs in `system/tools/aidl/codelab/` with any
+suggestions or corrections.
+
+### Wish list
+
+Add your suggested additions and updates to this codelab here!
+
+1. I wish this codelab would dive deeper into `DeathRecipients` and remote
+ reference counting
+
diff --git a/codelab/aidl/Android.bp b/codelab/aidl/Android.bp
new file mode 100644
index 0000000..f3ad901
--- /dev/null
+++ b/codelab/aidl/Android.bp
@@ -0,0 +1,43 @@
+// Copyright (C) 2025 The Android Open Source Project
+//
+// 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.
+
+aidl_interface {
+ // Package name for the interface. This is used when generating the libraries
+ // that the services and clients will depend on.
+ name: "hello.world",
+
+ // The source `.aidl` files that define this interface. It is recommended to
+ // create them in an `aidl/` directory next to this Android.bp file with a
+ // directory structure that follows the package name.
+ srcs: [
+ "hello/world/*.aidl",
+ ],
+
+ // For simplicity we start with an unstable interface. See
+ // https://source.android.com/docs/core/architecture/aidl/stable-aidl
+ // for details on stable interfaces.
+ unstable: true,
+
+ // This field controls which libraries are generated at build time and their
+ // backend-specific configurations.
+ backend: {
+ java: {
+ enabled: true,
+ platform_apis: true,
+ },
+ rust: {
+ enabled: true,
+ },
+ },
+}
diff --git a/codelab/aidl/hello/world/IHello.aidl b/codelab/aidl/hello/world/IHello.aidl
new file mode 100644
index 0000000..2b9694f
--- /dev/null
+++ b/codelab/aidl/hello/world/IHello.aidl
@@ -0,0 +1,8 @@
+package hello.world;
+
+interface IHello {
+ // Have the service log a message for us
+ void LogMessage(String message);
+ // Get a "hello world" message from the service
+ String getMessage();
+}
diff --git a/codelab/client/Android.bp b/codelab/client/Android.bp
new file mode 100644
index 0000000..d6fe14a
--- /dev/null
+++ b/codelab/client/Android.bp
@@ -0,0 +1,24 @@
+// Copyright (C) 2025 The Android Open Source Project
+//
+// 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.
+
+cc_test {
+ name: "hello-world-client-test",
+ srcs: ["testClient.cpp"],
+ shared_libs: [
+ "libbinder_ndk",
+ ],
+ static_libs: [
+ "hello.world-ndk",
+ ],
+}
diff --git a/codelab/client/testClient.cpp b/codelab/client/testClient.cpp
new file mode 100644
index 0000000..f0fa985
--- /dev/null
+++ b/codelab/client/testClient.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2025, The Android Open Source Project
+ *
+ * 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.
+ */
+#include <gtest/gtest.h>
+
+#include <android/binder_auto_utils.h>
+#include <android/binder_manager.h>
+
+#include <aidl/hello/world/IHello.h>
+
+using aidl::hello::world::IHello;
+
+TEST(HelloWorldTestClient, GetServiceSayHello) {
+ // Clients will get the binder service using the name that they registered
+ // with. This is up to the service. For this example, we use the interface
+ // descriptor that AIDL generates + "/default".
+ std::string instance = std::string(IHello::descriptor) + "/default";
+ ndk::SpAIBinder binder = ndk::SpAIBinder(AServiceManager_waitForService(instance.c_str()));
+ ASSERT_NE(binder, nullptr);
+
+ // If this is the wrong interface, this result will be null
+ auto hello = IHello::fromBinder(binder);
+ ASSERT_NE(hello, nullptr);
+
+ // All AIDL generated interfaces have a return value with the status of the
+ // transaction, even for void methods.
+ auto res = hello->LogMessage("Hello service!");
+ EXPECT_TRUE(res.isOk()) << res;
+}
diff --git a/codelab/service/Android.bp b/codelab/service/Android.bp
new file mode 100644
index 0000000..0116d45
--- /dev/null
+++ b/codelab/service/Android.bp
@@ -0,0 +1,57 @@
+// Copyright (C) 2025 The Android Open Source Project
+//
+// 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.
+
+rust_binary {
+ name: "hello-world-service-test",
+ srcs: [
+ "service_main.rs",
+ ],
+ rustlibs: [
+ "libhello_service",
+ "libbinder_rs",
+ "liblog_rust",
+ // The generated AIDL libraries have the backend appended to the
+ // end of the name.
+ // `hello.world-<backend>`. In this case the backend is `rust`
+ "hello.world-rust",
+ ],
+ init_rc: ["hello-world-service-test.rc"],
+}
+
+rust_fuzz {
+ name: "hello-world-fuzzer",
+ srcs: ["service_fuzzer.rs"],
+ // These fuzzer defaults have everything needed for the service fuzzer outside
+ // of the specific dependencies of this service.
+ defaults: ["service_fuzzer_defaults_rs"],
+ rustlibs: [
+ "libhello_service",
+ "liblog_rust",
+ "hello.world-rust",
+ ],
+}
+
+// Implementation of the interface. This is in its own library
+// so we can use it for the service on the device AND use it in the fuzzer
+// to find bugs.
+rust_library {
+ name: "libhello_service",
+ crate_name: "hello_service",
+ srcs: ["hello_service.rs"],
+ rustlibs: [
+ "libbinder_rs",
+ "liblog_rust",
+ "hello.world-rust",
+ ],
+}
diff --git a/codelab/service/hello-world-service-test.rc b/codelab/service/hello-world-service-test.rc
new file mode 100644
index 0000000..741832d
--- /dev/null
+++ b/codelab/service/hello-world-service-test.rc
@@ -0,0 +1,4 @@
+service hello-world-service-test /system/bin/hello-world-service-test
+ class core
+ user system
+ group nobody
diff --git a/codelab/service/hello_service.rs b/codelab/service/hello_service.rs
new file mode 100644
index 0000000..ec35c07
--- /dev/null
+++ b/codelab/service/hello_service.rs
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2025, The Android Open Source Project
+ *
+ * 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.
+ */
+//! Implements and serves the hello.world.IHello interface for the
+//! AIDL / Binder codelab
+
+use binder::{Interface, Result};
+use hello_world::aidl::hello::world::IHello::IHello;
+use log::info;
+
+//pub mod hello_service;
+
+/// Implementation for the IHello service used for a codelab
+pub struct Hello;
+
+impl Hello {}
+
+impl Interface for Hello {}
+
+impl IHello for Hello {
+ fn LogMessage(&self, msg: &str) -> Result<()> {
+ info!("{}", msg);
+ Ok(())
+ }
+ fn getMessage(&self) -> Result<String> {
+ Ok("Hello World!".to_string())
+ }
+}
diff --git a/codelab/service/service_fuzzer.rs b/codelab/service/service_fuzzer.rs
new file mode 100644
index 0000000..5838785
--- /dev/null
+++ b/codelab/service/service_fuzzer.rs
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2025, The Android Open Source Project
+ *
+ * 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.
+ */
+#![no_main]
+//! Fuzzer for the IHello service implementation
+
+use binder::BinderFeatures;
+use binder_random_parcel_rs::fuzz_service;
+use hello_service::Hello;
+use hello_world::aidl::hello::world::IHello::BnHello;
+use libfuzzer_sys::fuzz_target;
+
+fuzz_target!(|data: &[u8]| {
+ let service = BnHello::new_binder(Hello, BinderFeatures::default());
+ fuzz_service(&mut service.as_binder(), data);
+});
diff --git a/codelab/service/service_main.rs b/codelab/service/service_main.rs
new file mode 100644
index 0000000..cddc8a4
--- /dev/null
+++ b/codelab/service/service_main.rs
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2025, The Android Open Source Project
+ *
+ * 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.
+ */
+//! Service that registers the IHello interface for a codelab
+
+use hello_service::Hello;
+use hello_world::aidl::hello::world::IHello::{BnHello, IHello};
+
+fn main() {
+ // We join the threadpool with this main thread and never exit. We don't
+ // have any callback binders in this interface, so the single thread is OK.
+ // Set the max to 0 here so libbinder doesn't spawn any additional threads.
+ binder::ProcessState::set_thread_pool_max_thread_count(0);
+
+ let service = BnHello::new_binder(Hello, binder::BinderFeatures::default());
+ let service_name = format!("{}/default", Hello::get_descriptor());
+ binder::add_service(&service_name, service.as_binder())
+ .expect("Failed to register IHello service!");
+
+ binder::ProcessState::join_thread_pool()
+}