android Q HIDL(小屏显示)
手机设备上添加了一个小的lcd屏,需求是可以显示文字与图片.且可以在每个应用里面使用,过CTS,那么可供选择的实现方式也就没几种了.
(a)过cts的话最好是通过hidl与底层驱动通讯,hidl使用起来也挺方便.
(b)驱动使用的数据为32*40大小的char,大约1kB多点,数据量较小采用序列化的数据传输,不选择使用共享内存
(c)需要满足应用可使用则需要提供java接口,目前属于调试阶段,无法判断后期第三方应用是否能使用,故选择已有的DisplayManager服务 ,扩展其接口,有点是SE权限好控制,缺点是数据在传输过程中多了一次copy.
(d)数据的生成是采用从Bitmap中获取像素点的方式.毕竟是需要图片显示的,同时图片为了适配lcd显示的大小,也要使用Canvas缩小或者扩大其Bitmap.
(一)添加hw hal module
(1)添加h532blcd.default hal模块,创建h532blcd_t,同时生成HAL_MODULE_INFO_SYM.
#define H532BLCD_MODULE_API_VERSION_1_1 HARDWARE_MODULE_API_VERSION(1, 1)
#define H532BLCD_HARDWARE_MODULE_ID "h532blcd"
typedef struct h532blcd_module {
struct hw_module_t common;
} h532blcd_module_t;
typedef struct h532blcd {
struct hw_device_t common;
int (*lcd_send_data)(struct h532blcd *dev,const signed char *data);
} h532blcd_t;
/*
* Generic device handling
*/
static int h532blcd_open(const hw_module_t* module, const char* name,
hw_device_t** device) {
if (device == NULL) {
__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "NULL device on open");
return -EINVAL;
}
__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "Device open");
h532blcd_t *dev = malloc(sizeof(h532blcd_t));
memset(dev, 0, sizeof(h532blcd_t));
dev->common.tag = HARDWARE_DEVICE_TAG;
dev->common.version = 0;
dev->common.module = (struct hw_module_t*) module;
dev->common.close = h532blcd_close;
#if 1
dev->lcd_send_data = lcd_send_data;
#endif
*device = (hw_device_t*) dev;
return 0;
}
static struct hw_module_methods_t h532blcd_module_methods = {
.open = h532blcd_open,
};
h532blcd_module_t HAL_MODULE_INFO_SYM = {
.common = {
.tag = HARDWARE_MODULE_TAG,
.module_api_version = H532BLCD_MODULE_API_VERSION_1_1,
.hal_api_version = HARDWARE_HAL_API_VERSION,
.id = H532BLCD_HARDWARE_MODULE_ID,
.name = "h532blcd",
.author = "The Android Open Source Project",
.methods = &h532blcd_module_methods,
},
};
(2)打开设备节点与处于内核空间的lcd屏进行数据传输
typedef struct
{
unsigned char data[32*40]; // 1280
} __attribute__((packed))pixData;
#define H532BLCD_SEND_DATA _IOW('C', 0x01, pixData)
static int fd = -1;
extern int open_dev();
extern int close_dev();
extern int send_data(pixData *data);
(3)生成h532blcd.default.so文件至vendor/lib64/hw/h532blcd.default.so vendor/lib/hw/h532blcd.default.so.
注意H532BLCD_HARDWARE_MODULE_ID的值必须与生成的so文件前缀相同,这个是hal注册的关键
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_HEADER_LIBRARIES := libhardware_headers
LOCAL_SHARED_LIBRARIES := liblog libcutils
LOCAL_SRC_FILES := ioctl_h532blcd.c hw_h532blcd.c
LOCAL_PROPRIETARY_MODULE := true
LOCAL_MODULE_RELATIVE_PATH := hw
LOCAL_MODULE := h532blcd.default
LOCAL_INIT_RC := init.h532blcd.rc
include $(BUILD_SHARED_LIBRARY)
(4)修改设备节点权限 init.h532blcd.rc
on boot
# h532blcd
chmod 0666 /dev/h532blcdc
(5)添加SE权限
device.te
type simplelcd_device, dev_type;
file_contexts
/dev/h532blcdc u:object_r:simplelcd_device:s0
(二)注册hidl服务
开机后运行
(1)创建manifest.xml
<manifest version="1.0" type="device">
<hal format="hidl">
<name>vendor.hct.hardware.simplelcd</name>
<transport>hwbinder</transport>
<impl level="generic"></impl>
<version>1.0</version>
<interface>
<name>ISimpleLcd</name>
<instance>default</instance>
</interface>
</hal>
</manifest>
(2)创建hal文件ISimpleLcd.hal,同时运行hardware/interfaces/update-makefiles.sh自动生成Android.bp文件
package vendor.hct.hardware.simplelcd@1.0;
interface ISimpleLcd {
lcd_send_data(int8_t[1280] arg) generates (int32_t result);
}
(3)创建可执行文件vendor.hct.hardware.simplelcd@1.0-service
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := vendor.hct.hardware.simplelcd@1.0-service
LOCAL_INIT_RC := vendor.hct.hardware.simplelcd@1.0-service.rc
LOCAL_PROPRIETARY_MODULE := true
LOCAL_MODULE_RELATIVE_PATH := hw
LOCAL_SRC_FILES := \
SimpleLcd.cpp \
service.cpp \
LOCAL_SHARED_LIBRARIES := \
libcutils \
liblog \
libhidlbase \
libhidltransport \
libhardware \
libutils \
vendor.hct.hardware.simplelcd@1.0 \
include $(BUILD_EXECUTABLE)
vendor.hct.hardware.simplelcd@1.0-service.rc
service simplelcd_hal /vendor/bin/hw/vendor.hct.hardware.simplelcd@1.0-service
# "class hal" causes a race condition on some devices due to files created
# in /data. As a workaround, postpone startup until later in boot once
# /data is mounted.
class late_start
user system
group system input
SimpleLcd.cpp
namespace vendor {
namespace hct {
namespace hardware {
namespace simplelcd {
namespace V1_0 {
namespace implementation {
SimpleLcd *SimpleLcd::sInstance = nullptr;
SimpleLcd::SimpleLcd() : mDevice(nullptr){
LOGD("SimpleLcd()");
sInstance = this; // keep track of the most recent instance
mDevice = openHal();
}
SimpleLcd::~SimpleLcd() {
LOGD("~SimpleLcd()");
if (mDevice == nullptr) {
LOGD("No valid device");
return;
}
int err;
h532blcd_t* dev = reinterpret_cast<h532blcd_t*>(mDevice);
if (0 != (err = dev->common.close(mDevice))) {
LOGD("Can't close h532blcd module, error: %d", err);
return;
}
mDevice = nullptr;
}
ISimpleLcd* SimpleLcd::getInstance() {
if (!sInstance) {
sInstance = new SimpleLcd();
}
return sInstance;
}
Return<int32_t> SimpleLcd::lcd_send_data(const hidl_array<int8_t, 1280>& arg){
if(mDevice == NULL){
return -1;
}
h532blcd_t* dev = reinterpret_cast<h532blcd_t*>(mDevice);
LOGD("hal Device lcd_send_data \n");
return dev-> lcd_send_data(dev,arg.data());
}
hw_device_t* SimpleLcd::openHal(){
hw_device_t* dev = nullptr;
hw_module_t const* module;
int err = hw_get_module(H532BLCD_HARDWARE_MODULE_ID, &module);
if (err != 0) {
LOGD("Can't open SimpleLcd detect HW Module, error: %d", err);
return nullptr;
}
err = module->methods->open(module, "h532blcd", &dev);
if (err < 0) {
LOGD("Can't open SimpleLcd Detect, error: %d", err);
return nullptr;
}
LOGD("Open SimpleLcd hal");
return dev;
}
} //namespace implementation
} //namespace V1_0
} //namespace simplelcd
} //namespace hardware
} //namespace hct
} //namespace vendor
service.cpp
#define LOG_TAG "vendor.hct.hardware.simplelcd@1.0-service"
#include <android/log.h>
#include <hidl/HidlSupport.h>
#include <hidl/HidlTransportSupport.h>
#include <vendor/hct/hardware/simplelcd/1.0/ISimpleLcd.h>
#include "SimpleLcd.h"
using vendor::hct::hardware::simplelcd::V1_0::ISimpleLcd;
using vendor::hct::hardware::simplelcd::V1_0::implementation::SimpleLcd;
using android::hardware::configureRpcThreadpool;
using android::hardware::joinRpcThreadpool;
using android::sp;
int main() {
android::sp<ISimpleLcd> bio = SimpleLcd::getInstance();
configureRpcThreadpool(1, true /*callerWillJoin*/);
if (bio != nullptr) {
bio->registerAsService();
} else {
ALOGE("Can't create instance of BiometricsFingerprint, nullptr");
}
joinRpcThreadpool();
return 0; // should never get here
}
(三)frameworks曾添加接口
(1)frameworks/base/services/core/Android.bp
static_libs: [
"vendor.hct.hardware.simplelcd-V1.0-java",
],
(2)frameworks/base/core/java/android/hardware/display/IDisplayManager.aidl
/** @hide */
interface IDisplayManager {
// Temporarily sets the display brightness.
void setSimpleLcdShow(in byte[] data);
}
(3)frameworks/base/core/java/android/hardware/display/DisplayManager.java
提供两个接口(a)setSimpleLcdText显示文字(b)setSimpleLcdDrawable显示图片
/**
* @param text The text for LCD show.
*
* @hide
*/
public void setSimpleLcdText(CharSequence text){
Log.d("SimpleLcdDrawable","text ++ ");
if (Looper.getMainLooper() != Looper.myLooper()) {
throw new RuntimeException("setSimpleLcdText must called in MainThread!");
}
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.BLACK);
setLedTextSize(paint,DEFAULT_TEXT_SIZE);
measureTextBound(paint,text.toString(),LCD_HORIZONTAL_END,LCD_VERTICAL_END);
byte[] data = generateLedBitmap(renderText(text.toString(), paint));
Log.d("SimpleLcdDrawable","text -- ");
mGlobal.setSimpleLcdShow(data);
}
/**
* @param drawable The drawable for LCD show.
*
* @hide
*/
public void setSimpleLcdDrawable(Drawable drawable){
Log.d("SimpleLcdDrawable","drawable ++ ");
if (Looper.getMainLooper() != Looper.myLooper()) {
throw new RuntimeException("setSimpleLcdDrawable must called in MainThread!");
}
Drawable ledImage = drawable;
byte[] data = generateLedBitmap(renderDrawable(ledImage, LCD_HORIZONTAL_END, LCD_VERTICAL_END));
Log.d("SimpleLcdDrawable","drawable -- ");
mGlobal.setSimpleLcdShow(data);
}
/**
* set the text size
* @param paint paint
* @param size text size
*/
private void setLedTextSize(Paint paint ,float size) {
Log.d("SimpleLcdText", "setLedTextSize :" + size);
float ledTextSize = size;
paint.setTextSize(ledTextSize);
if(ledTextSize > 30){
paint.setTypeface(Typeface.DEFAULT_BOLD);
}else {
paint.setTypeface(Typeface.MONOSPACE);
}
}
/**
* measure the text width and height
* @param paint paint
* @param text text content
* @param width the Led Width
* @param height the Led Height
*/
private void measureTextBound(Paint paint,String text, int width, int height) {
Paint.FontMetrics m = paint.getFontMetrics();
measureTextWidth = (int) paint.measureText(text);
measureTextHeight = (int) (m.bottom - m.ascent);
float sacle = Math.min((float) width / measureTextWidth, (float) height / measureTextHeight);
if(sacle < 1){
setLedTextSize(paint, paint.getTextSize() * sacle);
measureTextBound(paint, text,width,height);
}
}
/**
* Transform text to bitmap
*
* @param text text content
* @param paint paint
* @return the bitmap of text
*/
private Bitmap renderText(CharSequence text, Paint paint) {
Bitmap bitmap = Bitmap.createBitmap(measureTextWidth, measureTextHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
int yPos = (int) ((canvas.getHeight() / 2) - ((paint.descent() + paint.ascent()) / 2));
canvas.drawText(text.toString(), 0, yPos, paint);
return bitmap;
}
/**
* Transform the image drawable to bitmap
*
* @param drawable the content drawable
* @param width the Led Width
* @param height the Led Height
* @return bitmap of drawable
*/
private static Bitmap renderDrawable(Drawable drawable, int width, int height) {
Bitmap bitmap = getBitmapFromDrawable(drawable);
float sacle = Math.min((float) width / bitmap.getWidth(), (float) height / bitmap.getHeight());
if(sacle < 1){
return Bitmap.createScaledBitmap(bitmap,
(int)(bitmap.getWidth() * sacle),
(int)(bitmap.getHeight() * sacle), true);
}else {
return bitmap;
}
}
/**
* Get bitmap from drawable, Copy from CircleImageView
*
* @param drawable the drawable
* @return the bitmap of drawable
*/
private static Bitmap getBitmapFromDrawable(Drawable drawable) {
if (drawable == null) {
return null;
}
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
}
try {
Bitmap bitmap;
if (drawable instanceof ColorDrawable) {
bitmap = Bitmap.createBitmap(2, 2, Bitmap.Config.ARGB_8888);
} else {
bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ALPHA_8);
}
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* Transform a bitmap to a led bitmap
*
* @param src the original bitmap
* @return led bitmap
*/
private byte[] generateLedBitmap(Bitmap src) {
byte[] data = new byte[LCD_VERTICAL_END * LCD_HORIZONTAL_END/8];
int bitmapH = src.getHeight();
int bitmapW = src.getWidth();
int paddStart = (LCD_HORIZONTAL_END - bitmapW)/2;
int paddTop = (LCD_VERTICAL_END- bitmapH)/2;
//StringBuffer stringBuffer = new StringBuffer();
for(int y = 0; y < src.getHeight();y++){
for(int x = 0;x < src.getWidth();x++){
int color = isInRange(src, x , y);
int lcdX = paddStart + x;
int lcdY = paddTop + y;
int dataID = (lcdX/8 + lcdY*32);
int byteId = 7 - lcdX%8;
if (color != 0) {
data[dataID] = (byte) (data[dataID] | (0x1<<byteId));
//stringBuffer.append("1");
}else {
//stringBuffer.append("0");
}
}
//stringBuffer.append("\n");
}
//Log.d("SimpleLcdDrawable",stringBuffer.toString());
return data;
}
/**
* Measure if x and y is in range of leds
*
* @param bitmap the origin bitmap
* @param x led x
* @param y led y
* @return the color , if color is zero means empty
*/
private int isInRange(Bitmap bitmap, int x, int y) {
if (bitmap == null)
return 0;
if (y> 0 && y < bitmap.getHeight()
&& x > 0 && x < bitmap.getWidth()) {
int px = bitmap.getPixel(x, y);
if(px == 0){
return 0;
}
return Color.argb(0xff, 0x00, 0x00, 0x00);
}else {
return 0;
}
}
(4)frameworks/base/core/java/android/hardware/display/DisplayManagerGlobal.java
实现IDisplayManager.aidl的接口
/**
*
* @param data The data for LCD.
*
* @hide
*/
public void setSimpleLcdShow(byte[] data) {
if(data != null && data.length == 1280){
StringBuffer stringBuffer = new StringBuffer();
for (int iLine=0; iLine<40; iLine++) {
for(int i = iLine*32; i < (iLine*32+31); i++) {
byte pointChar = data[i];
for(int j=7; j>=0; j--) {
if((pointChar>>j&0x1)!= 0){
stringBuffer.append("1");
}else{
stringBuffer.append("0");
}
}
}
if (DEBUG) {
Log.d("SimpleLcdDrawable",stringBuffer.toString());
}
stringBuffer.setLength(0);
}
}else {
throw new RuntimeException("setSimpleLcdShow data.length must 1280 ,but now is " + (data == null ? 0:data.length));
}
try {
mDm.setSimpleLcdShow(data);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
(5)frameworks/base/services/core/java/com/android/server/display/DisplayManagerService.java
实现IDisplayManager.aidl接口,同时通过hidl传递到hal层
@Override // Binder call
public void setSimpleLcdShow(byte[] data) {
final long token = Binder.clearCallingIdentity();
try {
Slog.d(TAG, "setSimpleLcdShow");
if (mSimpleLcd != null) {
int result = mSimpleLcd.lcd_send_data(data);
Slog.d(TAG, "setSimpleLcdShow dev(0) result = " + result);
}else{
mSimpleLcd = ISimpleLcd.getService();
Slog.d(TAG, "ISimpleLcd.getService");
int result = mSimpleLcd.lcd_send_data(data);
Slog.d(TAG, "setSimpleLcdShow dev(0) result = " + result);
}
}catch (RemoteException re){
Slog.d(TAG, "setSimpleLcdShow re = " + re.getMessage());
} finally {
Binder.restoreCallingIdentity(token);
}
}
(6)添加system_service能掉hidl的权限
system_server.te
allow system_server hal_simplelcd_hwservice:hwservice_manager find;
(四)调用实例
通过getSystemService获取到DisplayManager,然后调用setSimpleLcdText("HELLO")就可以在lcd屏上看到显示了.