Android智能电视待机流程分析

上一篇简单分析了Android原生的待机流程,这一篇分析一下我们康佳智能电视的STR待机流程。智能电视的待机流程和Android原生的还是有一些差别的。STR是Suspend to RAM的缩写,顾名思义就是挂起到内存。所以我们官方给STR待机起的名字叫做快速待机,也就是待机时间快,待机起来之后还会会到待机前的状态

OK,言归正传,同样我们先把大致的流程图贴出来

智能电视是通过遥控器上的待机键来使电视待机的,当用户按下Power key的时候,会在interceptKeyBeforeQueueing中拦截判断,代码片段如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int keyCode = event.getKeyCode();
if (enable_str == true) {
if ("1".equals(SystemProperties.get("mstar.str.suspending"))) {
Log.d(TAG, "STR forbid keyCode=" + keyCode + ",down=" +down);
return 0;
}
if ("2".equals(SystemProperties.get("mstar.str.suspending"))) {
//Fake key for STR power on
if (keyCode == KeyEvent.KEYCODE_TV_POWER) {
Log.d(TAG, "STR dc on down=" + down);
keyCode = KeyEvent.KEYCODE_POWER;
if (down == false) {
if (mPowerKeyHandled == false) {
Log.d(TAG, "STR power key down is missing !!!");
return 0;
} else {
Log.d(TAG, "STR dc on mstar.str.suspending = 1");
SystemProperties.set("mstar.str.suspending", "1");
}
}
} else {
Log.d(TAG, "STR dc on forbid keyCode=" + keyCode);
return 0;
}
}
}

我们可以看到在这个方法中有一句SystemProperties.set("mstar.str.suspending", "1");也就是当Power key第一次被按下的时候会执行该动作,将mstar.str.suspending变量的值置为1,然后如果这个过程中用户再次按下任意一个按键的时候就会进入第一个判断语句,然后被return掉不处理,这就是挡key机制

补充一下挡key机制:为了防止待机时乱按key导致问题,会加入一套挡key机制:一旦待机,任何key(包括Power key)都会被挡掉。知道SRT待机回来才会放开,具体实现是这样的:

  • 收到Power key(value == 26)之后,会在其key down时,把mstar.str.suspending置为1,此时会挡掉所有的key

  • SRT开机后,SuperNova(MStar的芯片系统,类似C++库)那边会把mstar.str.suspending置为2,并发一个假的Power key (value == 117) (之所以不用原来的Power key,是因为第1步中把所有的key都挡掉了,原来的Power key没作用了),来触发开机。但是,此时Android还没有ready好,仍然不能响应任何key,所以再一次把mstar.str.suspending置为1。给人感觉好像是啥都没做,值从1-2-1,但是通过这一步,我们获得了开机的事件,就可以继续跑Android的开机流程了。

  • 最后,等到Android那边ready,也就是ActivityManagerService.java中的comOutOfSleepIfNeededLocked里面把mstar.str.suspending置为0。到此SRT就结束了,开始响应所有的key。

上面插播了一段挡key机制,我们继续之前的话题。用户第一次按下Power key之后,mstar.str.suspending被置为1.同时通过如下代码还会将mSystemSuspend(str早期的标志位)置为true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
case KeyEvent.KEYCODE_POWER: {
// KONKA Patch Start (+caobin 2014-01-03)
if (down) {
if (!mSystemSuspend) {
result = ACTION_PASS_TO_USER;
mSystemSuspend = true;
Log.d(TAG, "make mSystemSuspend = true, result = " + result);
} else {
result &= ~ACTION_PASS_TO_USER;
mSystemSuspend = false;
Log.d(TAG, "STR resume, make mSystemSuspend false, result = " + result);
}
if (isScreenOn && !mPowerKeyTriggered
&& (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
mPowerKeyTriggered = true;
mPowerKeyTime = event.getDownTime();
interceptScreenshotChord();
}

ITelephony telephonyService = getTelephonyService();
boolean hungUp = false;
if (telephonyService != null) {
try {
if (telephonyService.isRinging()) {
// Pressing Power while there's a ringing incoming
// call should silence the ringer.
telephonyService.silenceRinger();
} else if ((mIncallPowerBehavior & Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0
&& telephonyService.isOffhook()) {
// Otherwise, if "Power button ends call" is enabled,
// the Power button will hang up any current active call.
hungUp = telephonyService.endCall();
}
} catch (RemoteException ex) {
Log.w(TAG, "ITelephony threw RemoteException", ex);
}
}
interceptPowerKeyDown(!isScreenOn || hungUp
|| mVolumeDownKeyTriggered || mVolumeUpKeyTriggered);
Log.d(TAG, "str power key down.before Queeing POWER_KEY down,
result = " + result);
} else {
Log.d(TAG, "str power key up");
// MStar Android Patch Begin
// normal dc off
if (enable_str == true) {
mPowerKeyTriggered = false;
cancelPendingScreenshotChordAction();
if (interceptPowerKeyUp(canceled || mPendingPowerKeyUpCanceled)) {
result = (result & ~ACTION_WAKE_UP) | ACTION_GO_TO_SLEEP;
}
mPendingPowerKeyUpCanceled = false;
Log.d(TAG, "mSystemSuspend = " + mSystemSuspend + ", result = " + result);
if (mSystemSuspend) {
Log.d(TAG, "we need wait shutdown logo finish");
SystemProperties.set("mstar.str.suspending", "1");
int alreadyWait = 0;
while (!isStandbyLogoFinish) {
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
alreadyWait += 200;
if (alreadyWait >= STANDBY_LOGO_WAIT_TIME_OUT) {
Log.d(TAG, "!!!!!! WRONG !!!! STADNBY WAIT TOO LONG !!!!!");
break;
}
}
isStandbyLogoFinish = false;
}
Log.d(TAG, "enter str = " + System.currentTimeMillis());
}
// MStar Android Patch End
Log.d(TAG, "before Queeing POWER_KEY up, result = " + result);
}
//KONKA Patch End
}

然后会在interceptKeyBeforeDispatching方法中拦截Powerkey down事件,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// KONKA Patch Start (+caobin 2014-01-03)
else if (keyCode == KeyEvent.KEYCODE_POWER) {
if (STANDBY_WANT_EXIT == mInVeryQuickStandby) {
Log.d(TAG, "before dispatching POWER_KEY, STANDBY_WANT_EXIT");
if (!down) {
Log.d(TAG, "powerkey up, exitVeryQuickStandby");
exitVeryQuickStandby();
} else {
Log.d(TAG, "powerkey down, throw key");
return -1;
}
} else {
if (down && (0 == repeatCount)) {
if (isStandbyHintNeeded()) {
Log.d(TAG, "before dispatching POWER_KEY, STR, powerkey down");
isStandbyLogoFinish = false;
mHandler.post(rStandbyDialogConfirmCallback);
// killCNTVBeforeStandby();
// setSleepModeToDongle();
return -1;
}
}
}
}
// KONKA Patch End

此时会走else中的流程,因为int mInVeryQuickStandby = STANDBY_NORMAL可以看出mInVeryQuickStandby 初始值不是STANDBY_WANT_EXIT。到这里会首先将isStandbyLogoFinish置为false就是说提示框还没有消失,同时会调用rStandbyDialogConfirmCallback线程来绘制提示框。我们看一下这个线程的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
final Runnable rStandbyDialogConfirmCallback = new Runnable() {
@Override
public void run() {
Log.d(TAG, "standbyDialog, in confirm callback -- modify by caobin");
if (null == standbyHintDialog) {
standbyHintDialog = StandbyHintDialog.create(mContext, mHandler);
}
Log.d(TAG, "standby logo shown = " + System.currentTimeMillis());
closeDoubleWhenStandy();
((AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE)).
setMasterMute(true, -10);
standbyHintDialog.setHintEndCallback(rStandbyHintEndCallback);
standbyHintDialog.setBeforDismissCallback(rDisableBacklight);
standbyHintDialog.show();
Intent intent = new Intent("com.konka.tv.hotkey.service.UNMUTE");
mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
if (!isNeedVeryQuickStandby()) {
mContext.sendBroadcastAsUser(new Intent(Intent.ACTION_SCREEN_OFF),
UserHandle.CURRENT);
}
}
};

该方法中首先会调用StandbyHintDialog类中的create创建对话框,然后会执行一些其他动作,比如关闭双通道之类的,接下来的两句:

1
2
standbyHintDialog.setHintEndCallback(rStandbyHintEndCallback);
standbyHintDialog.setBeforDismissCallback(rDisableBacklight);

我们从StandbyHintDialog.java这个类中找到这两个方法如下:

1
2
3
4
5
6
public void setHintEndCallback(Runnable callback) {
mCallbackRef = new WeakReference<Runnable>(callback);
}
public void setBeforDismissCallback(Runnable callback) {
mBeforDismiss = new WeakReference<Runnable>(callback);
}

其实就是设置回调线程而已,找出回调的线程,源码如下,这个是rStandbyHintEndCallback线程源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
final Runnable rStandbyHintEndCallback = new Runnable() {
@Override
public void run() {
Log.d(TAG, "standbyHintDialog, in hint end callback -- modify by caobin");
if (null != standbyHintDialog) {
standbyHintDialog = null;
}
if (null != standbyDialog) {
standbyDialog = null;
}
if (isNeedVeryQuickStandby()) {
if (!keyguardOn()) {
try {
Intent fakeStandby = new Intent();
fakeStandby.setClassName("com.konka.fakestandby",
"com.konka.fakestandby.MainActivity");
fakeStandby.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
mContext.startActivityAsUser(fakeStandby, UserHandle.CURRENT);
} catch (ActivityNotFoundException e) {}
}
enterVeryQuickStandby();
} else {}
isStandbyLogoFinish = true;
}
};

这个是rDisableBacklight线程源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
final Runnable rDisableBacklight = new Runnable() {
@Override
public void run() {
if (mSystemSuspend) {
if (isNeedVeryQuickStandby()) {
Log.d(TAG, "entering fakeStandby, we can close backlight");
} else {
Log.d(TAG, "entering STR, we can close backlight");
}
try {
if (null != TvManager.getInstance()) {
TvManager.getInstance().getPictureManager().disableBacklight();
}
} catch (TvCommonException e) {
e.printStackTrace();
}
}
}
};

rStandbyHintEndCallback线程是在对话框显示结束之后,启动一个Activity,其实这是一个假的Activity,其目的就是起一个Activity从而使电视待机前的Activity调用onstop方法,免得用户在玩游戏的时候,等待机起来发现游戏已经Game over了多不好啊。然后将isStandbyLogoFinish置为true,这个变量在STR线程中要读,一旦为true,STR认为待机前的工作做完了,就开始进入STR待机了

rDisableBacklight线程则是关闭电视背光的功能。enterVeryQuickStandby()里面会设置一些待机呼吸灯的模式

此时当Power key up的时候,会在interceptKeyBeforeQueueing中进行处理,如果发现isStandbyLogoFinish已经为true,那么就进入SuperNova的待机,这就是KONKA智能电视待机的大致流程