L'intégration des interfaces Hardware Abstraction Layer (HAL) à l'aide d'Android Interface Definition Language (AIDL) est une pratique recommandée dans les versions récentes d'Android, remplaçant progressivement HIDL. AIDL offre une approche unifiée pour la communication inter-processus (IPC), que ce soit entre des composants du framework Android, des applications, ou directement avec les couches HAL. Cette uniformité simplifie le développement et la maintenance, car un seul mécanisme IPC est utilisé. De plus, le système de contrôle de version d'AIDL est considéré comme plus robuste que celui de HIDL.
L'utilisation d'AIDL pour les HAL, particulièrement dans les interactions entre le framework et les fournisseurs (vendor), nécessite l'emploi de "Stable AIDL". Ceci garantit la stabilité des interfaces malgré les cycles de mise à jour divergents entre le système et le matériel.
Voici les étapes pour implémenter une interface HAL personnalisée en utilisant AIDL :
- Définition de l'interface HAL Créez un répertoire pour votre interfaec, par exemple :
/hardware/interfaces/myhal/aidl/. Dans ce répertoire, définissez l'interface AIDL dans un fichier.aidl, par exemple :/hardware/interfaces/myhal/aidl/android/hardware/myhal/IMyHal.aidl.
package android.hardware.myhal;
@VintfStability
interface IMyHal {
String getHardwareName();
void setConfiguration(in String config);
}
Il est crucial d'annoter chaque définition de type avec @VintfStability. AIDL supporte des types de données comme les énumérations, les structures et les parcelable, bien que leur disponibilité puisse varier selon la version d'Android.
- Configuration du fichier Android.bp Créez un fichier
Android.bpà la racine du répertoire de votre interface :/hardware/interfaces/myhal/aidl/Android.bp.
aidl_interface {
name: "android.hardware.myhal",
vendor: true,
srcs: ["android/hardware/myhal/*.aidl"],
stability: "vintf",
owner: "mycompany",
backend: {
cpp: {
enabled: false, // Désactiver le backend C++ si vous utilisez NDK
},
java: {
enabled: false, // Désactiver le backend Java si vous utilisez NDK
},
// Utiliser le backend NDK comme recommandé par Google
ndk: {
enabled: true,
},
},
}
Dans cet exemple, le backend ndk est activé, tandis que cpp et java sont désactivés. L'option vendor: true indique qu'il s'agit d'une interface spécifique au fournisseur.
- Compilasion du module Compilez votre module en utilisant la commande
mmm:
mmm hardware/interfaces/myhal/
Si vous rencontrez une erreur indiquant que le dump de l'API n'existe pas, exécutez la commande suggérée pour générer ou mettre à jour les définitions d'API :
m android.hardware.myhal-update-api
Après cette étape, relancez la compilation du module.
- Implémentation de l'interface HAL Une fois la compilation réussie, le code source pour le backend NDK sera généré. Vous pouvez trouver ces fichiers générés dans le répertoire de sortie de la compilation (par exemple,
out/soong/.intermediates/hardware/interfaces/myhal/aidl/android.hardware.myhal-V1-ndk-source).
Le fichier d'en-tête généré (IHardware.h) contiendra les déclarations des méthodes à implémenter :
// Extrait de IMyHal.h généré
virtual ::ndk::ScopedAStatus getHardwareName(std::string* _aidl_return) = 0;
virtual ::ndk::ScopedAStatus setConfiguration(const std::string& in_config) = 0;
Créez les fichiers source pour implémenter ces méthodes. Par exemple, dans /hardware/interfaces/myhal/aidl/default/MyHal.h :
#pragma once
#include <aidl/android/hardware/myhal/BnMyHal.h>
#include <string>
namespace aidl {
namespace android {
namespace hardware {
namespace myhal {
class MyHal : public BnMyHal {
public:
::ndk::ScopedAStatus getHardwareName(std::string* _aidl_return) override;
::ndk::ScopedAStatus setConfiguration(const std::string& in_config) override;
private:
std::string hardwareName = "DefaultHardware";
std::string currentConfig = "";
};
} // namespace myhal
} // namespace hardware
} // namespace android
} // namespace aidl
Et dans /hardware/interfaces/myhal/aidl/default/MyHal.cpp :
#include <android-base/logging.h>
#include "MyHal.h"
#define LOG_TAG "MyHalService"
namespace aidl {
namespace android {
namespace hardware {
namespace myhal {
::ndk::ScopedAStatus MyHal::getHardwareName(std::string* _aidl_return) {
*_aidl_return = hardwareName;
ALOGI("Returning hardware name: %s", hardwareName.c_str());
return ::ndk::ScopedAStatus::ok();
}
::ndk::ScopedAStatus MyHal::setConfiguration(const std::string& in_config) {
currentConfig = in_config;
ALOGI("Configuration set to: %s", currentConfig.c_str());
return ::ndk::ScopedAStatus::ok();
}
} // namespace myhal
} // namespace hardware
} // namespace android
} // namespace aidl
- Implémentation du service Créez le point d'entrée du service dans un fichier
main.cpp, par exemple :/hardware/interfaces/myhal/aidl/default/main.cpp.
#include <android-base/logging.h>
#include <android/binder_manager.h>
#include <android/binder_process.h>
#include <binder/ProcessState.h>
#include "MyHal.h"
using aidl::android::hardware::myhal::MyHal;
using std::string_literals::operator""s;
int main() {
// Initialiser le processus Binder pour les appels inter-processus du fournisseur
android::ProcessState::initWithDriver("/dev/vndbinder");
// Configurer le pool de threads Binder
ABinderProcess_setThreadPoolMaxThreadCount(4); // Exemple de configuration
ABinderProcess_startThreadPool();
// Créer et enregistrer l'instance du service HAL
std::shared_ptr<MyHal> myHalService = ndk::SharedRefBase::make<MyHal>();
const std::string instanceName = MyHal::descriptor + "/default"s;
if (myHalService) {
binder_status_t status = AServiceManager_addService(myHalService->asBinder().get(), instanceName.c_str());
if (status != STATUS_OK) {
ALOGE("Failed to register IMyHal service with instance '%s'", instanceName.c_str());
return -1;
}
ALOGI("IMyHal service registered successfully with instance '%s'", instanceName.c_str());
} else {
ALOGE("Failed to create MyHal service instance");
return -1;
}
// Attendre les appels Binder
ABinderProcess_joinThreadPool();
return EXIT_FAILURE; // Ne devrait pas être atteint
}
- Script de démarrage du service (.rc) Créez un fichier
.rcpour décrire comment le système doit démarrer votre service. Par exemple :/hardware/interfaces/myhal/aidl/default/android.hardware.myhal-service.rc.
service myhal-service /vendor/bin/hw/android.hardware.myhal-service
interface aidl android.hardware.myhal.IMyHal/default
class hal
user system
group system
- Déclaration VINTF Créez un fichier XML pour déclarer votre interface AIDL dans la matrice de cmopatibilité VINTF. Par exemple :
/hardware/interfaces/myhal/aidl/default/android.hardware.myhal-service.xml.
<manifest version="1.0" type="device">
<hal format="aidl">
<name>android.hardware.myhal</name>
<fqname>IMyHal/default</fqname>
</hal>
</manifest>
- Script de construction du service (Android.bp) Créez un fichier
Android.bppour le binaire du service :/hardware/interfaces/myhal/aidl/default/Android.bp.
cc_binary {
name: "android.hardware.myhal-service",
vendor: true,
relative_install_path: "hw",
init_rc: ["android.hardware.myhal-service.rc"],
vintf_fragments: ["android.hardware.myhal-service.xml"],
srcs: [
"MyHal.cpp",
"main.cpp",
],
cflags: [
"-Wall",
"-Werror",
],
shared_libs: [
"libbase",
"liblog",
"libhardware",
"libbinder_ndk", // Pour les interfaces AIDL NDK
"libbinder",
"libutils",
"android.hardware.myhal-V1-ndk", // Dépendance à l'interface AIDL générée
],
}
- Intégration dans le système Ajoutez votre service aux packages du produit dans le fichier
device.mkde votre appareil :
# Ajout pour MyHal
PRODUCT_PACKAGES += \
android.hardware.myhal \
android.hardware.myhal-service
Mettez à jour la matrice de compatibilité (compatibility_matrix.xml ou une version plus récente) pour inclure votre HAL :
<hal format="aidl" optional="true">
<name>android.hardware.myhal</name>
<version>1.0</version>
<interface>
<name>IMyHal</name>
<instance>default</instance>
</interface>
</hal>
Des ajustements de permissions SELinux peuvent être nécessaires pour permettre au service de fonctionner correctement.
- Client de test Un client peut être développé dans un processus utilisateur (application Android) ou dans le serveur système.
Pour un client Java dans une application :
// Dans une application Android
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
import android.hardware.myhal.IMyHal; // Importer l'interface générée
// ...
String serviceName = "android.hardware.myhal.IMyHal/default"; // Ou utiliser une constante définie
IBinder binder = ServiceManager.getService(serviceName);
if (binder == null) {
Log.e(TAG, "Service " + serviceName + " not found");
} else {
IMyHal myHal = IMyHal.Stub.asInterface(binder);
if (myHal == null) {
Log.e(TAG, "Failed to get IMyHal interface");
} else {
try {
String name = myHal.getHardwareName();
Log.d(TAG, "Hardware Name: " + name);
myHal.setConfiguration("new_config_value");
} catch (RemoteException e) {
Log.e(TAG, "RemoteException", e);
}
}
}
Notez que l'importation de l'interface (IMyHal) dépendra de la manière dont le JAR ou le package contenant l'interface AIDL générée est mis à disposition du projet de l'application.