状态栏上闹钟图标显示过程简介

1. 状态栏上闹钟图标显示过程简介

闹钟App设置闹钟时,都是通过AlarmManager,即AlarmManagerService服务来实现的,
每次创建或者更新闹钟时,都会调用AlarmManager.setExact()来向AlarmManagerService服务设置闹钟响的时间;

AlarmManagerService服务通过native函数set()来设置内核中闹钟,并且发送广播AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED来通知SystemUI进行系统图标的显示,同时会循环调用native函数waitForAlarm()监听到Alarm变化,当监听到有Alarm时间到了,也会发送AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED广播出去;

SystemUI的PhoneStatusBarPolicy.mIntentReceiver会监听这个广播,当接收到AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED广播后,调用StatusBarManagerService服务来设置状态上显示的system icon和icon visibility:

mService.setIcon(SLOT_ALARM_CLOCK, R.drawable.stat_sys_alarm, 0, null);
mService.setIconVisibility(SLOT_ALARM_CLOCK, mCurrentUserSetup && hasAlarm);

在StatusBarManagerService.setIcon()中,服务会通过IStatusBar mBar来处理icon的显示情况,
mBar就是SystemUI中BaseStatusBar的IStatusBar.Stub子类CommandQueue类mCommandQueue对象,通过注册到StatusBarManagerService服务中的,
BaseStatusBar创建mCommandQueue对象时,还把实现了CommandQueue.Callbacks接口的BaseStatusBar对象也传递过去,
CommandQueue mCommandQueue = new CommandQueue(this,mIconList);
这样StatusBarManagerService服务就可以通过SystemUI中的IStatusBar.Stub子类CommandQueue类mCommandQueue对象来调用SystemUI来处理icon显示

注意:因为notification icon和system icon是状态栏上左右两边的不同的图标,左边是通知图标(notification icon),右边的是系统图标(system icon); system icon显示是不受notification影响的,所以调用NotificationManager发出通知是不会在system icon区域显示图标的,只会在notification icon区域显示,所以这里实现闹钟图标显示时是Alarm通过AlarmManagerService发广播给SystemUI的PhoneStatusBarPolicy,SystemUI再调用StatusBarManagerService服务添加icon,StatusBarManagerService服务再通过SystemUI的CommandQueue对象来实现在SystemUI中显示icon的。

2. 状态栏上闹钟图标显示过程的时序图

3. 状态栏上闹钟图标显示过程的实现代码

1).闹钟App中调用设置闹钟:

AlarmProvider.java

1
2
3
4
5
6
public static void enableNextAlert(Context context) {
...
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
am.setExact(AlarmManager.RTC_WAKEUP, item.alarmAlertTime, mPendingIntent);
...
}

2).AlarmManager设置闹钟:

在闹钟App中调用AlarmManager的setExact()方法后,这里AlarmManager就会通过IAlarmManager.Stub和AlarmManagerServie通信Binder通信。

AlarmManager.java

1
2
3
4
5
6
7
8
9
10
11
12
13
public void setExact(int type, long triggerAtMillis, PendingIntent operation) {
setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, operation, null, null);
}

private void setImpl(int type, long triggerAtMillis, long windowMillis, long intervalMillis,
int flags, PendingIntent operation, WorkSource workSource, AlarmClockInfo alarmClock) {
...
try {
mService.set(type, triggerAtMillis, windowMillis, intervalMillis, flags, operation,
workSource, alarmClock);
} catch (RemoteException ex) {
}
}

3).AlarmManagerService设置闹钟:

更新SystemUI状态栏中系统图标闹钟图标的广播是从AlarmManagerService这个服务中发出来了,这样可以所有App中设置闹钟的逻辑都会集中在这个服务中来处理。

AlarmManagerService.java

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
private final IBinder mService = new IAlarmManager.Stub() {
@Override
public void set(String callingPackage,
int type, long triggerAtTime, long windowLength, long interval, int flags,
PendingIntent operation, IAlarmListener directReceiver, String listenerTag,
WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock) {
...
setImpl(type, triggerAtTime, windowLength, interval, operation, directReceiver,
listenerTag, flags, workSource, alarmClock, callingUid, callingPackage);
...
}
}

void setImpl(int type, long triggerAtTime, long windowLength, long interval,
PendingIntent operation, IAlarmListener directReceiver, String listenerTag,
int flags, WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock,
int callingUid, String callingPackage) {
...
/* AppCore: Restrict 500 alarms for each App } */
setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, maxElapsed,
interval, operation, directReceiver, listenerTag, flags, true, workSource,
alarmClock, callingUid, callingPackage);
...
}

private Alarm setImplLocked(int type, long when, long whenElapsed, long windowLength,
long maxWhen, long interval, PendingIntent operation, IAlarmListener directReceiver,
String listenerTag, int flags, boolean doValidate, WorkSource workSource,
AlarmManager.AlarmClockInfo alarmClock, int callingUid, String callingPackage) {
Alarm a = new Alarm(type, when, whenElapsed, windowLength, maxWhen, interval,
operation, directReceiver, listenerTag, workSource, flags, alarmClock,
callingUid, callingPackage);
...
setImplLocked(a, false, doValidate);
return a;
}

private void setImplLocked(Alarm a, boolean rebatching, boolean doValidate) {
...
rescheduleKernelAlarmsLocked(); //调用内核的native方法设置闹钟
updateNextAlarmClockLocked(); //更新状态栏中显示的系统图标状态
...
}

private void updateNextAlarmClockLocked() {
...
updateNextAlarmInfoForUserLocked(userId, newAlarm); //更新指定userId状态栏中显示的系统图标状态
...
}

private void updateNextAlarmInfoForUserLocked(int userId,
AlarmManager.AlarmClockInfo alarmClock) {
...
mHandler.removeMessages(AlarmHandler.SEND_NEXT_ALARM_CLOCK_CHANGED);
mHandler.sendEmptyMessage(AlarmHandler.SEND_NEXT_ALARM_CLOCK_CHANGED); //通过UI线程去发送广播
}

private class AlarmHandler extends Handler {
public void handleMessage(Message msg) {
...
switch (msg.what) {
case SEND_NEXT_ALARM_CLOCK_CHANGED:
sendNextAlarmClockChanged();
break;
...
}
}

private void sendNextAlarmClockChanged() {
....
getContext().sendBroadcastAsUser(NEXT_ALARM_CLOCK_CHANGED_INTENT,
new UserHandle(userId)); //发送广播去显示状态栏中的系统图标
....
}

private static final Intent NEXT_ALARM_CLOCK_CHANGED_INTENT =
new Intent(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)
.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);

以下是如何调用内核来设置闹钟和闹钟时间到后的处理逻辑,可以参考如下,不过这里我们只是跟踪状态栏上系统图标中闹钟图标的显示过程,所以就不深入跟踪了,有兴趣的同事可以自己研究。

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
void rescheduleKernelAlarmsLocked() {				//调用内核的native方法设置闹钟
...
setLocked(ELAPSED_REALTIME, nextNonWakeup);
...
}

private void setLocked(int type, long when) {
...
set(mNativeData, type, alarmSeconds, alarmNanoseconds);
...
}

private native void set(long nativeData, int type, long seconds, long nanoseconds);

private class AlarmThread extends Thread
{
public AlarmThread()
{
super("AlarmManager");
}
public void run()
{
while (true)
{
...
int result = waitForAlarm(mNativeData); //循环调用阻塞函数waitForAlarm()来监听到Alarm变化
boolean hasWakeup = triggerAlarmsLocked(triggerList, nowELAPSED, nowRTC);
if (!hasWakeup && checkAllowNonWakeupDelayLocked(nowELAPSED)) {
rescheduleKernelAlarmsLocked(); //调用内核的native方法设置闹钟
updateNextAlarmClockLocked(); //更新状态栏中显示的系统图标状态
}
...
}
}
}

4). SystemUI接收广播:

PhoneStatusBarPolicy.java

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
private final StatusbarManager mService;

private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)) {
updateAlarm();
}
...
}
};

public PhoneStatusBarPolicy(Context context, StatusBarIconController iconController,
CastController cast, HotspotController hotspot, UserInfoController userInfoController,
BluetoothController bluetooth, RotationLockController rotationLockController,
DataSaverController dataSaver) {

// listen for broadcasts
IntentFilter filter = new IntentFilter();
filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
filter.addAction(TelecomManager.ACTION_CURRENT_TTY_MODE_CHANGED);
mContext.registerReceiver(mIntentReceiver, filter, null, mHandler);
...
// Alarm clock
mService.setIcon(SLOT_ALARM_CLOCK, R.drawable.stat_sys_alarm, 0, null);
mService.setIconVisibility(SLOT_ALARM_CLOCK, false);
}

private void updateAlarm() {
final AlarmClockInfo alarm = mAlarmManager.getNextAlarmClock(UserHandle.USER_CURRENT);
final boolean hasAlarm = alarm != null && alarm.getTriggerTime() > 0;
final boolean zenNone = mZen == Global.ZEN_MODE_NO_INTERRUPTIONS;
if (mCurrentUserSetup && hasAlarm) {
//通过StatusbarManagerService去更新系统图标
mService.setIcon(SLOT_ALARM_CLOCK, R.drawable.stat_sys_alarm, 0, null);
}
//通过StatusbarManagerService去显示系统图标
mService.setIconVisibility(SLOT_ALARM_CLOCK, mCurrentUserSetup && hasAlarm);
}

5). StatusBarManagerService调用SystemUI的通信对象来添加系统图标:

StatusBarManagerService.java

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
private volatile IStatusBar mBar;

@Override
public void registerStatusBar(IStatusBar bar, StatusBarIconList iconList,
int switches[], List<IBinder> binders) {
...
mBar = bar;
...
}

@Override
public void setIcon(String slot, String iconPackage, int iconId, int iconLevel,
String contentDescription) {
int index = mIcons.getSlotIndex(slot);
StatusBarIcon icon = new StatusBarIcon(iconPackage, UserHandle.OWNER, iconId,
iconLevel, 0,
contentDescription);
mIcons.setIcon(index, icon);
if (mBar != null) {
try {
mBar.setIcon(index, icon);
} catch (RemoteException ex) {
}
}
}

@Override
public void setIconVisibility(String slot, boolean visible) {
int index = mIcons.getSlotIndex(slot);
StatusBarIcon icon = mIcons.getIcon(index);
if (icon.visible != visible) {
icon.visible = visible;
if (mBar != null) {
try {
mBar.setIcon(index, icon);
} catch (RemoteException ex) {
}
}
}
}
}

这里的IStatusBar其实就是SystemUI中的CommandQueue类,它是IStatusBar.Stub的子类,它是在BaseStatusBar的start()方法中通过获取StatusBarManagerService服务对象,调用服务对象的registerStatusBar(IStatusBar bar, StatusBarIconList iconList,int switches[], List binders)方法把CommandQueue对象注册到StatusBarManagerService服务对象中来方便StatusBarManagerService服务和SystemUI进程之间进行通信的。BaseStatusBar中建立和StatusBarManagerService服务通信的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
protected IStatusBarService mBarService;

public void start() {
...
mCommandQueue = new CommandQueue(this, mIconList);
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
try {
mBarService.registerStatusBar(mCommandQueue, mIconList, switches, binders);
} catch (RemoteException ex) {
// If the system process isn't there we're doomed anyway.
}
...
}

6). SystemUI中CommandQueue通信接口:

CommandQueue.java

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
private Callbacks mCallbacks;

public CommandQueue(Callbacks callbacks, StatusBarIconList list) {
mCallbacks = callbacks; //mCallbacks是BaseStatusBar和PhoneStatusBar实现CommandQueue.Callbacks的接口对象
mList = list;
}

public void setIcon(int index, StatusBarIcon icon) {
synchronized (mList) {
int what = MSG_ICON | index;
mHandler.removeMessages(what);
mHandler.obtainMessage(what, OP_SET_ICON, 0, icon.clone()).sendToTarget(); //统一发到handler中处理
}
}

private final class H extends Handler {
public void handleMessage(Message msg) {
final int what = msg.what & MSG_MASK;
switch (what) {
case MSG_ICON: {
final int index = msg.what & INDEX_MASK;
final int viewIndex = mList.getViewIndex(index);
switch (msg.arg1) {
case OP_SET_ICON: {
StatusBarIcon icon = (StatusBarIcon)msg.obj;
StatusBarIcon old = mList.getIcon(index);
if (old == null) {
mList.setIcon(index, icon);
//调用PhoneStatusBar.addIcon()方法来添加系统图标
mCallbacks.addIcon(mList.getSlot(index), index, viewIndex, icon);
} else {
mList.setIcon(index, icon);
//调用PhoneStatusBar.addIcon()方法来更新系统图标
mCallbacks.updateIcon(mList.getSlot(index), index, viewIndex,
old, icon);
}
break;
}
}
break;
}
}
}
}

7). SystemUI中PhoneStatusBar调用状态栏图标控制类来添加系统图标:

PhoneStatusBar.java

1
2
3
4
5
StatusBarIconController mIconController;	//状态栏图标控制类

public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) {
mIconController.addSystemIcon(slot, index, viewIndex, icon);
}

8). SystemUI中StatusBarIconController添加系统图标:

StatusBarIconController.java & StatusBarIconView.java

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
//StatusBarIconController.java

private LinearLayout mStatusIcons;

public StatusBarIconController(Context context, View statusBar, View keyguardStatusBar,
PhoneStatusBar phoneStatusBar) {
...
mStatusIcons = (LinearLayout) statusBar.findViewById(R.id.statusIcons); //系统图标布局容器
...
}

public void addSystemIcon(String slot, int index, int viewIndex, StatusBarIcon icon) {
boolean blocked = mIconBlacklist.contains(slot);
StatusBarIconView view = new StatusBarIconView(mContext, slot, null, blocked, 0);
view.set(icon);
mStatusIcons.addView(view, viewIndex, new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize)); //把图标添加到布局容器中
view = new StatusBarIconView(mContext, slot, null, blocked, 0);
view.set(icon);
mStatusIconsKeyguard.addView(view, viewIndex, new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize));
applyIconTint();
}



//StatusBarIconView.java

public boolean set(StatusBarIcon icon) {
if (!iconEquals) {
if (!updateDrawable(false /* no clear */)) return false;
}
if (!numberEquals) {
if (icon.number > 0 && getContext().getResources().getBoolean(
R.bool.config_statusBarShowNumber)) {
placeNumber();
} else {
mNumberBackground = null;
mNumberText = null;
}
invalidate();
}
if (!visibilityEquals) {
setVisibility(icon.visible && !mBlocked ? VISIBLE : GONE);
}
return true;
}

public void updateDrawable() {
updateDrawable(true /* with clear */);
}

private boolean updateDrawable(boolean withClear) {
Drawable drawable = getIcon(mIcon);
setImageDrawable(drawable);
return true;
}

private Drawable getIcon(StatusBarIcon icon) {
return getIcon(getContext(), icon);
}

public static Drawable getIcon(Context context, StatusBarIcon icon) {
return icon.icon.loadDrawableAsUser(context, userId);
}

4.总结

通过以上对状态栏中闹钟图标的显示过程,我们可以知道,状态栏右边的系统图标的显示,是可以由系统App控制来显示的,系统App可以通过获取StatusBarManagerService这个服务对象来设置状态栏中的系统图标。

代码实现如下:

1
2
3
4
private static final String SLOT_ALARM_CLOCK = "alarm_clock";
private final StatusBarManager mService = (StatusBarManager) context.getSystemService(Context.STATUS_BAR_SERVICE);
mService.setIcon(SLOT_ALARM_CLOCK, R.drawable.stat_sys_alarm, 0, null);
mService.setIconVisibility(SLOT_ALARM_CLOCK, mCurrentUserSetup && hasAlarm);

还要记得在AndroidManifest.xml文件中加入以下权限:

<uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
<uses-permission android:name="android.permission.STATUS_BAR" />