前言
定时开机需要底层 RTC 时钟支持,本文只针对 MTK 平台,其它平台不一定能适用请慎重参考文末链接
定时开关机的原理其实和闹钟是一样的,只不过是个特殊的闹钟而已,由于之前 MTK 6.0 的系统中自带就有定时开关机的功能(在设置中嵌入菜单),
8.1 中我们也将在设置界面中嵌入此功能。
代码
直接 copy 6.0 中的 SchedulePowerOnOff 源码,如果你没有可下载我提供的,将 app 源码放置到 *vendor\mediatek\proprietary\packages\apps* 路径下
修改一 在 AndroidManifest.xml 中增加 Settings 配置(下载的源码中已经修改过)
<activity android:name="com.mediatek.schpwronoff.AlarmClock"
android:label="@string/schedule_power_on_off_settings_title"
android:configChanges="orientation|keyboardHidden|keyboard|navigation">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="com.android.settings.SCHEDULE_POWER_ON_OFF_SETTING" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<!--add for settings show quick into -->
<intent-filter android:priority="5">
<action android:name="com.android.settings.action.EXTRA_SETTINGS" />
</intent-filter>
<meta-data
android:name="com.android.settings.category"
android:value="com.android.settings.category.ia.homepage" />
<meta-data
android:name="com.android.settings.icon"
android:resource="@drawable/ic_settings_schpwronoff" />
<!-- end -->
</activity>
修改二 在 MtkAlarmManagerService 中添加 SchedulePowerOnOff 的包名
vendor\mediatek\proprietary\frameworks\base\services\core\java\com\mediatek\server\MtkAlarmManagerService.java
/**
*For PowerOffalarm feature, this function is used for AlarmManagerService
* to set the latest alarm registered
*/
private void resetPoweroffAlarm(Alarm alarm) {
String setPackageName = alarm.operation.getTargetPackage();
long latestTime = alarm.when;
// Only DeskClock is allowed to set this alarm
if (mNativeData != 0 && mNativeData != -1) {
if (setPackageName.equals("com.android.deskclock")
|| setPackageName.equals("com.mediatek.schpwronoff")) {//add SchedulePowerOnOff package name
/// M: Extra Logging @{
if (DEBUG_ALARM_CLOCK) {
Slog.i(TAG, "mBootPackage = " + setPackageName + " set Prop 2");
}
///@}
SystemProperties.set("persist.sys.bootpackage", "2");
set(mNativeData, PRE_SCHEDULE_POWER_OFF_ALARM,
latestTime / 1000, (latestTime % 1000) * 1000 * 1000);
SystemProperties.set("sys.power_off_alarm", Long.toString(latestTime / 1000));
.......
}
这样就 ok 了,需要注意的是在 8.1 上设置定时开机时间必须大于 120s,否则不会自动开机,据说是新版本的问题,6.0 是没有问题的,无时间限制。
6.0 的 MTK 开关机分析可参考 Android MTK auto power on & off And DeskClock app
开关机简单分析
用户通过 SetAlarm.java PreferenceActivity 来选择开关机时间,最终点击保存调用 saveAlarm()
SchedulePowerOnOff\src\com\mediatek\schpwronoff\SetAlarm.java
private void saveAlarm() {
final String alert = Alarms.ALARM_ALERT_SILENT;
mEnabled |= mRepeatPref.mIsPressedPositive;
Alarms.setAlarm(this, mId, mEnabled, mHour, mMinutes, mRepeatPref.getDaysOfWeek(), true, "", alert);
if (mEnabled) {
popAlarmSetToast(this.getApplicationContext(), mHour, mMinutes, mRepeatPref.getDaysOfWeek(), mId);
}
}
注意里面的 mId 字段,为 1 表示定时开机,为 2 表示定时关机,通过 setAlarm 来设置"闹钟"
SchedulePowerOnOff\src\com\mediatek\schpwronoff\Alarms.java
public static void setAlarm(Context context, int id, boolean enabled, int hour, int minutes,
Alarm.DaysOfWeek daysOfWeek, boolean vibrate, String message, String alert) {
final int initSize = 8;
ContentValues values = new ContentValues(initSize);
ContentResolver resolver = context.getContentResolver();
// Set the alarm_time value if this alarm does not repeat. This will be
// used later to disable expired alarms.
long time = 0;
if (!daysOfWeek.isRepeatSet()) {
time = calculateAlarm(hour, minutes, daysOfWeek).getTimeInMillis();
}
Log.d("@M_" + TAG, "** setAlarm * idx " + id + " hour " + hour + " minutes " + minutes
+ " enabled " + enabled + " time " + time);
values.put(Alarm.Columns.ENABLED, enabled ? 1 : 0);
values.put(Alarm.Columns.HOUR, hour);
values.put(Alarm.Columns.MINUTES, minutes);
values.put(Alarm.Columns.ALARM_TIME, time);
values.put(Alarm.Columns.DAYS_OF_WEEK, daysOfWeek.getCoded());
values.put(Alarm.Columns.VIBRATE, vibrate);
values.put(Alarm.Columns.MESSAGE, message);
values.put(Alarm.Columns.ALERT, alert);
resolver.update(ContentUris.withAppendedId(Alarm.Columns.CONTENT_URI, id), values, null,
null);
if (id == 1) {
// power on
setNextAlertPowerOn(context);
} else if (id == 2) {
// power off
setNextAlertPowerOff(context);
}
}
private static void enableAlert(Context context, final Alarm alarm, final long atTimeInMillis) {
if (alarm == null) {
return;
}
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Log.d("@M_" + TAG, "** setAlert id " + alarm.mId + " atTime " + atTimeInMillis);
Intent intent = new Intent(context, com.mediatek.schpwronoff.SchPwrOffReceiver.class);
Parcel out = Parcel.obtain();
alarm.writeToParcel(out, 0);
out.setDataPosition(0);
intent.putExtra(ALARM_RAW_DATA, out.marshall());
PendingIntent sender = PendingIntent.getBroadcast(context, 0, intent,
PendingIntent.FLAG_CANCEL_CURRENT);
am.setExact(AlarmManager.RTC_WAKEUP, atTimeInMillis, sender);
Calendar c = Calendar.getInstance();
c.setTime(new java.util.Date(atTimeInMillis));
Log.d("@M_" + TAG, "Alarms.enableAlertPowerOff(): setAlert id " + alarm.mId + " atTime "
+ c.getTime());
}
setNextAlertPowerOff 中通过调用 enableAlert(),设置一个 RTC_WAKEUP 时钟,当到设置的关机时间时,SchPwrOffReceiver 广播会被触发,弹出一个 30s 的倒计时提醒用户,点击取消则取消本次定时关机操作,点击确定则立即执行关机操作,关机操作通过隐藏 action ACTION_REQUEST_SHUTDOWN
SchedulePowerOnOff\src\com\mediatek\schpwronoff\ShutdownActivity.java
private void fireShutDown() {
Intent intent = new Intent(Intent.ACTION_REQUEST_SHUTDOWN);
intent.putExtra(Intent.EXTRA_KEY_CONFIRM, false);
intent.setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
再回到定时开机操作 enableAlertPowerOn 中
private static void enableAlertPowerOn(Context context, final Alarm alarm,
final long atTimeInMillis) {
Log.d("@M_" + TAG, "** setAlert id " + alarm.mId + " atTime " + atTimeInMillis);
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, com.mediatek.schpwronoff.SchPwrOnReceiver.class);
Parcel out = Parcel.obtain();
alarm.writeToParcel(out, 0);
out.setDataPosition(0);
intent.putExtra(ALARM_RAW_DATA, out.marshall());
PendingIntent sender = PendingIntent.getBroadcast(context, 0, intent,
PendingIntent.FLAG_CANCEL_CURRENT);
am.setExact(7, atTimeInMillis, sender);
Calendar c = Calendar.getInstance();
c.setTime(new java.util.Date(atTimeInMillis));
Log.d("@M_" + TAG, "Alarms.enableAlertPowerOn(): setAlert id " + alarm.mId + " atTime "
+ c.getTime());
}
可以看到此处的 setExact 类型变成了 7, 之前的定时关机是** AlarmManager.RTC_WAKEUP**,这个 7 有点来头
frameworks\base\core\java\android\app\AlarmManager.java
/**
* Alarm time in {@link System#currentTimeMillis System.currentTimeMillis()}
* (wall clock time in UTC), which will wake up the device when
* it goes off.
*/
public static final int RTC_WAKEUP = 0;
....
/**
* M: This alarm type is used to set an alarm that would be triggered if device
* is in powerOff state. It is set to trigger POWER_OFF_ALARM_BUFFER_TIME ms earlier
* than the actual alarm time so that phone is in wakeup state when actual alarm
* triggers
*/
/** @hide */
public static final int PRE_SCHEDULE_POWER_OFF_ALARM = 7;
.....
对应到 MtkAlarmManagerService 中增加要设置的包名,再往下就是 C 中的设置,可参考 Android自动开关机实现