定时开关机-Android4.4/6.0

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_27840681/article/details/78889813

一、寄存器介绍——RTCSA & RTCSAR

1.RTC是Real Time Clock的简称,它在硬件电路上单独供电,当系统关机时,CPU和其他外部硬件设备全部掉电,但是RTC仍然继续工作.
2.HWCR (Hibernate Wakeup Control Register)是一个控制休眠唤醒的寄存器,如果我们要使用休眠状态下RTC唤醒的功能,我们需要打开它的第0位ELAM(RTC Alarm Wakeup enable),当ELAM置1时,使能ELAM功能。
3.RTCSR (RTC Second Registe)是一个32位的寄存器,它的值以1Hz的频率加1,即每秒自动加1。
4.RTCSAR (RTC Second Alarm Register)是一个以秒为单位的闹钟寄存器,我们可以将设置的格林威治时间转换成相应的秒数然后写进这个寄存器,即完成了我们设置的闹钟。我们打开HWCR中的ELAM,按power键关机,当RTC检测到RTCSR == RTCSAR的值时,RTC将会唤醒CPU,并从XBOOT开始进行开机启动。

二、定时开关机原理

1.Batch

从Android4.4开始,Alarm默认为非精准模式,除非显示指定采用精准模式。在非精准模式下,Alarm是批量提醒的,每个alarm根据其触发时间和最大触发时间的不同会被加入到不同的batch中,同一个batch的不同alarm是同时发生的,这样就无法实现精准闹钟。官方的解释是批量处理可以减少设备被唤醒次数以及节约电量,不过针对精准闹钟,官方预留的方法是setExact和setWindow。
这里写图片描述
在 AlarmManagerService 中真正设置 alarm 的函数是 setImplLocked 函数,在这个函数中把 alarm 添加到 mAlarmBatchs 中,mAlarmBatchs 会把触发时间相近的Alarm 放在同一个 batch 中,然后每个 bach 根据时间排序放在 mAlarmBatchs 中,前面的就是先要触发的 alarm。

可是仅仅把 alarm 加入到 batch 中还不行,系统还必须提供一个类似于 Looper的东西一直去遍历这个列表,一旦它发现有些 alarm 的时间已经到达就要把它取出来去执行。 AlarmThread 会一直循环的跑着,一旦有新的 alarm 触发,它就会取出一个 batch 然后逐个发送 PendingIntent,但是, AlarmThread 没有用处;定时开机功能只是往寄存器 SNVS 中写入了一些值。

三、定时开关机流程

定时关机之前: 人为关机再开机,应该还会定时开关机,但是只会定时关机,不会定时开机;
定时开机之前: 只要一直保持上电(中间没有插拔电源或按钮,没有破坏 Uboot 状态),就可以开机;人为强制开机(按钮或者电源),也应该可以开机, 人为开机再关机,不会定时开机。

1.定时开机

(1)发送开机intent

pendingIntent = PendingIntent.getBroadcast(context,0,poweron_intent,PendingIntent.FLAG_CANCEL_CURRENT);
am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,calendar.getTimeInMillis(), poweron_pendingIntent);

(2)AlarmManagerService.cpp 提供写入定时开机时间到底层的入口 updateRtcAlarm
()
(3)com_android_server_AlarmManagerService.cpp定时开机 JNI 层实现 ioctl()

static void android_server_AlarmManagerService_updateRtcAlarm(JNIEnv* env,
jobject obj, jint fd, jint seconds)
{
    int result = ioctl(fd, ANDROID_RTC_ALARM_SET, &seconds);
    ALOGE("set rtc alarm to %d later: %s\n", seconds,strerror(errno));
    if (result < 0)
    {
        ALOGE("Unable to set rtc alarm to %d later: %s\n", seconds,strerror(errno));
    }
}

(4)interface.c 的 rtc_set_alarm 实现 ioctl()

这里写图片描述

2.定时关机

发送关机intent

pendingIntent = PendingIntent.getBroadcast(context,0,poweroff_intent,PendingIntent.FLAG_CANCEL_CURRENT);
am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
am.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),pendingIntent);

接收关机intent

 if(from_intent.getAction().equals("com.android.settings.action.REQUEST_POWER_OFF")
) {
Intent intent = new Intent(Intent.ACTION_REQUEST_SHUTDOWN);
intent.putExtra(Intent.EXTRA_KEY_CONFIRM, false);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivityAsUser(intent, UserHandle.CURRENT);
}

这里写图片描述

四、移植到Android6.0

定时开机功能, 由于硬件平台和 Android4.4 系统的硬件平台不同, 以及Android6.0 和 Android4.4 的 Alarm 机制不同, 因此在该项目中,采用“真关机”,在掉电后由纽扣电池供应的 RTC Alarm 进行计时,当到达时间后,设备自动开机。

  1. 硬件上的差异: Android4.4 对应的平台的 RTC Alarm 是靠电源进行供电,因此在定时自动关机后,设备不能真正掉电,而是重启到 uboot 阶段,即“假关机”。Android6.0 对应的平台的 RTC Alarm 是靠纽扣电池进行供电,因此设备可以完全掉电,即“真关机”。
  2. 软件上的差异:主要分为 JNI 层和驱动层。
    (1)字符设备节点不同: Android6.0 的 RTC 驱动在注册的时候提供了两个字符设备给用户空间供操作。
    (1.1) /dev/alarm, android 特有设备,为了提高平台无关性而加入,这里不关注闹钟的设置。
    (1.2) /dev/rtcx,原始 linux 操作字符设备的方法。
    字符设备对应的上层接口不同: 因此 JNI 层针对这两个结点分别对应不
    同的 ioctl。以访问 RTC 设备为例。

    • Alarm 节点: android_server_AlarmManagerService_setKernelTime ->AlarmImplAlarmDriver::setTime -> ioctl(fds[0], ANDROID_ALARM_SET_RTC, &ts);->alarm_ioctl (alarm-dev.c) -> alarm_do_ioctl -> alarm_set_rtc -> rtc_set_rtc
      (interface.c)。
    • RTC 节点: from_sys_clock -> xioctl(rtc, RTC_SET_TIME, &tm_time) ->rtc_dev_ioctl -> rtc_set_time (interface.c)。
  3. 寻找 RTC Alarm 对应的 JNI 上层。通过查找发现只有 RTC 节点才有rtc_set_alarm (interface.c)对应的 ioctl(rtc, RTC_ALM_SET, &tm_time),而 Alarm 节点没有 rtc_set_alarm (interface.c)对应的 ioctl(rtc, ANDROID_ALARM_SET_RTC_ALM,&tm_time).

  4. 具体修改:
    方案一:使用 Alarm 节点
    ( 4.1)定义 Alarm 节点对应的新的宏, ANDROID_ALARM_SET_RTC_ALM。
    ( 4.2) framework 层:调用自定义的 updateRtcAlarm( seconds)。
    (4.3) JNI 层:定义函数 updateRtcAlarm,并调用自定义AlarmImplAlarmDriver::setRtcAalrm; 定义 setRtcAlarm( timespec),调用 ioctl(fds[0],ANDROID_ALARM_SET_RTC_ALM, &ts)。
    ( 4.4) kernel 层 alarm-dev.c:添加 case ANDROID_ALARM_SET_RTC_ALM,调用自定义 alarm_set_alarm; alarm_set_alarm 调用 rtc_set_alarm (interface.c)。 成功访问 Alarm 字符设备。
    方案二:使用 RTC 节点
    ( 4.1)不需要定义新的宏,因为 RTC 节点有对应的修改 RtcAlarm 的宏,RTC_ALM_SET。
    ( 4.2) framework 层:调用自定义的 updateRtcAlarm( seconds)。
    ( 4.3) JNI 层:定义函数 updateRtcAlarm,并调用自定义AlarmImplAlarmFd::setRtcAalrm; 定义 setRtcAlarm( timeval),调用 ioctl(fds[0],RTC_ALM_SET, &ts) , ioctl(fds[0], RTC_AIE_ON, &ts))。 这里需要注意 RTC_AIE_ON必须调用,该宏的作用是打开 Alarm 中断,使硬件 RTC Alarm 使能 enable,这样写下去的开机时间才能真正触发,且顺序不能颠倒。
    ( 4.4) kernel 层不需要修改, rtc-dev.c 已经有 case RTC_ALM_SET,调用rtc_set_alarm (interface.c)。 成功访问 RTC 字符设备。

  5. 定时开机 disable 时写到硬件,即硬件上一直都存在时间,需要关闭Alarm 中断。
    (5.1)首先,在 apk 层 disable 定时开关机功能时,要发送和 enable 不同的 intent,这样有利于 JNI 进行不同情况下做的动作。
    onPreferenceChanged()函数中

if(autoEnable) setPoweron(true); else setPoweron(false);

setPoweron()函数中,

if(true) intent = new Intent(“com.android.settings.action.REQUEST_POWER_ON”); 
if(false) intent = new Intent(“com.android.settings.action.CANCLE_POWER_ON”);

(5.2) AlarmManagerService 针对不同的 intent,向 JNI 层发送不同的 flag

if(operation.getIntent().getAction().equals(“com.android.settings.action.REQUEST_POWER_ON”)) 
    updateRtcAlarm(true);
else if(operation.getIntent().getAction().equals(“com.android.settings.action.CANCL
E _POWER_ON”) 
    updateRtcAlarm(false);

(5.3) JNI 层把 flag 传入 setRtcAlarm(flag), setRtcAlarm(flag)根据不同的 flag 调
用不同的 ioctl()

updateRtcAlarm(flag){setRtcAlarm(flag);}
setRtcAlarm(flag){
    if(flag) 
        ioctl(RTC_ALM_SET);     
        ioctl(RTC_AIE_ON);
    else 
        ioctl(RTC_AIE_OFF);}

(5.4)这样,在定时开关机 disable 时,可以关闭 kernel 层的 Alarm 中断。

展开阅读全文

没有更多推荐了,返回首页