android4.2增长了多用户功能,终于在迟迟以后与linux保持了一致。可是手机上的多用户实际上是至关鸡肋的,试想手机这种移动设备基本 上就是每个人的惟一id,因此基本上不存在多用户共用设备的状况。也正由于此以及专利的缘由,因此电话上的多用户功能是关闭的,只有平板上的多用户是打 开的。但仍是要感谢谷歌开发人员引入多用户机制,这样能够帮助开发一些安全系统有极大帮助。java
但打开多用户以后,有一个比较蛋疼的地方是没法在多用户中打电话发短信。查看源码的知,这是android对于电话通讯这一块根本没有作多用户兼容性适 配。只是在PhoneApp中简单粗暴的作了一个单用户判断if (UserHandle.myUserId() == UserInfo.ROOT_USER_ID ),在其它用户中根本没法使用电话和短信。而平板上又无这种通讯需求,因此谷歌开发人员根本没在这块作代码适配。唉,无奈咱们公司项目须要这方面的功能, 即在多用户下也要能打电话发短信。因此只能硬这头皮上了,去作谷歌人员未竟的工做。这个过程是痛苦的前期后后后找了好几个phone方面比较熟悉的兄弟帮 忙分析代码,加起来有快10天的工做量,终于初步知足了需求,能打电话发短信了。下面是将这一过程当中遇到的问题,作个摘录以备忘。linux
首先理解多用户原理,就算多用户究竟是一个什么东西以及是一个什么样的实现机制。多用户固名思意,就是在同一台设备上隔离出另外一个用户空间,这个空间里 面运行的程序与普通空间运行的程序是隔离的,彻底是在两个进程中,固然数据的存储,好比数据库也彻底是独立的。最直观的感觉就是,两个用户中装上同一个应 用,那么这两个应用的导航页是须要走两遍的。固然这里面涉及到不少隔离,好比install,锁屏,某些设置等,因此在看4.2以后的源代码,会发现基本 上全部的模块都涉及到多用户的判断,这说明android在加多用户这个功能的时候,谷歌开发人员也作了一个海量的工做啊,这里表示钦佩和感激。android
好了,闲话少说,继续说多用户下phone进程问题。数据库
一。首先建立多用户以后,该用户下会有一个com.android.phone进程,可是该进程是彻底没有东西的,由于在进程入口PhoneApp中已经作了判断:安全
public void onCreate() {
if (UserHandle.myUserId() == 0) {
// We are running as the primary user, so should bring up the
// global phone state.
if (MSimTelephonyManager.getDefault().isMultiSimEnabled()) {
mPhoneGlobals = new MSimPhoneGlobals(this);
} else {
mPhoneGlobals = new PhoneGlobals(this);
}
mPhoneGlobals.onCreate();
}
}网络
看到了吧,只有在0用户(根用户)下,才能进行phone模块的初始化。因此这里作了第一个修改就是拿掉这个判断,这个很容易,再也不赘述。app
代码位置android/packages/services/Telephony/src/com/android/phone/PhoneApp.java中的onCreate()方法socket
二。去掉判断以后,固然但愿能直接打电话了,但事情永远不会这么顺利,接下来下面这个异常挡住了前路:ui
E/AndroidRuntime( 3452): Caused by: java.lang.SecurityException
E/AndroidRuntime( 3452): at android.os.BinderProxy.transact(Native Method)
E/AndroidRuntime( 3452): at android.os.ServiceManagerProxy.addService(ServiceManagerNative.java:150)
E/AndroidRuntime( 3452): at android.os.ServiceManager.addService(ServiceManager.java:72)
E/AndroidRuntime( 3452): at com.android.internal.telephony.IccSmsInterfaceManager.<init>(IccSmsInterfaceManager.java:128)
E/AndroidRuntime( 3452): at com.android.internal.telephony.PhoneProxy.init(PhoneProxy.java:95)
E/AndroidRuntime( 3452): at com.android.internal.telephony.PhoneProxy.<init>(PhoneProxy.java:84)
E/AndroidRuntime( 3452): at com.android.internal.telephony.PhoneFactory.makeDefaultPhone(PhoneFactory.java:134)
E/AndroidRuntime( 3452): at com.android.internal.telephony.PhoneFactory.makeDefaultPhones(PhoneFactory.java:59)
E/AndroidRuntime( 3452): at com.android.phone.PhoneGlobals.onCreate(PhoneGlobals.java:416)
E/AndroidRuntime( 3452): at com.android.phone.PhoneApp.onCreate(PhoneApp.java:45)
E/AndroidRuntime( 3452): at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1009)
E/AndroidRuntime( 3452): at android.app.LoadedApk.makeApplication(LoadedApk.java:526)
E/AndroidRuntime( 3452): ... 12 morethis
分析异常log,发现是在添加一个与短信相关的服务isms时失败,报了一个 SecurityException。可是在普通用户下这个服务是添加成功 了的,因此在这里有一个初步的猜想,就是添加系统服务这个动做并非任何进程都能作的,由于在普通用户下phone进程uid=1001,而在多用户下 phone的uid=901001(9用户),因此猜想是底层c代码中对addservice的调用者作了权限控制。果不其然,跟踪addservice 的源码最后跟到c层,在service_manager.c中有一个do_add_service()方法,这个方法就是ServiceManager的 addService方法的最终实现,在该方法中对调用者的uid作了一个判断:
int svc_can_register(unsigned uid, uint16_t *name)
{
if ((uid == 0) || (uid == AID_SYSTEM))
return 1;
for (n = 0; n < sizeof(allowed) / sizeof(allowed[0]); n++)
if ((uid == allowed[n].uid) && str16eq(name, allowed[n].name))
return 1;
return 0;
}
能够发现,只有root,system,以及系统基础的phone,短信等进程才能经过判断,而9用户中的phone进程的uid==901001(多用 户进程uid==userId*100000+uid),显然不在这个范围以内,固然添加系统服务失败。修改方法是在这里面加上一句多用户兼容性适配 uid = uid % 100000。这样多用户下相关进程也是能够添加系统服务了。
代码位置:android/frameworks/native/cmds/servicemanager/service_manager.c的svc_can_register()方法.
三。服务添加完了,事情还远远没有结束。接下来,在多用户中,打电话时提示“没法访问移动网络”.
显然手机没有连上信号,查看log
I/RILJ ( 3839): Couldn't find 'rild' socket; retrying after timeout
I/RILJ ( 3839): Couldn't find 'rild' socket; retrying after timeout
I/RILJ ( 3839): Couldn't find 'rild' socket; retrying after timeout
I/RILJ ( 3839): Couldn't find 'rild' socket; retrying after timeout
I/RILJ ( 3839): Couldn't find 'rild' socket; retrying after timeout
I/RILJ ( 3839): Couldn't find 'rild' socket; retrying after timeout
I/RILJ ( 3839): Couldn't find 'rild' socket; retrying after timeout
E/RILJ ( 3839): Couldn't find 'rild' socket after 8 times, continuing to retry silently
这些log是在RIL.java中报出来的。这是java层与ril层通讯的类,而这个异常就是在与ril连接的时候报出来的,说明在多用户下是连接不上ril的。
事情到这里就很很差搞了,本人对于ril通讯这一块彻底没接触过啊。可是项目工期紧急,只能去求助公司的phone这块研发人员。最终,黄天不负苦 心人,通过与phone这块人员的讨论以后,找到了一个解决方案:就是在用户切换的时候,对ril进行一个断开重连的过程,由于ril连接同时只能被一个 客户端连接,而多用户里是会有多个phone进程的。因此在这里,必须将以前普通用户下的ril连接断开,在新用户里从新连接ril。固然,这里确定须要 一个对phone很熟悉的人员找到这样一个切换连接的地方,而咱们公司正好各方面的人才都有,实现这样一个方案不是太困难。
同时,为了连上ril还要在ActivityManagerService的startProcessLocked()方法里开启phone进程的时候,将进程的gid=1001.这里是一个权限判断,对于gid!=1001的进程是不能连上ril服务的。
四。用户切换时要替换两个系统服务
在PhoneInterfaceManager里切换phone的service(打电话相关)IccSmsInterfaceManager里切换isms(发短信相关)
这两个修改主要是保证来回切换用户时,其它进程经过这两个服务获取电话短信相关状态操做时能正确获取。
好了,总体上主要是作了这四个修改。虽然这里说的很简单,但整个过程实际上是很漫长纠结的,一度觉得解决不了了,毕竟google人员这一块也是简单粗暴的没有处理。但万幸最终在同僚帮助下解决了。
感谢同僚!