背景
针对之前的文章《Android 利用多APN 实现APN分离》,新的需求到来啦。不但要求不同的用户用不同APN上网,而且还要各自的用户可以对数据分别开关(即通过systemUI的快捷方式开个数据的时候只针对当前用户),这样多用户的环境隔离更加彻底,在数据上网这块,相互不影响。
所谓的上网分离
之前分离APN,实现了手机有拨号产生两个IP地址,分别对应两个用户。但是对于标准安卓系统,数据开关是作为一个整体来看的,只要数据开关关闭,那么所以建好的数据链接都会被断掉,然后整个手机都无法上网,反之亦然。现在,我们要做的就是扩展这个控制开关,使之可以针对不同的用户生效,并且还要兼容标准SDK的接口,即装在另一个用户的app,如果需要控制数据上网,那么不应该影响另一个用户app数据的使用,这就需要对监听数据状态的应用根据用户进行分类,本用户数据状态的改变不应该发送给另一个用户,反正亦然。
主要思路
需要改动的点如下:
- SystemUI的分开显示(即通知栏上数据的状态图标要根据本用户的数据设置来显示)。
- ConnectivityService中数据状态的转换,即当本用户app查询数据情况的时候,要分别返回各自的网络状态。
- DcTracker中,对于建立和拆除数据连接的操作要根据当前用户来分别的保留另一个用户的数据连接,只能针对当前用户进行数据操作。
- 标准SDK接口PhoneListener监听数据状态时候,对于改变onDataConnectionStateChanged/onDataActivity消息的发送,要针对不同的用户发送。
- TelephonyManager中对于数据开关的查询和设置调用,要根据用户调用不同的接口。
- PhoneInterfaceManger中,对于数据开关的查询和设置要扩展新的对用户的接口。并且,对各个用户数据开关状态的维护也要在这个类里面做。
- 在Setting的database中,添加对于用户的数据状态的相关内容的存储。
- 我们还有自己的mdm数据管控接口,这块也要修改相关的代码(这个不是不是标准安卓的)。
关键的改动
不会根据上面每一个都列出来,这里只列出关键的改动,其他的都是对新接口的适配,所以就不详说啦。
framework的改动
- ITelephony.aidl(frameworks/base/telephony/java/com/android/internal/telephony/ITelephony.aidl)中添加安装用户id来获得和设置数据开关的接口
void setDataEnabledAsUser(int subId, boolean enable, int userId);
boolean getDataEnabledAsUser(int subId, int userId);
- Telephony Manger.java(frameworks/base/telephony/java/android/telephony/TelephonyManager.java)
主要做的就是包装标准接口getDataEnabled/setDataEnabled的实现来调用新的getDataEnabledAsUser/setDataEnabledAsUser接口,具体实现的地方是在PhoneInterfaceManager中实现。这其实就是简单的aidl远程调用。
/**
* @hide
*/
public boolean getDataEnabled(int subId, int userId) {
boolean retVal = false;
Log.d(TAG, "getDataEnabled subId " + subId + " userId " + userId);
try {
ITelephony telephony = getITelephony();
if (telephony != null)
retVal = telephony.getDataEnabledAsUser(subId, userId);
Log.d(TAG, "getDataEnabled retVal " + retVal);
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelephony#getDataEnabled", e);
} catch (NullPointerException e) {
}
return retVal;
}
/**
* @hide
*/
public void setDataEnabledAsUser(int subId, boolean enable, int userId) {
try {
Log.d(TAG, "setDataEnabled: enabled=" + enable + " userId " + userId);
ITelephony telephony = getITelephony();
if (telephony != null)
telephony.setDataEnabledAsUser(subId, enable, userId);
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelephony#setDataEnabled", e);
}
}
- ConnectivityService(frameworks/base/services/core/java/com/android/server/ConnectivityService.java)中的getNetworkInfo函数实现的修改,如果发现是非主用户的app来查询networkInfo,这个时候,因为是我们扩展的非标准的连接正在生效,那么我们需要把非标准的连接info转换成标准的default类型的mobile连接,返回给应用,这样应用可以获得正确的网络状态。
@Override
public NetworkInfo getNetworkInfo(int networkType) {
NetworkState state;
//final NetworkState state = getFilteredNetworkState(networkType, uid, false);
if (currentUserId == 10 && networkType == ConnectivityManager.TYPE_MOBILE) {
state = getFilteredNetworkState(ConnectivityManager.TYPE_MOBILE_DEFAULT2, uid, false);
state.networkInfo.setType(ConnectivityManager.TYPE_MOBILE);
state.networkInfo.setTypeName(getNetworkTypeName(ConnectivityManager.TYPE_MOBILE));
Log.d(TAG, "getNetworkInfo after modified networkInfo " + state.networkInfo);
} else {
state = getFilteredNetworkState(networkType, uid, false);
}
return state.networkInfo;
}
- TelephonyRegistry.java(frameworks/baseservices/core/java/com/android/server/TelephonyRegistry.java)对于PhoneListener回掉实现的更改。
public void notifyDataConnectionForSubscriber(int subId, int state,
boolean isDataConnectivityPossible, String reason, String apn, String apnType,
LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
int networkType, boolean roaming) {
.......
int currentUserId = ActivityManager.getCurrentUser();
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
PhoneStateListener.LISTEN_DATA_CONNECTION_STATE) &&
idMatch(r.subId, subId, phoneId)) {
try {
if (r.callingPackage.equals("com.android.systemui")
&& ((apnType.equals(PhoneConstants.APN_TYPE_DEFAULT2) && currentUserId == 10)
|| (apnType.equals(PhoneConstants.APN_TYPE_DEFAULT) && currentUserId == 0))) {
log("Notify data connection for systemui");
r.callback.onDataConnectionStateChanged(mDataConnectionState[phoneId],
mDataConnectionNetworkType[phoneId]);
} else if (((r.callerUserId != currentUserId) || r.callingPackage.equals("com.android.systemui"))
&& (apnType.equals(PhoneConstants.APN_TYPE_DEFAULT) || apnType.equals(PhoneConstants.APN_TYPE_DEFAULT2))) {
log("Notify data connection: backup state for other user");
mDataConnectionStateForOtherUser = mDataConnectionState[phoneId];
mDataConnectionNetworkTypeForOtherUser = mDataConnectionNetworkType[phoneId];
mBackupSubId = subId;
mBackupPhoneId = phoneId;
} else {
log("Notify data connection state changed on sub: " +
subId + " record: " + r + " currentUser " + ActivityManager.getCurrentUser() + " apnType " + apnType);
r.callback.onDataConnectionStateChanged(mDataConnectionState[phoneId],
mDataConnectionNetworkType[phoneId]);
}
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
.......
- DcTracker.java(frameworks/opt/telephony/src/java/com/android/internal/telephony/dataconnection/DcTracker.java)中cleanUpAllConnections函数中,不拆除另一个用户的数据。
if ((cleanUpDefault2 && apnContext.getApnType().equals(PhoneConstants.APN_TYPE_DEFAULT))
|| (!cleanUpDefault2 && apnContext.getApnType().equals(PhoneConstants.APN_TYPE_DEFAULT2))) {
log("cleanUpAllConnections cleanUpDefault2 " + cleanUpDefault2 + " type " + apnContext.getApnType() + " ignore!!!");
continue;
}
- PhoneInterfaceManager.java(packages/services/Telephony/src/com/android/phone/PhoneInterfaceManager.java)
实现ITelephony中定义的函数。
public boolean getDataEnabledAsUser(int subId, int userId) {
boolean enabled = !mDisabledUsers.contains(Integer.valueOf(userId));
log("getDataEnabledAsUser ret " + enabled);
return enabled;
}
public void setDataEnabledAsUser(int subId, boolean enable, int userId) {
final boolean oldValue = enable;
if (userId >= 0) {
Integer uobj = Integer.valueOf(userId);
if (enable) {
if (mDisabledUsers.contains(uobj)) {
mDisabledUsers.remove(uobj);
persistDataUsersState();
}
} else {
if (!mDisabledUsers.contains(uobj)) {
mDisabledUsers.add(uobj);
persistDataUsersState();
}
}
enable = !mDisabledUsers.containsAll(getAllUserIds());
}
if (enable) {
persistDataState(DATA_ENABLED);
} else {
persistDataState(DATA_DISABLED);
}
}
可以看到,我们用了一个mDisabledUsers的集合来保存禁用数据的用户id,只有当两个用户多打开或者都关闭数据的时候,才会改变标准的数据设置。这样我们就不用扩展Setting数据库里面数据开关啦。但是我们还是要需要保存禁用数据的用户的,这样我们还是要扩展一个Setting数据库项"data_disabled_users",里面存的是用户id,用空格分开。这样做的好处就是方便扩展,如果以后有更多的用户要进行数据分离,那么我们就不需要再扩展Setting数据库了,如果一个用户对应一个数据库项的话,那么用户增多还会带来代码的修改。
总结
其实,这个需求是顺着APN分离来的,但是比APN分离要麻烦琐碎,因为要考虑标准接口的兼容,不同用户app获取状态的统一,所以改动和适配的地方更多一些。另外,对于如何保存分离的开关数据,也是考虑了好久才定下的方案。
基本上,对于数据连接这块的分离就完全实现啦。使用多用户的时候可以感觉在用两个不同的手机。
网友评论