Legacy HAL
Conventional & legacy HAL implementations are typically built into shared library .so modules. The application can use that shared library to communicate with a target device. Note that both Conventional and Legacy HAL are no longer supported from Android 8.0.
Last update: 2022-06-29
Table of Content
This guide is written for AOSP android-10.0.0_r47
Legacy HAL#
Legacy HALs (deprecated in Android 8.0) are interfaces that predate conventional HALs. While there’s no uniform or standardized way to describe a legacy HAL, anything predating Android 8.0 that is not a conventional HAL is a legacy HAL.
A few important subsystems (Wi-Fi, Radio Interface Layer, and Bluetooth) are legacy HALs. Parts of some legacy HALs are contained in libhardware_legacy, while other parts are interspersed throughout the codebase.
This method is the simplest way to implement a HAL library because it is the way to create a shared library without any standardized interface.
Access to HAL#
The HAL implementations are built to lib*.so
. You can link to the library and call to its functions.
For example:
shared_libs: [
"lib<hardware>",
]
Framework Stacks#
Implementation#
Refer to Kernel Module to build and install a module to system. This guide assumes that invcase
module is loaded as below:
+ on boot
+ insmod /system/lib/modules/invcase.ko
+ chown system system /dev/invcase
+ chmod 0600 /dev/invcase
Overview#
-
AOSP
-
build
- make
- target
- product
-
base_vendor.mk
Include new packages+ PRODUCT_PACKAGES += \ + libinvcase \ + invcase_legacy_test \ + Invcase
-
- product
- target
- make
-
packages
- apps
-
Invcase
-
src
- com
- invcase
-
Invcase.java
Get InvcaseManager from SystemServiceclass Invcase extends Activity { InvcaseManager mManager; onCreate() { mManager = getSystemService(Context.INVCASE_SERVICE); } }
-
- invcase
- com
-
res
- layout
- mipmap
- values
-
AndroidManifest.xml
Export main activity on Launcher<manifest package="com.invcase" > <application> <activity android:name=".Invcase" android:exported="true" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
android:exported="true"
is mandatory on Android 12 -
Android.bp
android_app { name: "Invcase", srcs: ["src/**/*.java"], platform_apis: true }
platform_apis: true
: use System API when do not specify any target platform
-
-
- apps
-
frameworks
-
base
-
Android.bp
java_defaults { name: "framework-defaults", installable: true, srcs: [ + "core/java/android/hardware/invcase/IInvcaseManager.aidl", ] }
-
api
-
current.txt
+ package android.hardware.invcase { + + public final class InvcaseManager { + method @NonNull public String getData(); + method public void putData(@NonNull String); + } + + }
-
-
core
- java
-
android
-
app
-
SystemServiceRegistry.java
Create an InvcaseManager instance and add it to the System Service list+ import android.hardware.invcase.InvcaseManager; public final class SystemServiceRegistry { static { + registerService(Context.INVCASE_SERVICE, InvcaseManager.class, + new CachedServiceFetcher<InvcaseManager>() { + @Override + public InvcaseManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + return new InvcaseManager(ctx); + }}); } }
-
-
content
-
Context.java
Add Invcase Serive namepublic abstract class Context { + public static final String INVCASE_SERVICE = "invcase"; @StringDef(suffix = { "_SERVICE" }, value = { + INVCASE_SERVICE, }) }
-
-
hardware
-
invcase
-
IInvcaseManager.aidl
Define API for Invcase Interfaceinterface IInvcaseManager { getData(); putData(); }
-
InvcaseManager.java
Use Invase Serive through the Invcase Interfaceclass InvcaseManager { IInvcaseManager mService; InvcaseManager(Context) throws ServiceNotFoundException { this(context, IInvcaseManager.Stub.asInterface( ServiceManager.getServiceOrThrow(Context.INVCASE_SERVICE)) ); } InvcaseManager(Context, IInvcaseManager){} getData() { mService.getData(); } putData() { mService.putData(); } }
-
-
-
-
- java
-
services
-
core
-
java
- com
- android
- server
- invcase
-
InvcaseService.java
Implement Invcase Interface functions, public the interfaceclass InvcaseService extends SystemService { IInvcaseManagerImpl extends IInvcaseManager.Stub { getData() { invcase_native_read(); } putData() { invcase_native_write(); } } mManagerService; onStart() { publishBinderService(Context.INVCASE_SERVICE, mManagerService); } }
-
- invcase
- server
- android
- com
-
jni
-
com_android_server_invcase_InvcaseService.cpp
Map java functions to native functions#include <hardware_legacy/invcase.h> jni_read () { invcase_read(); } jni_write () { invcase_write(); } static const JNINativeMethod method_table[] = { { "invcase_native_read", "()Ljava/lang/String;", (void*)jni_read }, { "invcase_native_write", "(Ljava/lang/String;)V", (void*)jni_write }, }; register_android_server_InvcaseService(method_table);
-
onload.cpp
Register mapped function callsnamespace android { + int register_android_server_InvcaseService(JNIEnv *env); }; extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) { + register_android_server_InvcaseService(env); }
-
Android.bp
cc_library_static { srcs: [ + "com_android_server_invcase_InvcaseService.cpp", ], } cc_defaults { shared_libs: [ + "libinvcase", ], }
-
-
-
-
java
- com
- android
- server
-
SystemServer.java
Start Invcase Service+ import com.android.server.invcase.InvcaseService; public final class SystemServer implements Dumpable { private void startBootstrapServices(@NonNull TimingsTraceAndSlog t) { + traceBeginAndSlog("StartInvcaseService"); + mSystemServiceManager.startService(InvcaseService.class); + traceEnd(); } }
-
- server
- android
- com
-
-
-
hardware
-
libhardware_legacy
-
include
- hardware_legacy
-
invcase.h
Declare APIsinvcase_read(); invcase_write();
Wrap with
extern "C" {}
when using C language
-
- hardware_legacy
-
invcase
-
invase.c
Implement APIs using the device fileinvcase_read() { read("/dev/invcase"); } invcase_write() { write("/dev/invcase"); }
-
Android.bp
cc_library_shared { name: "libinvcase" }
-
test
- invcase_legacy_test.c
- Android.bp
-
-
-
-
HAL Library#
Define headers:
Declare APIs:
invcase_read
invcase_write
#ifndef INVCASE_LEGACY_H
#define INVCASE_LEGACY_H
#include <sys/types.h>
#ifdef __cplusplus
extern "C" {
#endif
#define INVCASE_BUFFER_MAX_SIZE 1024
int invcase_read(char *buf, size_t count);
int invcase_write(char *buf, size_t count);
#ifdef __cplusplus
}
#endif
#endif /* INVCASE_LEGACY_H */
Implement the library:
- Include
utils/Log.h
to useALOGD
,ALOGE
macros which will print out to Logcat - Implement the APIs which return
0
when succeeded
#define LOG_TAG "Invcase"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <utils/Log.h>
#include "hardware_legacy/invcase.h"
#define INVCASE_DEVICE_FILE "/dev/invcase"
void __attribute__ ((constructor)) invcase_loaded() {
ALOGD("Legacy HAL Module: Loaded");
}
void __attribute__ ((destructor )) invcase_unloaded() {
ALOGD("Legacy HAL Module: Unloaded");
}
int invcase_write(char *buf, size_t count) {
int fd;
int ret;
fd = open(INVCASE_DEVICE_FILE, O_WRONLY);
if (fd < 0) {
ALOGE("Unable to open %s to write\n", INVCASE_DEVICE_FILE);
return fd;
}
ret = write(fd, buf, count);
if (ret < 0) {
ALOGE("Unable to write to %s\n", INVCASE_DEVICE_FILE);
return ret;
}
ALOGD("invcase_write: buf= %s\n", buf);
close(fd);
return 0;
}
int invcase_read(char *buf, size_t count) {
int fd;
int ret;
fd = open(INVCASE_DEVICE_FILE, O_RDONLY);
if (fd < 0) {
ALOGE("Unable to open %s to read\n", INVCASE_DEVICE_FILE);
return fd;
}
ret = read(fd, buf, count);
if (ret < 0) {
ALOGE("Unable to read from %s\n", INVCASE_DEVICE_FILE);
return ret;
}
ALOGD("invcase_read: buf= %s\n", buf);
close(fd);
return 0;
}
Build the shared library libinvcase.so
:
cc_library_shared {
name: "libinvcase",
srcs: [
"invcase.c"
],
cflags: [
"-Wall",
"-Werror"
],
header_libs: [
"libhardware_legacy_headers"
],
shared_libs: [
"liblog",
"libcutils",
],
}
A test application that uses the HAL shared library:
#include <stdio.h>
#include <string.h>
#include "hardware_legacy/invcase.h"
int main()
{
char buffer[INVCASE_BUFFER_MAX_SIZE];
printf("InvCase Legacy HAL Test App\n");
printf("Input a string: ");
scanf("%s", buffer);
invcase_write(buffer, strlen(buffer));
invcase_read(buffer, INVCASE_BUFFER_MAX_SIZE);
printf("Converted string: ");
printf("%s\n", buffer);
return 0;
}
Build the test app:
cc_binary {
name: "invcase_legacy_test",
srcs: [
"invcase_legacy_test.c"
],
shared_libs: [
"libinvcase"
],
}
Include HAL Library and the Test app to the system packages:
+ PRODUCT_PACKAGES += \
+ libinvcase \
+ invcase_legacy_test
This will include below files to system:
/system/lib/libinvcase.so
/system/lib64/libinvcase.so
/system/bin/invcase_legacy_test
Rebuild the system image, run the emulator and run the test app:
JNI Wrapper#
The Java Native Interface (JNI) is a foreign function interface programming framework that enables Java code running in a Java virtual machine (JVM) to call and be called by native applications (programs specific to a hardware and operating system platform) and libraries written in other languages such as C, C++ and assembly.
-
Create java functions that call to native function in HAL library
jni_read
calls toinvcase_read
jni_write
calls toinvcase_write
-
Register mapped functions with their signatures (encoded parameters)
-
Note that Java functions always have 2 default arguments:
JNIEnv* env
: a structure containing methods that we can use our native code to access Java elementsjobject selfClass
: the class of calling object
Implement JNI Mapping
Note that the function jniRegisterNativeMethods
will register JNI functions for the class frameworks/services/core/java/com/android/server/invcase/InvcaseService.java
:
#define LOG_TAG "Invcase"
#include "jni.h"
#include <nativehelper/JNIHelp.h>
#include "android_runtime/AndroidRuntime.h"
#include <utils/misc.h>
#include <utils/Log.h>
#include <stdio.h>
#include <hardware_legacy/invcase.h>
namespace android
{
static void jni_init (
JNIEnv* /* env */,
jobject /* clazz */
) {
}
static void jni_deinit (
JNIEnv* /* env */,
jobject /* clazz */
) {
}
static jstring jni_read (
JNIEnv* env,
jobject /* clazz */
) {
char buff[INVCASE_BUFFER_MAX_SIZE];
int err = -1;
ALOGD("JNI: invcase_read: %p\n", invcase_read);
err = invcase_read(buff, INVCASE_BUFFER_MAX_SIZE);
if (err != 0) {
ALOGE("JNI: Can not read from device\n");
}
ALOGD("JNI: jni_read: %s\n", buff);
return env->NewStringUTF(buff);
}
static void jni_write (
JNIEnv* env,
jobject /* clazz */,
jstring string
) {
const char *buff = env->GetStringUTFChars(string, NULL);
int length = env->GetStringLength(string);
int err = -1;
ALOGD("JNI: jni_write: %s length= %d\n", buff, length);
ALOGD("JNI: invcase_write: %p\n", invcase_write);
err = invcase_write((char *)buff, length);
if (err != 0) {
ALOGE("JNI: Can not write to device\n");
}
}
static const JNINativeMethod method_table[] = {
{ "invcase_native_init", "()V", (void*)jni_init },
{ "invcase_native_deinit", "()V", (void*)jni_deinit },
{ "invcase_native_read", "()Ljava/lang/String;", (void*)jni_read },
{ "invcase_native_write", "(Ljava/lang/String;)V", (void*)jni_write },
};
int register_android_server_InvcaseService(JNIEnv *env) {
ALOGD("JNI: register_android_server_InvcaseService\n");
return jniRegisterNativeMethods(
env,
"com/android/server/invcase/InvcaseService",
method_table,
NELEM(method_table)
);
}
}
Call the register function:
namespace android {
+ int register_android_server_InvcaseService(JNIEnv *env);
};
extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
+ register_android_server_InvcaseService(env);
}
Build new JNI wrapper:
cc_library_static {
name: "libservices.core",
srcs: [
+ "com_android_server_invcase_InvcaseService.cpp",
],
}
cc_defaults {
name: "libservices.core-libs",
shared_libs: [
+ "libinvcase",
],
Declare API:
+ package android.hardware.invcase {
+
+ public final class InvcaseManager {
+ method @NonNull public String getData();
+ method public void putData(@NonNull String);
+ }
+
+ }
Service and Manager#
Define a name for new Service
public abstract class Context {
@StringDef(suffix = { "_SERVICE" }, value = {
+ INVCASE_SERVICE,
})
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.hardware.invcase.InvcaseManager}.
+ *
+ * @see #getSystemService(String)
+ * @hide
+ */
+ public static final String INVCASE_SERVICE = "invcase";
Define the Service Interface
Use AIDL to describe public functions exported by the Service:
package android.hardware.invcase;
/**
* Invcase Manager interface
*
* {@hide}
*/
interface IInvcaseManager {
String getData();
void putData(String data);
}
Build AIDL:
java_defaults {
name: "framework-defaults",
installable: true,
srcs: [
+ "core/java/android/hardware/invcase/IInvcaseManager.aidl",
]
}
Implement the Service Manager
A Service Manager is the wrapper for the interface of the target Service which is obtained by calling to <Interface>.Stub.asInterface
.
User application will call to the functions of the Service Manager, not directly calling to the actual service interface.
package android.hardware.invcase;
import android.annotation.NonNull;
import android.content.Context;
import android.util.Log;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
public final class InvcaseManager {
static final String TAG = "Invcase";
private final Context mContext;
private final IInvcaseManager mService;
/**
* Creates a InvcaseManager.
*
* @hide
*/
public InvcaseManager(@NonNull Context context) throws ServiceNotFoundException {
this(context, IInvcaseManager.Stub.asInterface(
ServiceManager.getServiceOrThrow(Context.INVCASE_SERVICE)));
}
/**
* Creates a InvcaseManager with a provided service implementation.
*
* @hide
*/
public InvcaseManager(@NonNull Context context, @NonNull IInvcaseManager service) {
mContext = context;
mService = service;
Log.d(TAG, "InvcaseManager: mContext= " + mContext + " mService= " + mService);
}
public @NonNull String getData() {
try {
String str = mService.getData();
Log.d(TAG, "InvcaseManager: mService.getData= " + str);
return str;
} catch (RemoteException e) {
e.printStackTrace();
}
return null;
}
public void putData(@NonNull String data) {
try {
Log.d(TAG, "InvcaseManager: mService.putData= " + data);
mService.putData(data);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
Implement the Service
The Service will implement the actual code for Service Interface functions by extending the <Interface>.Stub
class.
Note that JNI Native functions are exported to this object, therefore, it can call to HAL library’s functions.
package com.android.server.invcase;
import android.hardware.invcase.IInvcaseManager;
import android.content.Context;
import android.util.Log;
import com.android.server.SystemService;
public class InvcaseService extends SystemService {
static final String TAG = "Invcase";
static final boolean DEBUG = false;
final IInvcaseManagerImpl mManagerService;
private final class IInvcaseManagerImpl extends IInvcaseManager.Stub {
@Override
public String getData() {
String str = invcase_native_read();
Log.d(TAG, "InvcaseService: IInvcaseManager.getData= " + str);
return str;
}
@Override
public void putData(String data) {
Log.d(TAG, "InvcaseService: IInvcaseManager.putData= " + data);
invcase_native_write(data);
}
}
public InvcaseService(Context context) {
super(context);
invcase_native_init();
mManagerService = new IInvcaseManagerImpl();
Log.d(TAG, "InvcaseService: mManagerService= " + mManagerService);
}
@Override
public void onStart() {
publishBinderService(Context.INVCASE_SERVICE, mManagerService);
Log.d(TAG, "InvcaseService: onStart");
}
@Override
protected void finalize() throws Throwable {
invcase_native_deinit();
super.finalize();
}
private static native void invcase_native_init();
private static native void invcase_native_deinit();
private static native String invcase_native_read();
private static native void invcase_native_write(String string);
}
Run our service:
All system services are run in the same process called system_server
which is implemented in the SystemServer.java
. This process runs under system
user.
import com.android.server.invcase.InvcaseService;
import android.util.Log;
public final class SystemServer implements Dumpable {
private void startBootstrapServices(@NonNull TimingsTraceAndSlog t) {
+ // Manages Invcase device.
+ traceBeginAndSlog("StartInvcaseService");
+ Log.d("Invcase", "SystemServer: start InvcaseService");
+ mSystemServiceManager.startService(InvcaseService.class);
+ traceEnd();
User App#
The User App will be very simple to test the hardware. It contains an EditText
to get user input, a Button
to execute commands, and a TextView
to display the result.
Implement the User App
- Use
getSystemService(Context.INVCASE_SERVICE)
to obtain the instance ofInvcaseManager
- Call to hardware through the
InvcaseManager
APIs
package com.invcase;
import android.hardware.invcase.InvcaseManager;
import android.content.Context;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.util.Log;
public class Invcase extends Activity {
private static final String TAG = "Invcase";
private InvcaseManager mManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mManager = (InvcaseManager)getSystemService(Context.INVCASE_SERVICE);
Log.d(TAG, "App: mManager= " + mManager);
Button btn = (Button)findViewById(R.id.button);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
EditText editText = (EditText)findViewById(R.id.editText);
String txt = editText.getText().toString();
Log.d(TAG, "App: request= " + txt);
mManager.putData(txt);
String ret = mManager.getData();
Log.d(TAG, "App: received= " + ret);
TextView tv = (TextView)findViewById(R.id.textView);
tv.setText(ret);
}
});
}
}
Add User App to the Launcher
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.invcase" >
<application
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" >
<activity
android:name=".Invcase"
android:exported="true"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
on Android 12, must use
android:exported="true"
Build User App
android_app {
name: "Invcase",
platform_apis: true,
srcs: [
"src/**/*.java"
]
}
Add User App to system packages:
+ PRODUCT_PACKAGES += \
+ Invcase
Permission#
The device /dev/invcase
is created at boot with root permission.
The HAL Library is loaded when JNI Wrapper for Invcase Service is run, therefore, HAL code will run with system
permission which attaches to the system_server
process.
The Android Init Language uses init*.rc
files to automatically do some actions when a condition is met.
For the target hardware, Android Init will use init.<hardware>.rc
file. On Emulator, it is init.ranchu.rc
.
Add below lines to change the owner and permission on the /dev/invcase
at boot:
+ on boot
+ chown system system /dev/invcase
+ chmod 0600 /dev/invcase
Build and Run#
The Invcase Manager exports new Service APIs, therefore, need to rebuild the list of system APIs.
m all -j$(nproc)
Run the Emulator and run Invcase app:
Start the Logcat to see debug lines:
logcat -s Invcase
There are 2 processes:
-
The
system_server
process (yellow) does below things:- Load HAL module
- Load JNI functions
- Start Invcase Service whichs creates an object of Invcase Manager Implementation
-
The user app process (white) does below things:
- Load System Service Registry to have an object of Invcase Manager Interface
- Use received service interface to communicate with Invcase Manager Implementation in the Invcase Service