Skip to content



Android Automotive »

Conventional 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-07-14


This guide is written for AOSP android-10.0.0_r47

Conventional HAL#

Conventional HALs (deprecated in Android 8.0) are interfaces that conform to a specific named and versioned application binary interface (ABI). To guarantee that HALs have a predictable structure, each hardware-specific HAL interface has properties defined in hardware.h. This interface allows the Android system to load correct versions of your HAL modules consistently.

The bulk of Android system interfaces (camera, audio, sensors, etc.) are in the form of conventional HALs, which are defined under hardware/libhardware/include/hardware.

A Conventional HAL interface consists of two components: modules and devices.

HAL Module#

The struct hw_module_t represents a module and contains metadata such as the version, name, and author of the module. Android uses this metadata to find and load the HAL module correctly.

hardware/libhardware/include/hardware/hardware/h
typedef struct hw_module_t {
    uint32_t tag; // must be init to HARDWARE_MODULE_TAG 
    uint16_t module_api_version;
    uint16_t hal_api_version;
    const char *id;
    const char *name;
    const char *author;
    struct hw_module_methods_t* methods;
    void* dso;
} hw_module_t;

In addition, the hw_module_t struct contains a pointer to another struct, hw_module_methods_t, that contains a pointer to an open function for the module. This open function is used to initiate communication with the hardware for which the HAL is serving as an abstraction.

hardware/libhardware/include/hardware/hardware/h
typedef struct hw_module_methods_t {
    int (*open)(const struct hw_module_t* module, const char* id,
            struct hw_device_t** device);
} hw_module_methods_t;

Each hardware-specific HAL usually extends the generic hw_module_t struct with additional information for that specific piece of hardware. For example, adding to 2 functions read and write:

hardware/libhardware/include/hardware/hardware/h
typedef struct my_module {
    hw_module_t common;
    int (*read)(void);
    int (*write)(int data);
} my_module_t;

When you implement a HAL and create the module struct, you must name it HAL_MODULE_INFO_SYM and fill all the fields:

struct my_module HAL_MODULE_INFO_SYM = {
    .common = {
        .tag = HARDWARE_MODULE_TAG,
        .module_api_version = 1,
        .hal_api_version = 0,
        .id = MY_MODULE_ID,
        .name = "My Module",
        .author = "Me",
        .methods = &my_hw_module_methods,
    },
};

HAL device#

A device is represented by the hw_device_t struct. Similar to a module, each type of device defines a detailed version of the generic hw_device_t that contains function pointers for specific features of the hardware.

hardware/libhardware/include/hardware/hardware/h
typedef struct hw_device_t {
    uint32_t tag; // must be init to HARDWARE_DEVICE_TAG
    uint32_t version;
    struct hw_module_t* module;
    /** Close this device */
    int (*close)(struct hw_device_t* device);
} hw_device_t;
typedef struct my_device {
    struct hw_device_t common;
    uint32_t (*get_id)();
} my_device_t;

Access to HAL#

HAL implementations are built into modules (.so) files and are dynamically linked by Android when appropriate. You can build your modules by creating Android.mk files for each of your HAL implementations and pointing to your source files. In general, your shared libraries must be named in a specific format, so they can be found and loaded properly.

The naming scheme varies slightly from module to module, but follows the general pattern of: <device_name>.<module_type>.

Using the libhardware library, you can open a HAL library by following below steps:

  1. Call to hw_get_module(char* id, struct hw_module_t ** module) to get a pointer pointing to the target module

  2. Call module->common.methods->open() function to get a pointer pointing to the target device

  3. Use the device pointer to call to target specific functions, such as read() or write()

Framework Stacks#

Differences between Legacy HAL and Conventional HAL:

Legacy HAL in the Framework stacks

Conventional HAL in the Framework stacks

Implementation#

conventional_hal.zip

Refer to Kernel Module to build and install a module to system. This guide assumes that invcase module is loaded as below:

device/generic/goldfish/init.ranchu.rc
+ 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 += \
              +     invcase.default \
              +     invcase_conventional_test
              +     Invcase
              
    • packages

      • apps
        • Invcase

          • src

            • com
              • invcase
                • Invcase.java

                  Get InvcaseManager from SystemService
                  class Invcase extends Activity {
                      InvcaseManager mManager;
                      onCreate() {
                          mManager = getSystemService(Context.INVCASE_SERVICE);
                      }
                  }
                  
          • 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

    • 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 name
                  public abstract class Context {
                  +     public static final String INVCASE_SERVICE = "invcase";
                      @StringDef(suffix = { "_SERVICE" }, value = {
                  +         INVCASE_SERVICE,
                      })
                  }
                  
              • hardware

                • invcase

                  • IInvcaseManager.aidl

                    Define API for Invcase Interface
                    interface IInvcaseManager {
                        getData();
                        putData();
                    }
                    
                  • InvcaseManager.java

                    Use Invase Serive through the Invcase Interface
                    class 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(); }
                    }
                    
        • services

          • core

            • java

              • com
                • android
                  • server
                    • invcase
                      • InvcaseService.java

                        Implement Invcase Interface functions, public the interface
                        class InvcaseService extends SystemService {
                            IInvcaseManagerImpl extends IInvcaseManager.Stub {
                                getData() { invcase_native_read(); }
                                putData() { invcase_native_write(); }
                            } mManagerService;
                            onStart() {
                                publishBinderService(Context.INVCASE_SERVICE, mManagerService);
                            }
                        }
                        
            • jni

              • com_android_server_invcase_InvcaseService.cpp

                Map java functions to native functions
                #include <hardware/hardware.h>
                #include <hardware/invcase.h>
                jni_init () { device = hw_get_module()->open(); }
                jni_deinit () { free(device); }
                jni_read () { device->read(); }
                jni_write () { device->write(); }
                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 },
                };
                register_android_server_InvcaseService(method_table);
                
              • onload.cpp

                Register mapped function calls
                namespace 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",
                    ],
                }
                
        • 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();
                      }
                  }
                  
    • hardware

      • libhardware

        • include

          • hardware
            • invcase.h

              Declare APIs
              #include <hardware/hardware.h>  // hw_module_t, hw_device_t
              struct invcase_module_t {
                  struct hw_module_t common;
              };
              struct invcase_device_t {
                  struct hw_device_t common;
                  ssize_t (*write)(char *, size_t);
                  ssize_t (*read)(char *, size_t);
              };
              

              Wrap with __BEGIN_DECLS and __END_DECLS when using C language

        • modules

          • Android.mk

            Add module to build slots
            hardware_modules := \
                camera \
                gralloc \
                sensors \
            +     invcase
            
          • invcase

            • invase.c

              Implement APIs using the device file
              dev_open() {device = new invcase_device_t {
                  .write = dev_write;
                  .read = dev_read;
              } }
              dev_close() {free(device) }
              dev_read() { read("/dev/invcase"); }
              dev_write() { write("/dev/invcase"); }
              hw_module_methods_t invcase_module_methods = {
                  .open = dev_open,
              };
              struct invcase_module_t HAL_MODULE_INFO_SYM = {
                  .common = {
                      .tag = HARDWARE_MODULE_TAG,
                      .id = INVCASE_HARDWARE_MODULE_ID,
                      .methods = &invcase_module_methods,
                  },
              };
              
            • Android.bp

              cc_library_shared { name: "invcase.default" }
              
        • tests

          • invcase
            • invcase_conventional_test.c
            • Android.bp

HAL Library#

Define headers:

Declare structures:

  • invcase_module_t
  • invcase_device_t
hardware/libhardware/include/hardware/invcase.h
#ifndef INVCASE_CONVENTIONAL_H
#define INVCASE_CONVENTIONAL_H

#include <sys/types.h>          // size_t, ssize_t
#include <hardware/hardware.h>  // hw_module_t, hw_device_t

/* Use this for C++ */
__BEGIN_DECLS

#define INVCASE_HARDWARE_MODULE_ID  "invcase"   // must be the same as the header file
#define INVCASE_DEBUG               "invcase: "
#define INVCASE_BUFFER_MAX_SIZE     1024

struct invcase_module_t {
    struct hw_module_t common;
};

struct invcase_device_t {
    struct hw_device_t common;
    ssize_t (*write)(char *, size_t);
    ssize_t (*read)(char *, size_t);
};

__END_DECLS

#endif /* INVCASE_CONVENTIONAL_H */

Implement the library:

  • Include utils/Log.h to use ALOGD, ALOGE macros which will print out to Logcat
  • Must create the structure HAL_MODULE_INFO_SYM
  • Implement functions and assign to the module structures
    • dev_open to initialize the device structure
    • dev_read and dev_write access to the device file
hardware/libhardware/modules/invcase/invcase.c
#define LOG_TAG "Invcase"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <utils/Log.h>
#include "hardware/invcase.h"

#define INVCASE_DEVICE_FILE "/dev/invcase"

static int dev_open(const struct hw_module_t *module, const char *name, struct hw_device_t **device);
static int dev_close(struct hw_device_t* device);
static ssize_t dev_write(char *buf, size_t count);
static ssize_t dev_read(char *buf, size_t count);

/* defined in hardware/hardware.h */
static struct hw_module_methods_t invcase_module_methods = {
    .open = dev_open,
};


/* every module must have HAL_MODULE_INFO_SYM */
struct invcase_module_t HAL_MODULE_INFO_SYM = {
    .common = {
        .tag = HARDWARE_MODULE_TAG,
        .version_major = 1,
        .version_minor = 0,
        .id = INVCASE_HARDWARE_MODULE_ID,
        .name = INVCASE_HARDWARE_MODULE_ID,
        .author = "vqtrong",
        .methods = &invcase_module_methods,
    },
};

struct invcase_device_t *invcase_dev;

static void __attribute__ ((constructor)) dev_loaded() {
    ALOGD("Conventional HAL Module: Loaded");
}

static void __attribute__ ((destructor )) dev_unloaded() {
    ALOGD("Conventional HAL Module: Unloaded");
}

static int dev_open(
    const struct hw_module_t *module, 
    const char *id, 
    struct hw_device_t **device
) {
    // use calloc to initialize memory with 0
    invcase_dev = (struct invcase_device_t *) calloc(1, sizeof(struct invcase_device_t));
    if(invcase_dev == NULL) {
        ALOGE("Conventional HAL Module: Unable to calloc, %s, ID: %s\n", strerror(errno), id);
        return -ENOMEM;
    }

    invcase_dev->common.tag = HARDWARE_MODULE_TAG;
    invcase_dev->common.version = 1;
    invcase_dev->common.module = (struct hw_module_t *)module;
    invcase_dev->common.close = dev_close;
    invcase_dev->write = dev_write;
    invcase_dev->read = dev_read;

    *device = (struct hw_device_t *)invcase_dev;
    ALOGD("Conventional HAL Module: Device %s is initialized\n", id);

    return 0;
}

static int dev_close(struct hw_device_t *device) {
    if(device) {
        free(device);
        ALOGD("Conventional HAL Module: free device\n");
    }
    return 0;
}

static ssize_t dev_write(char *buf, size_t count) {
    int fd;
    int ret;

    fd = open(INVCASE_DEVICE_FILE, O_WRONLY);
    if (fd < 0) {
        ALOGE("Conventional HAL Module: Unable to open %s to write\n", INVCASE_DEVICE_FILE);
        return fd;
    }

    ret = write(fd, buf, count);
    if (ret < 0) {
        ALOGE("Conventional HAL Module: Unable to write to %s\n", INVCASE_DEVICE_FILE);
        return ret;
    }

    ALOGD("Conventional HAL Module: invcase_write: buf= %s\n", buf);
    close(fd);
    return 0;
}

static ssize_t dev_read(char *buf, size_t count) {
    int fd;
    int ret;

    fd = open(INVCASE_DEVICE_FILE, O_RDONLY);
    if (fd < 0) {
        ALOGE("Conventional HAL Module: Unable to open %s to read\n", INVCASE_DEVICE_FILE);
        return fd;
    }

    ret = read(fd, buf, count);
    if (ret < 0) {
        ALOGE("Conventional HAL Module: Unable to read from %s\n", INVCASE_DEVICE_FILE);
        return ret;
    }

    ALOGD("Conventional HAL Module: invcase_read: buf= %s\n", buf);
    close(fd);
    return 0;
}

Build the module library invcase.default.so:

hardware/libhardware/modules/invcase/Android.bp
// This default implementation is loaded if no other device specific modules are
// present. The exact load order can be seen in libhardware/hardware.c
// The format of the name is invcase.<hardware>.so

cc_library_shared {
    name: "invcase.default",
    relative_install_path: "hw",
    srcs: [
        "invcase.c"
    ],
    cflags: [
        "-Wall", 
        "-Werror"
    ],
    header_libs: [
        "libhardware_headers"
    ],
    shared_libs: [
        "liblog",
        "libcutils",
    ],
}

A test application that uses the HAL module library:

hardware/libhardware/tests/invcase/invcase_conventional_test.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <hardware/invcase.h>

int main() {

    int err;
    struct invcase_module_t *module = NULL;
    struct invcase_device_t *device = NULL;
    char buffer[INVCASE_BUFFER_MAX_SIZE];

    printf("InvCase Conventional HAL Test App\n");

    err =  hw_get_module(
        INVCASE_HARDWARE_MODULE_ID, 
        (struct hw_module_t const**) &module
    );

    if (err != 0) {
        printf("hw_get_module() failed: (%s)\n", strerror(-err));
        return -1;
    } else {
        printf("hw_get_module() OK\n");
        if (module->common.methods->open(
                    (struct hw_module_t *) module, 
                    INVCASE_HARDWARE_MODULE_ID, 
                    (struct hw_device_t **) &device
                ) != 0) {
            printf("HAL failed to open! (%s)\n",strerror(-err));
            return -1;
        } else {
            printf("hw_get_module() Open: OK\n");
        }
    }

    printf("Input a string: ");
    scanf("%s", buffer);

    err = device->write(buffer, strlen(buffer));
    if (err != 0) {
        printf("HAL failed to write! (%s)\n", strerror(-err));
    }

    err = device->read(buffer, INVCASE_BUFFER_MAX_SIZE);
    if (err != 0) {
        printf("HAL failed to read! (%s)\n", strerror(-err));
    }

    printf("Converted string: ");
    printf("%s\n", buffer);

    return 0;
}

Build the test app:

hardware/libhardware/tests/invcase/Android.bp
cc_binary {
    name: "invcase_conventional_test",
    srcs: [
        "invcase_conventional_test.c"
    ],
    shared_libs: [
        "liblog",
        "libhardware",
    ],
    cflags: [
        "-Wall",
        "-Werror",
        "-Wextra",
    ],
}

Include HAL Library and the Test app to the system packages:

build/target/product/base_vendor.mk
+ PRODUCT_PACKAGES += \
+     invcase.default \
+     invcase_conventional_test

This will include below files to system:

/system/lib/hw/invcase.default.so
/system/lib64/hw/invcase.default.so
/system/bin/invcase_conventional_test

Rebuild the system image, run the emulator and run the test app:

Conventional HAL 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 to invcase_read
    • jni_write calls to invcase_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 elements
    • jobject 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:

frameworks/base/services/core/jni/com_android_server_invcase_InvcaseService.cpp
#define LOG_TAG "Invcase"
// #define INVCASE_USE_CONVENTIONAL_HAL

#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/hardware.h>
#include <hardware/invcase.h>

namespace android
{
    static struct invcase_module_t *module = NULL;
    static struct invcase_device_t *device = NULL;

    static void jni_init (
        JNIEnv* /* env */,
        jobject /* clazz */
    ) {
        int err;

        err =  hw_get_module(
            INVCASE_HARDWARE_MODULE_ID, 
            (struct hw_module_t const**) &module
        );

        if (err != 0) {
            ALOGE("JNI: hw_get_module() failed: (%s)\n", strerror(-err));
            return;
        } else {
            ALOGD("JNI: hw_get_module() OK\n");
            if (module->common.methods->open(
                        (struct hw_module_t *) module, 
                        INVCASE_HARDWARE_MODULE_ID, 
                        (struct hw_device_t **) &device
                    ) != 0) {
                ALOGE("JNI: HAL failed to open! (%s)\n",strerror(-err));
                return;
            } else {
                ALOGD("JNI: hw_get_module() Open: OK\n");
            }
        }
    }

    static void jni_deinit (
        JNIEnv* /* env */,
        jobject /* clazz */
    ) {
        if (device) {
            free(device);
            ALOGD("JNI: Free device\n");
        }
    }

    static jstring jni_read (
        JNIEnv* env,
        jobject /* clazz */
    ) {
        char buff[INVCASE_BUFFER_MAX_SIZE];
        int err = -1;

        if(device) {
            ALOGD("JNI: device->read: %p\n", device->read);
            err = device->read(buff, INVCASE_BUFFER_MAX_SIZE);
        } else {
            ALOGE("JNI: Device is NULL\n");
        }

        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);

        if(device) {
            ALOGD("JNI: device->write: %p\n", device->write);
            err = device->write((char *)buff, length);
        } else {
            ALOGE("JNI: Device is NULL\n");
        }

        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:

frameworks/base/services/core/jni/onload.cpp
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:

frameworks/base/services/core/jni/Android.bp
cc_library_static {
    name: "libservices.core",
    srcs: [
+         "com_android_server_invcase_InvcaseService.cpp",
    ],
}

cc_defaults {
    name: "libservices.core-libs",
    shared_libs: [
+         "libinvcase",
    ],

Declare API:

frameworks/base/api/current.txt
+ 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

frameworks/base/core/java/android/content/Context.java
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:

frameworks/base/core/java/android/hardware/invcase/IInvcaseManager.aidl
package android.hardware.invcase;

/**
 * Invcase Manager interface
 *
 * {@hide}
 */
interface IInvcaseManager {
    String getData();
    void putData(String data);
}

Build AIDL:

frameworks/base/Android.bp
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.

frameworks/base/core/java/android/hardware/invcase/InvcaseManager.java
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.

frameworks/base/services/core/java/com/android/server/invcase/InvcaseService.java
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.

frameworks/base/services/java/com/android/server/SystemServer.java
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 of InvcaseManager
  • Call to hardware through the InvcaseManager APIs
packages/apps/Invcase/src/com/invcase/Invcase.java
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

packages/apps/Invcase/AndroidManifest.xml
<?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

packages/apps/Invcase/Android.bp
android_app {
    name: "Invcase",
    platform_apis: true,
    srcs: [
        "src/**/*.java"
    ]
}

Add User App to system packages:

build/target/product/base_vendor.mk
+ 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:

device/generic/goldfish/init.ranchu.rc
+ 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:

The User Test application

Start the Logcat to see debug lines:

logcat -s Invcase

Logcat shows Invcase calls

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

Comments