第四章:Android灯光系统(3)-编写HAL

在编写HAL代码之前,我们先回顾一下之前的硬件访问服务,安卓的应用程序是用java语音写的,如果想访问硬件,必须调用C函数,他怎么调用C函数呢?
1.loadLibrary( 加载C库),在C库中,他会存在一个JNI_onLoad函数,在加载C库时,该函数会被调用。在该函数内会通过jniRegisterNativeMethods注册本地方法(把C函数转化为java方法),我们把具有该功能的文件称为JNI文件。他会向上提供java调用C函数的接口,向下调用HAL(硬件操作)程序。
2.JNI(cpp文件)通过hw_get_module获取hw_module_t结构体,在根据hw_module_t调用open函数,获取一个我们自定义的结构体(如:light_device_t)的结构体,该结构体存在对硬件操作的函数,即HAL文件中实现的函数。
那我们我们怎么写HAL的文件呢?

  1. 实现一个hw_module_t结构体
  2. 实现一个open函数,返回一个自定义的结构体,并且这个结构体的第一个成员必须是hw_module_t。

JNI(.cpp)分析

灯光系统对应的JNI文件为com_android_server_lights_LightsService.cpp,在文件的setLight_native函数中,有如下代码:

static JNINativeMethod method_table[] = {
    { "init_native", "()J", (void*)init_native },
    { "finalize_native", "(J)V", (void*)finalize_native },
    { "setLight_native", "(JIIIIII)V", (void*)setLight_native },
};

int register_android_server_LightsService(JNIEnv *env)
{
    return jniRegisterNativeMethods(env, "com/android/server/lights/LightsService",
            method_table, NELEM(method_table));
}

可知注册了山个本地方法,init_native,finalize_native",setLight_native,这些方法对应的C函数中,肯定加载HAL文件的相关操作,先查看init_native函数:

tatic jlong init_native(JNIEnv *env, jobject clazz)
{
    int err;
    hw_module_t* module;
    Devices* devices;
 
    devices = (Devices*)malloc(sizeof(Devices));
    
    err = hw_get_module(LIGHTS_HARDWARE_MODULE_ID, (hw_module_t const**)&module);
    if (err == 0) {
        devices->lights[LIGHT_INDEX_BACKLIGHT]
                = get_device(module, LIGHT_ID_BACKLIGHT);
        devices->lights[LIGHT_INDEX_KEYBOARD]
     ......................
    }
	........................
}

其中使用了hw_get_module获取一个 hw_module_t结构体指针 module,然后通过get_device,根据传入不同的name获取相应的light_device_t*结构体,从其中我们可以知道,一个HAL文件中,可以对应多个light_device_t。

java应用层分析

我们之前提到过,安卓系统中存在多种逻辑上的灯光如下:

#define LIGHT_ID_BACKLIGHT          "backlight"
#define LIGHT_ID_KEYBOARD          // "keyboard"
#define LIGHT_ID_BUTTONS           //"buttons"
#define LIGHT_ID_BATTERY            "battery"
#define LIGHT_ID_NOTIFICATIONS      "notifications"
#define LIGHT_ID_ATTENTION         // "attention"
#define LIGHT_ID_BLUETOOTH         // "bluetooth"
#define LIGHT_ID_WIFI              // "wifi"

其中灰色部分代表手机没有实现的,
现在手机没有键盘了,所以"keyboard"没有实现,现在的几个手机按键一般由特殊材料做成,发光的原理是利用屏幕的光进行反射所以没有实等等。故此一般只实现了以上没有被注释的三个:“backlight”,“battery”,“notifications”。其中backlight比较特殊,是独有的硬件,我们暂时不讲解,我们想先分析"battery"与"notifications",他们是共用一个硬件。

电池灯

一般电池的灯光现象如下:
在这里插入图片描述
当然,这是我们经验的依据,那么他代码是怎么实现的呢?我们怎么去查找他对应的代码呢?我们可以通过
#define LIGHT_ID_BATTERY “battery”
在源码中搜索LIGHT_ID_BATTERY ,我们可以找到一个名为BatteryService.java的文件,有如下代码:

mBatteryLight = lights.getLight(LightsManager.LIGHT_ID_BATTERY);

调用getLight获取一个mBatteryLight,那么这个mBatteryLight怎么使用呢?我们往下搜索mBatteryLight可以看到updateLightsLocked() 方法中使用了mBatteryLight:

        public void updateLightsLocked() {
            final int level = mBatteryProps.batteryLevel; //获取当前电量
            /*获得电池的状态,由一下状态:正在充电,断开充电器,没有充电,满电等*/
            final int status = mBatteryProps.batteryStatus;
            /*如果电量少于设点的mLowBatteryWarningLevel*/
            if (level < mLowBatteryWarningLevel) {
            	//如果在进行充电
                if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
                    // Solid red when battery is charging
                    mBatteryLight.setColor(mBatteryLowARGB);
                } else {
                    // Flash red when battery is low and not charging
                    mBatteryLight.setFlashing(mBatteryLowARGB, Light.LIGHT_FLASH_TIMED,
                            mBatteryLedOn, mBatteryLedOff);
                }
            } else if (status == BatteryManager.BATTERY_STATUS_CHARGING
                    || status == BatteryManager.BATTERY_STATUS_FULL) {
                if (status == BatteryManager.BATTERY_STATUS_FULL || level >= 90) {
                    // Solid green when full or charging and nearly full
                    mBatteryLight.setColor(mBatteryFullARGB);
                } else {
                    // Solid orange when charging and halfway full
                    mBatteryLight.setColor(mBatteryMediumARGB);
                }
            } else {
                // No lights if not charging and not low
                mBatteryLight.turnOff();
            }
        }

代码注解比较详细,不再进行多次分析,其中 mBatteryLight.setColor(mBatteryLowARGB)根据mBatteryLowARGB设定灯的颜色,我们查看mBatteryLowARGB可以看到如下

    mBatteryLowARGB = context.getResources().getInteger(
             com.android.internal.R.integer.config_notificationsBatteryLowARGB);

然后我们在搜索config_notificationsBatteryLowARGB找到Config.xml文件中的:

    <!-- Default value for led color when battery is low on charge -->
    <integer name="config_notificationsBatteryLowARGB">0xFFFF0000</integer>

我们可以知道ARGB为0xFFFF0000,所以设置颜色为mBatteryLowARGB实际为红色,其余的颜色分析也是同理,不再分析。

扫描二维码关注公众号,回复: 5261284 查看本文章

通知灯

同样,我们不知道通知灯的源代码在哪里,使用同样的方法,根据

#define LIGHT_ID_NOTIFICATIONS      "notifications"

在源码中搜索LIGHT_ID_NOTIFICATIONS,我们可以找到.NotificationManagerService.java文件,首先,他也是根据 "notifications"获取一个mNotificationLight,继续往下追踪mNotificationLight,可以看到他也同电池灯一样,实现了一个updateLightsLocked函数,该函数的注解在源码中很详细就不再进行讲解了,主要实现了以下功能
1.当打电话或者屏幕是亮着的时候,会关闭通知灯。
2.如果没有打电话,根据应用程序传进去的颜色值设定LED灯。

注意:通知灯的优先级比电池灯高。

编写HAL

在我们的GEC3399开发板上,只有两个LED并且都为蓝色,所以没有办法模拟RGB多种颜色,在这里,我们把LED1单做R红色,把LED2单做绿色,来进行模拟。

根据前面的分析,编写灯光系统的HAL,我们需要实现以下几点:
1.实现一个名为HMI的hw_module_t结构体
2.实现一个open函数,他会根据name返回一个light_device_t结构体
3.实现多个light_device_t结构体,每一个对应一个DEVICE,light_device_t的第一个成员为hw_module_t,紧接着一个set.light函数

在编写该HAL时,我们不需要全部自己编写,我们可以参考原代码中的SDK/hardware/rockchip/liblights/lights.cpp,编写我们自己的lights.c如下:

#define LOG_NDEBUG 0
#define LOG_TAG "mylights"
#include <cutils/log.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <hardware/lights.h>

char const*const RED_LED_FILE 			= "/sys/class/leds/led1/brightness";
char const*const GREEN_LED_FILE 		= "/sys/class/leds/led2/brightness";

char const*const RED_LED_FILE_TRIGGER	= "/sys/class/leds/led1/trigger";
char const*const GREEN_LED_FILE_TRIGGER	= "/sys/class/leds/led2/trigger";

char const*const RED_LED_FILE_DELAYON	= "/sys/class/leds/led1/delay_on";
char const*const GREEN_LED_FILE_DELAYON	= "/sys/class/leds/led2/delay_on";

char const*const RED_LED_FILE_DELAYOFF	= "/sys/class/leds/led1/delay_off";
char const*const GREEN_LED_FILE_DELAYOFF= "/sys/class/leds/led2/delay_off";

char const*const LCD_BACKLIGHT_FILE     = "sys/class/backlight/backlight/brightness";


/* Synchronization primities */
static pthread_once_t g_init = PTHREAD_ONCE_INIT;
static pthread_mutex_t g_lock = PTHREAD_MUTEX_INITIALIZER;
/* Mini-led state machine */
static struct light_state_t g_notification;
static struct light_state_t g_battery;


static int write_int (const char *path, int value) {
	int fd;
	static int already_warned = 0;
	fd = open(path, O_RDWR);
	if (fd < 0) {
		if (already_warned == 0) {
			ALOGE("write_int failed to open %s\n", path);
			already_warned = 1;
		}
		return -errno;
	}
	char buffer[20];
	int bytes = snprintf(buffer, sizeof(buffer), "%d\n", value);
	int written = write (fd, buffer, bytes);
	close(fd);
	return written == -1 ? -errno : 0;
}
static int write_string (const char *path, const char *value) {
	int fd;
	static int already_warned = 0;
	fd = open(path, O_RDWR);
	if (fd < 0) {
		if (already_warned == 0) {
			ALOGE("write_string failed to open %s\n", path);
			already_warned = 1;
		}
		return -errno;
	}
	char buffer[20];
	int bytes = snprintf(buffer, sizeof(buffer), "%s\n", value);
	int written = write (fd, buffer, bytes);
	close(fd);
	return written == -1 ? -errno : 0;
}
/* Color tools */
static int is_lit (struct light_state_t const* state) {
	return state->color & 0x00ffffff;
}
static int rgb_to_brightness (struct light_state_t const* state) {
	int color = state->color & 0x00ffffff;
	return ((77*((color>>16)&0x00ff))
			+ (150*((color>>8)&0x00ff)) + (29*(color&0x00ff))) >> 8;
}
/* The actual lights controlling section */
static int set_light_backlight (struct light_device_t *dev, struct light_state_t const *state) {
	int brightness = rgb_to_brightness(state);
	int als_mode;

	ALOGV("%s brightness=%d color=0x%08x",__func__,brightness,state->color);
	pthread_mutex_lock(&g_lock);
	write_int (LCD_BACKLIGHT_FILE, brightness);
	pthread_mutex_unlock(&g_lock);
	return 0;
}

static void set_shared_light_locked (struct light_device_t *dev, struct light_state_t *state) {
	int r, g, b;
	int delayOn,delayOff;
	r = (state->color >> 16) & 0xFF;
	g = (state->color >> 8) & 0xFF;
	b = (state->color) & 0xFF;
        delayOn = state->flashOnMS;
	delayOff = state->flashOffMS;
	if (state->flashMode != LIGHT_FLASH_NONE) {
		write_string (RED_LED_FILE_TRIGGER, "timer");
		write_string (GREEN_LED_FILE_TRIGGER, "timer");
		write_int (RED_LED_FILE_DELAYON, delayOn);
		write_int (GREEN_LED_FILE_DELAYON, delayOn);
		write_int (RED_LED_FILE_DELAYOFF, delayOff);
		write_int (GREEN_LED_FILE_DELAYOFF, delayOff);
	} else {
		write_string (RED_LED_FILE_TRIGGER, "none");
		write_string (GREEN_LED_FILE_TRIGGER, "none");
	}
	write_int (RED_LED_FILE, r);
	write_int (GREEN_LED_FILE, g);
}
static void handle_shared_battery_locked (struct light_device_t *dev) {
	if (is_lit (&g_notification)) {
		set_shared_light_locked (dev, &g_notification);
	} else {
		set_shared_light_locked (dev, &g_battery);
	}
}
static int set_light_battery (struct light_device_t *dev, struct light_state_t const* state) {
	ALOGV("%s flashMode=%d onMS=%d offMS color=0x%08x", __func__,state->flashMode,state->flashOnMS,state->flashOffMS,state->color);
	pthread_mutex_lock (&g_lock);
	g_battery = *state;
	handle_shared_battery_locked(dev);
	pthread_mutex_unlock (&g_lock);
	return 0;
}
static int set_light_notifications (struct light_device_t *dev, struct light_state_t const* state) {
	ALOGV("%s flashMode=%d onMS=%d offMS color=0x%08x", __func__,state->flashMode,state->flashOnMS,state->flashOffMS,state->color);	
	pthread_mutex_lock (&g_lock);
	g_notification = *state;
	handle_shared_battery_locked(dev);
	pthread_mutex_unlock (&g_lock);
	return 0;
}
/* Initializations */
void init_globals () {
	pthread_mutex_init (&g_lock, NULL);
}
/* Glueing boilerplate */
static int close_lights (struct light_device_t *dev) {
	if (dev)
		free(dev);
	return 0;
}
static int open_lights (const struct hw_module_t* module, char const* name,
						struct hw_device_t** device) {
	int (*set_light)(struct light_device_t* dev,
					 struct light_state_t const *state);
	if (0 == strcmp(LIGHT_ID_BACKLIGHT, name)) {
		set_light = set_light_backlight;
	}
	else if (0 == strcmp(LIGHT_ID_BATTERY, name)) {
		set_light = set_light_battery;
	}
	else if (0 == strcmp(LIGHT_ID_NOTIFICATIONS, name)) {
		set_light = set_light_notifications;
	}
	else {
		return -EINVAL;
	}
	pthread_once (&g_init, init_globals);
	struct light_device_t *dev = malloc(sizeof (struct light_device_t));
	memset(dev, 0, sizeof(*dev));
	dev->common.tag 	= HARDWARE_DEVICE_TAG;
	dev->common.version = 0;
	dev->common.module 	= (struct hw_module_t*)module;
	dev->common.close 	= (int (*)(struct hw_device_t*))close_lights;
	dev->set_light 		= set_light;
	*device = (struct hw_device_t*)dev;
	return 0;
}
static struct hw_module_methods_t lights_module_methods = {
	.open = open_lights,
};
struct hw_module_t HAL_MODULE_INFO_SYM = {
	.tag = HARDWARE_MODULE_TAG,
	.version_major = 1,
	.version_minor = 0,
	.id = LIGHTS_HARDWARE_MODULE_ID,
	.name = "GEC-3399 lights module",
	.author = "GEC-3399",
	.methods = &lights_module_methods,
};

首先我们在文件末尾定义了一个HAL_MODULE_INFO_SYM结构体,其中id = LIGHTS_HARDWARE_MODULE_ID不能缺少,JNI文件在需要灯光进行操作时,就是通过该ID获取hw_module_t结构体。在hw_module_t中存在成员lights_module_methods,其子成员,我们实现了一个open函数,该函数主要是根据JNI文件com_android_server_lights_LightsService.cpp在调用get_device时传入的参数,返回对应的hw_device_t结构体,hw_device_t结构体的不同点在于成员set_light的不同,当需要控制通知灯时,我们当然返回set_light指向设置LED灯的函数,当需要控制背光灯时,我们当然返回set_light指向设置背光灯的函数…

在我们编写的lights.c点C文件中,我们主要实现了3个函数,分别为set_light_backlight,set_light_battery,set_light_notifications。他们的实现也很简单,在上一节中,我们知道,只需要往以下文件写值,就是实现对灯的控制:

char const*const RED_LED_FILE 			= "/sys/class/leds/led1/brightness";
char const*const GREEN_LED_FILE 		= "/sys/class/leds/led2/brightness";

char const*const RED_LED_FILE_TRIGGER	= "/sys/class/leds/led1/trigger";
char const*const GREEN_LED_FILE_TRIGGER	= "/sys/class/leds/led2/trigger";

char const*const RED_LED_FILE_DELAYON	= "/sys/class/leds/led1/delay_on";
char const*const GREEN_LED_FILE_DELAYON	= "/sys/class/leds/led2/delay_on";

char const*const RED_LED_FILE_DELAYOFF	= "/sys/class/leds/led1/delay_off";
char const*const GREEN_LED_FILE_DELAYOFF= "/sys/class/leds/led2/delay_off";

char const*const LCD_BACKLIGHT_FILE     = "sys/class/backlight/backlight/brightness";

所以,我们的lights.c文件实质就是这些文件的操作,我们知道,电池灯和通知灯是使用同一个设备,通知灯的优先级是高于电池灯的,所以在电池灯和通知灯不能同时对led进行操作,并且通知灯有权打断电池灯的显示。为了实现该功能,定义了两个全局变量static struct light_state_t g_notification,
static struct light_state_t g_battery,用来表示指示灯的状态为电池灯或者为通知灯,亦或者两者都是,或者都不是。所以电池灯和
通知灯都调用函数

static void handle_shared_battery_locked (struct light_device_t *dev) {
	if (is_lit (&g_notification)) {
		set_shared_light_locked (dev, &g_notification);
	} else {
		set_shared_light_locked (dev, &g_battery);
	}
}

他会先判断此时灯是否处于点亮状态,没有点亮,直接操作即可,如果此时处于点亮状态,则在判断是否为通知点亮状态,如果是,则继续维持通知状态,如果不是则显示电量指示灯。

其中还实现set_light_backlight函数,该函数是对背光灯的操作,过程比较简单,直接操作"sys/class/backlight/backlight/brightness"文件。

编译烧写

编译完成lights.c文件后,我们在hardware/libhardware/modules目录下创建文件夹lights,把lights.c拷贝到新创建的目录下,并且在新建目录下编写Android.mk文件,内容如下

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := lights.rk3399

# HAL module implementation stored in
# hw/<VIBRATOR_HARDWARE_MODULE_ID>.default.so
LOCAL_MODULE_RELATIVE_PATH := hw
LOCAL_C_INCLUDES := hardware/libhardware
LOCAL_SRC_FILES := lights.c
LOCAL_SHARED_LIBRARIES := liblog
LOCAL_MODULE_TAGS := userdebug

include $(BUILD_SHARED_LIBRARY)

然后在SDK目录下执行:
source build/envsetup.sh
lunch rk3399_all-userdebug
mmm hardware/libhardware/modules

完成之后,我们还需要删除系统自带自动生成的lights.rk3399.so,但是为了下次方便,我们还需要备份,即把SDK/out/target/product/rk3399_all/system/vendor/lib64/hw/lights.rk3399.so(该文文件由之前提到的SDK/hardware/rockchip/liblights/lights.cpp编译生成)重命名为1lights.rk3399.so,在前面加上一个1,这样系统就找不到自带的lights.rk3399.so文件,就会加载我们生成的lights.rk3399.so,我们生成的lights.rk3399.so在out/target/product/rk3399_all/system/lib64/hw目录下。
最后执行
make snod -j3
等待编译完成,烧写system.img到开发板,烧写完成完成之后,我们在终端执行logcat mylights:V *:S,然后通过触摸屏调整屏幕的亮度,我们可以看到类似如下打印

01-18 15:29:15.729   596   617 V mylights: set_light_backlight brightness=102 color=0xff666666
01-18 15:29:22.565   596   697 V mylights: set_light_backlight brightness=103 color=0xff676767
01-18 15:29:22.581   596   697 V mylights: set_light_backlight brightness=106 color=0xff6a6a6a
01-18 15:29:22.598   596   697 V mylights: set_light_backlight brightness=109 color=0xff6d6d6d
01-18 15:29:22.615   596   697 V mylights: set_light_backlight brightness=113 color=0xff717171

并且可以调整屏幕亮度,则代表实验成功。

小节结语

该小节首先简单分析了一下java应用层,然后参考hardware/rockchip/liblights/lights.cpp编写了HAL文件lights.c,并且分析其原理。下小节将为大家分析安卓源码中的电池灯部分,敬请期待。

猜你喜欢

转载自blog.csdn.net/weixin_43013761/article/details/86777813