My first time dealing with AIDL (and contributing to Lineage in the meantime)
So recently I got a bit interested in wanting to learn about some more Android internals and I thought a good way of doing that would be contributing to an AOSP ROM. I specifically wanted to do something with HALs (Hardware Abstraction Layers) as those are extremely crucial for communicating with hardware in Android. My first thought was to find something that was either broken or needed an update/improvement.
I came across the repo hardware/lineage/interfaces where a lot of Open Source community built HALs lie. The first thing I noticed was that a lot of these HALs were still using the old HIDL (Hardware Interface Definition Language) implementation instead of the newly adopted AIDL (Android Interface Definition Language) implementation.
I specifically got interested in the PowerShare (reverse wireless charging) implementation (because it was smaller and easier to interpret), I wanted to rewrite this HAL into AIDL but honestly during that time had no idea how I'd do that. But first I looked into the code and looked at the cpp implementation (Powershare.cpp) and tried to understand whats going on.
Return <bool> PowerShare::isEnabled() {
std::string value;
if (!ReadFileToString(POWERSHARE_PATH, &value)) {
LOG(ERROR) << "Failed to read current powershare value";
return false;
}
value = android::base::Trim(value);
return value == POWERSHARE_ENABLED;
}
Return<bool> PowerShare::setEnabled(bool enable) {
const auto& value = enable ? POWERSHARE_ENABLED : POWERSHARE_DISABLED;
WriteStringToFile(value, POWERSHARE_PATH, true);
return isEnabled();
}
This was the main implementation, it's pretty simple, I'm sure if you're reading this, you have already understood whats going on. But if you haven't, let me explain,
This basically writes '1' (POWERSHARE_ENABLED) or '0' (POWERSHARE_DISABLED) to a path (POWERSHARE_PATH). This path is different for every device and needs to be explicitly defined in a makefile, for my Pixel 7, the path is /sys/class/power_supply/wireless/device/rtx.
When '1' is written to that path, it enables the PowerShare function. Most of this is taken care of by the kernel, this HAL is basically just a toggle for the Kernel.
Now, the only thing I had to do was convert this HAL to AIDL. There were two other files as well, service.cpp which would register the HAL and the header Powershare.h that defined all the functions, those needed to be rewritten to a certain degree as well.
After a bit of diging up documentations and being code reviewed, I came up with this:
ndk::ScopedAStatus PowerShare::isEnabled(bool* _aidl_return) {
std::string value;
if (!ReadFileToString(POWERSHARE_PATH, &value)) {
LOG(ERROR) << "Failed to read current PowerShare state";
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
}
*_aidl_return = Trim(value) == POWERSHARE_ENABLED;
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus PowerShare::setEnabled(bool enable) {
std::string value = enable ? POWERSHARE_ENABLED : POWERSHARE_DISABLED;
if (!WriteStringToFile(value, POWERSHARE_PATH, true)) {
LOG(ERROR) << "Failed to write PowerShare state";
return ndk::ScopedAStatus::fromExceptionCode(EX_SERVICE_SPECIFIC);
}
return ndk::ScopedAStatus::ok();
}
The logic stays the same, but the syntax and format has changed. The other files had to be changed accordingly:
Powershare.h
// HIDL
class PowerShare : public IPowerShare {
public:
Return<bool> isEnabled() override;
Return<bool> setEnabled(bool enable) override;
Return<uint32_t>getMinBattery() override;
Return<uint32_t> setMinBattery(uint32_t minBattery) override;
};
// AIDL
class PowerShare : public BnPowerShare {
public:
ndk::ScopedAStatus isEnabled(bool* _aidl_return) override;
ndk::ScopedAStatus setEnabled(bool enable) override;
// Unused stubs
ndk::ScopedAStatus getMinBattery(int32_t* _aidl_return) override;
ndk::ScopedAStatus setMinBattery(int32_t minBattery) override;
};
setMinBattery() is now int32_t and getMinBattery() is now int32_t* instead of being void. isEnabled() is also a boolean now instead of being void.
service.cpp
// HIDL
int main() {
sp<IPowerShare> powerShareService = new PowerShare();
configureRpcThreadpool(1, true /*callerWillJoin*/);
if (powerShareService->registerAsService() != android::OK) {
LOG(ERROR) << "Can't register PowerShare HAL service";
return 1;
}
joinRpcThreadpool();
return 0; // should never get here
}
// AIDL
int main() {
ABinderProcess_setThreadPoolMaxThreadCount(0);
std::shared_ptr<IPowerShare> powershare = ndk::SharedRefBase::make<IPowerShare>();
const std::string instance = std::string(PowerShare::descriptor) + "/default";
binder_status_t status =
AServiceManager_addService(powershare->asBinder().get(), instance.c_str());
CHECK_EQ(status, STATUS_OK);
ABinderProcess_joinThreadPool();
return EXIT_FAILURE; // should not reach
}
This, in my opinion, has changed the most. Before it would just register the service on its own, but now we can do it ourselves and give it a proper descriptor.
You can take a look at how the format has changed. One benefit that AIDL has over HIDL is that developers can control the code all they want and are not restricted in pre-defined definitions.
This is only the 'backend' of the PowerShare service, there were a few changes needed from the Java (UI) side of things. Specifically in the PowerShareTile.java file that defines the PowerShare tile. Then there was sepolicy. But I feel like talking about those things would be useless, since this blog was specifically aimed at the HAL.
But if you ARE interested in whatever changes I've done, take a look here: powershare-aidl
Well, thats it for this blog, I know its kinda boring and doesn't have much info, but I'll try going more in depth about what I do for the next time, this is my first ever blog, and it just makes me a bit nervous. Either way, I had a lot of fun writing the AIDL service and it genuinely taught me a lot of things (as well as making this website and writing this blog)
go back