在展讯的8810智能机平台上,发现一个modem assert发来的信号,它会在状态栏上(StatusBar)的左侧显示出一个海盗图标,这是怎么回事?
1.
那么就先看看状态栏的左侧图标是如何显示出来的吧?
1.1
查看了相关信息,如果想在状态栏上显示出自定义的图标, 一般是使用如下的流程:
private NotificationManager nm;private Notification n;
nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
n = new Notification();
n.icon = R.drawable.icon;
n.tickerText = "Test Notification";
n.when = System.currentTimeMillis();
Intent intent = new Intent(MainActivity.this,MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(MainActivity.this,0,intent,0);
n.setLatestEventInfo(MainActivity.this,"My Title","My Content",pi);
Log.d(TAG,"nm.notify....");
nm.notify(1,n);
在调用notify之后, 自定义的图标就会显示到状态栏上的左侧位置。
1.2
但是整个系统中,调用notify接口的应用非常多, 一下在找不出到底谁在调用notify来显示海盗图标。
因此,考虑在notify这个接口内容加一些log信息来追踪源头。
先找到NotificationManager的notify函数实现如下:public void notify(String tag, int id, Notification notification)
{
int[] idOut = new int[1];
INotificationManager service = getService();
String pkg = mContext.getPackageName();
if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
try {
service.enqueueNotificationWithTag(pkg, tag, id, notification, idOut);
if (id != idOut[0]) {
Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);
}
} catch (RemoteException e) {
}
}
注意: if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
从上面这个打印语句,我们可以得到究竟是哪个package的代码在调用notify接口,这正是我需要的,enable这个log语句,然后再次复现问题,抓取log:E/ModemClient( 561): Modem Assert!!!!
E/ModemClient( 561): Assert in file TdDspAssert.c at line 142 info=[TD DSP ASSERT ID 0x5010]
V/NotificationManager( 561): com.android.modemassert: notify(1, Notification(vibrate=[0,10000],sound=default,defaults=0x1,flags=0x0))
很高兴,我们很顺利的得到了package的名字: com.android.modemassert, 根据这个信息,我们找到了modemassert.apk, 看到调用notify的代码位于ModemAssert.java中。
1.3
既然是个apk,那么查看一下Manifest.xml:
<receiver android:name="StartAssert"><intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<service android:name="com.android.modemassert.ModemAssert"></service>
apk包含一个BroadcastReceiver和一个Service。
1.3.1
BroadcastReceiver接收到BOOT_COMPLETED消息后会startService。
1.3.2
在service的onCreate()中创建socket:
s = new LocalSocket();LocalSocket s = null;
LocalSocketAddress l = null;
mSocket = s;
mSocketAddr = l;
mSocket.connect(mSocketAddr);
注意:private static final String MODEM_SOCKET_NAME = "modemd";
通过这个socket,如果接收到发来的Assert信息,就把它显示到statusBar上。
由此可知,modemassert.apk是socket的client端,那么socket的server端是谁呢?
2.
通过socket_name:"modemd"来搜索,可以找到在modemd.c中有:
#define MODEM_SOCKET_NAME "modemd"modemd是一个可执行程序,即一个守护进程,在它的main函数中:
2.1
先开启一个线程,在线程中创建servier socket,与client之间建立链接。
sfd = socket_local_server(MODEM_SOCKET_NAME,0, SOCK_STREAM);。。。
n=accept(sfd,NULL,NULL);
。。。
client_fd[i]=n;
把已经建立好连接的client socketId 存放在client_fd[]中。
2.2
从“/dev/vbpipe2“中读取数据,如果读到assertinfo,就将它发送给socket的client端。
#define MODEM_PATH "/dev/vbpipe2"。。。
modemfd = open(MODEM_PATH, O_RDONLY);
。。。
numRead = read(modemfd, buf, DATA_BUF_SIZE);
。。。
ret = write(client_fd[i], buf, numRead);
看到这里,实际上modem的assertInfo是通过“dev/vbpipe2”读取到的。这个有待进一步分析了。
2.3
我们还可以在init.rc中看到modemd的启动信息:
service modemd /system/bin/modemd
socket modemd stream 666 system system
user system
group system radio
oneshot
总的来说,modemassert.apk和modemd守护进程之间构成了socket的client和server交互,最终传送assertinfo到用户界面的状态栏上。
通过命令:adb shell procrank可以看到系统开机后,这两个进程都开启了:
610 16016K 15968K 2092K 1416K com.android.modemassert
95 324K 320K 85K 80K /system/bin/modemd