http://blog.csdn.net/g_salamander/article/details/8487328
新项目的手机需要实现关机状态下的闹钟,早在刚开始接触 android 的时候都在想为什么 android 不支持关机状态下的一些功能呢?像充电或者闹钟什么的,虽然每个平台的驱动不一样但上层应用是可以提供统一接口的呀,果然在 4.0 的时候支持关机充电了,关机闹钟仍然不在默认支持中。市场上的很多品牌手机也都不支持这个功能,让很多用惯了 Feature Phone 以及担心辐射的用户都不习惯。这次做关机闹钟在一些思路上借鉴了关机充电的实现方法。
整体思路如下:
在 uboot 中通过 PMU 判断开机的原因,如果是 RTC 模块使能开机则在 uboot 中传递启动参数 androidboot.mode=alarm,然后在 init 进程中判断启动模式(当前系统有 3 种启动模式:normal、charger、alarm),如果是 alarm 模式则启动 alarm 服务,alarm 服务与应用程序 alarm关联,因此需要编写应用程序来实现关机闹钟的功能。应用程序主要实现以下几个个方面的功能:1、显示关机闹钟的 UI 以及当前时间;2、播放闹铃;3、读取 input 事件判断用户操作;4、用户可以在 UI 中选择懒人模式、开机或者关机。下面逐个解析这几个功能的实现:
1、UI
在 zygote 没有启动之前完成 UI 显示可以参考 charger 的做法,用 android 的 minui 接口,这些接口实现了图形的描绘以及固定大小的文字显示,函数介绍如下:
- int gr_init(void);
- void gr_exit(void);
-
- int gr_fb_width(void);
- int gr_fb_height(void);
- gr_pixel *gr_fb_data(void);
- void gr_flip(void);
- void gr_fb_blank(bool blank);
-
- void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a);
- void gr_fill(int x, int y, int w, int h);
- int gr_text(int x, int y, const char *s);
- int gr_measure(const char *s);
- void gr_font_size(int *x, int *y);
-
- void gr_blit(gr_surface source, int sx, int sy, int w, int h, int dx, int dy);
- unsigned int gr_get_width(gr_surface surface);
- unsigned int gr_get_height(gr_surface surface);
-
- int res_create_surface(const char* name, gr_surface* pSurface);
- void res_free_surface(gr_surface surface);
图片只支持 png 格式,做这个 UI 的图片资源花了不少时间(没做过美工),一般图片的显示先由 res_create_surface 创建资源数据,然后调用 gr_blit 填充,最后调用 gr_flip 刷新显示。在关机闹钟的界面还需要显示当前时间,最开始调用 minui 默认的字库来显示,但是默认字库的字体太小了,只支持 10 x 18 ASIC-II 编码的字符,效果很不好,后来就把时间需要的 10 个数字以及符号以图片的形式显示。
2、闹铃
在这个阶段播放闹铃只能选择 tinyplay,tinyplay 是 android 自带的一款简易播放器,只能播放固定格式的 wav 文件。UI 显示以及播放闹铃分别独占一个线程,以保证各自不被干扰。
3、input evnt
当闹钟开始响后,用户可以通过触摸屏点击选择是否开关机或者进入懒人模式,这里就需要对用户操作做出判断,即在程序中去读取 /dev/input 下面设备的数据。当进入懒人模式后会停止闹铃 5 分钟再响,这个阶段需要关闭 lcd 和 触摸屏,用户可以通过按键提前唤醒。input event 是在进程中循环读取并处理的,示例代码如下:
- static int event_loop(void)
- {
- int i;
- int ret = 0;
- int nfds = ALARM_MAX_DEVICE;
- struct input_event event;
- const char *device = NULL;
- const char *device_path = "/dev/input";
-
- ret = scan_dir(device_path);
- if(ret < 0) {
- printf("scan dir failed for %s.\n", device_path);
- return ret;
- }
-
- for(;;) {
- poll(ufds, nfds, -1);
-
- for (i = 0; i < nfds; i++) {
- if(ufds[i].revents) {
- if(ufds[i].revents & POLLIN) {
- ret = read(ufds[i].fd, &event, sizeof(event));
- if (ret < (int)sizeof(event)) {
- printf("could not get event.\n");
- continue;
- }
- handle_event(event.type, event.code, event.value);
- }
- }
- }
- }
-
- return 0;
- }
附:android 权限管理机制
这次在编译使用 alarm 的时候遇到了一个权限问题:编译出来的 alarm 可执行程序在 out 目录下面是拥有可执行权限的,但是在烧录到机器后发现没有可执行权限了,最后才发现是 android 的权限管理机制引起的。
1、文件系统中的权限设定
在 android 系统编译完成后会生成后缀为 img 的文件如:system.img、boot.img,包含了许多目录和文件。在编译的时候会用到 mkbootfs 等命令,这些命令将会调用 system/core/include/private/android_filesystem_config.h文件中预定义的权限,来预置这些目录和文件的初始访问权限。关机闹钟的可执行程序就需要在这里预置权限。示例定义如下:
-
- static struct fs_path_config android_dirs[] = {
- { 00770, AID_SYSTEM, AID_CACHE, "cache" },
- { 00771, AID_SYSTEM, AID_SYSTEM, "data/app" },
- { 00771, AID_SYSTEM, AID_SYSTEM, "data/app-private" },
- ...
- { 00755, AID_ROOT, AID_ROOT, "system/etc/ppp" },
- { 00777, AID_ROOT, AID_ROOT, "sdcard" },
- { 00755, AID_ROOT, AID_ROOT, 0 },
- };
-
-
- static struct fs_path_config android_files[] = {
- { 00440, AID_ROOT, AID_SHELL, "system/etc/init.goldfish.rc" },
- { 00550, AID_ROOT, AID_SHELL, "system/etc/init.goldfish.sh" },
- { 00644, AID_SYSTEM, AID_SYSTEM, "data/app/*" }, /* app目录下所有文件的权限 */
- { 00644, AID_MEDIA_RW, AID_MEDIA_RW, "data/media/*" },
- { 00644, AID_SYSTEM, AID_SYSTEM, "data/app-private/*" },
- { 00644, AID_APP, AID_APP, "data/data/*" }, /* data目录下所有文件的权限 */
- ...
- { 06750, AID_ROOT, AID_SHELL, "system/bin/run-as" },
- { 00755, AID_ROOT, AID_SHELL, "system/bin/*" }, /* 所有系统可执行程序的权限 */
- { 00755, AID_ROOT, AID_ROOT, "system/lib/valgrind/*" },
- { 00755, AID_ROOT, AID_SHELL, "system/xbin/*" },
- { 00755, AID_ROOT, AID_SHELL, "system/xlib/*" },
- { 00755, AID_ROOT, AID_SHELL, "system/vendor/bin/*" },
- { 00750, AID_ROOT, AID_SHELL, "sbin/*" },
- { 00755, AID_ROOT, AID_ROOT, "bin/*" },
- { 00750, AID_ROOT, AID_SHELL, "init*" },
- { 00750, AID_ROOT, AID_SHELL, "charger*" }, /* 关机充电可执行程序的权限 */
- { 00750, AID_ROOT, AID_SHELL, "alarm*" },
- { 00644, AID_ROOT, AID_ROOT, 0 },
- };
如果有新的需要只要把相应的预定义设置添加到相应数组即可。
2、设备文件的权限
android 系统中,kernel 启动后运行的第一个进程为 init,init 进程会打开定义为 NETLINK_KOBJECT_UEVENT的 socket,接收内核发送的添加或删除设备的 uevnet 消息。并通过调用 system/core/init/devices.c文件中的 add_dev_perms 函数完成设备文件的权限初始化,参数来自于 xxx.rc 文件。
其他由 kernel 或 init 及其脚本创建的目录或文件,如果需要修改访问权限,可以通过 init 使用的脚本文件 init.rc 控制,但不建议直接修改 init.rc 文件,而应该集中在和硬件相关的或新增的 init.vendore.rc 脚本文件中修改,这样做的好处是集中管理,便于移植和版本升级。