手机的设置中可以获取apn列表,通过代码分析,会调用ApnSetting的静态方法mvnoMatches过滤部分虚拟运营商,最终得到apn列表。
if (r != null && !TextUtils.isEmpty(mvnoType) && !TextUtils.isEmpty(mvnoMatchData)) {
if (ApnSetting.mvnoMatches(r, mvnoType, mvnoMatchData)) {
mvnoList.add(pref);
......
}
} else {
mnoList.add(pref);
}
mvnoMatches() 的实现如下:
public static boolean mvnoMatches(IccRecords r, String mvnoType, String mvnoMatchData) {
if (mvnoType.equalsIgnoreCase("spn")) {
if ((r.getServiceProviderName() != null) &&
r.getServiceProviderName().equalsIgnoreCase(mvnoMatchData)) {
return true;
}
} else if (mvnoType.equalsIgnoreCase("imsi")) {
String imsiSIM = r.getIMSI();
if ((imsiSIM != null) && imsiMatches(mvnoMatchData, imsiSIM)) {
return true;
}
} else if (mvnoType.equalsIgnoreCase("gid")) {
String gid1 = r.getGid1();
int mvno_match_data_length = mvnoMatchData.length();
if ((gid1 != null) && (gid1.length() >= mvno_match_data_length) &&
gid1.substring(0, mvno_match_data_length).equalsIgnoreCase(mvnoMatchData)) {
return true;
}
}
/// M: reflection for telephony add-on @{
try {
if (sMethodMvnoMatchesEx != null) {
return (boolean) sMethodMvnoMatchesEx.invoke(null, r, mvnoType, mvnoMatchData);
}
} catch (Exception e) {
Rlog.d(LOG_TAG, e.toString());
}
/// @}
return false;
}
其中r是通过mUiccController获取的,而mUiccController是mUiccController的单例对象。
r = mUiccController.getIccRecords(SubscriptionManager.getPhoneId(
mSubscriptionInfo.getSubscriptionId()), UiccController.APP_FAM_3GPP);
mUiccController = UiccController.getInstance();
在对应的清单文件中,标明了它运行在phone进程。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
package="com.android.settings"
coreApp="true"
android:sharedUserId="android.uid.system">
<original-package android:name="com.android.settings" />
<application >
<!-- Runs in the phone process since it needs access to UiccController -->
<activity android:name="Settings$ApnSettingsActivity"
android:label="@string/apn_settings"
android:launchMode="singleTask"
android:taskAffinity="com.android.settings"
android:configChanges="orientation|keyboardHidden|screenSize|screenLayout|smallestScreenSize"
android:parentActivityName="Settings$NetworkDashboardActivity"
android:process="com.android.phone">
<intent-filter android:priority="1">
<action android:name="android.settings.APN_SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.VOICE_LAUNCH" />
</intent-filter>
<meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
android:value="true" />
<meta-data android:name="com.android.settings.FRAGMENT_CLASS"
android:value="com.android.settings.ApnSettings" />
</activity>
</application>
<uses-permission android:name="android.permission.WRITE_APN_SETTINGS"/>
</manifest>
打算通过aidl来实现获取apn列表的功能:客户端发起调用,绑定服务端的service,获取apn列表后,
调用系统的mvnoMatches方法[自己实现不易],精确筛选后,把获取结果返回,最后在客户端显示。
就模仿着settings,使service运行在phone进程。
<service android:name="com.mediatek.settings.service.MyService"
android:process="com.android.phone">
<intent-filter>
<action android:name="com.mediatek.settings.MyService.action"/>
</intent-filter>
</service>
但是在查询数据库时,会报无权限的错误
08-01 10:38:32.295 853 877 W System.err: java.lang.SecurityException: No permission to write APN settings
08-01 10:38:32.296 853 877 W System.err: at com.android.providers.telephony.TelephonyProvider.checkPermission(TelephonyProvider.java:2900)
08-01 10:38:32.296 853 877 W System.err: at com.android.providers.telephony.TelephonyProvider.query(TelephonyProvider.java:2273)
08-01 10:38:32.296 853 877 W System.err: at android.content.ContentProvider.query(ContentProvider.java:1055)
08-01 10:38:32.296 853 877 W System.err: at android.content.ContentProvider.query(ContentProvider.java:1147)
08-01 10:38:32.296 853 877 W System.err: at android.content.ContentProvider$Transport.query(ContentProvider.java:240)
08-01 10:38:32.296 266 1555 D AudioALSAStreamManager: +syncSharedOutDevice(), routingSharedOutDevice: 2
08-01 10:38:32.296 853 877 W System.err: at android.content.ContentResolver.query(ContentResolver.java:754)
08-01 10:38:32.296 853 877 W System.err: at android.content.ContentResolver.query(ContentResolver.java:704)
08-01 10:38:32.296 853 877 W System.err: at android.content.ContentResolver.query(ContentResolver.java:662)
08-01 10:38:32.297 853 877 W System.err: at com.mediatek.settings.service.MyAPNManager.getAPN(MyAPNManager.java:122)
08-01 10:38:32.297 266 1555 D AudioALSAStreamManager: -syncSharedOutDevice()
08-01 10:38:32.297 853 877 W System.err: at com.mediatek.settings.service.MyApnService$1.getAPN(MyApnService.java:56)
08-01 10:38:32.297 853 877 W System.err: at com.mediatek.settings.service.CSAndoridGoApn$Stub.onTransact(CSAndoridGoApn.java:50)
08-01 10:38:32.297 266 1555 D AudioALSAVolumeController: AudioALSAVolumeController getMasterVolume
08-01 10:38:32.297 853 877 W System.err: at android.os.Binder.execTransact(Binder.java:697)
通过log对比发现,TelephonyProvider的checkPermission方法,settings源码处返回0,即PackageManager.PERMISSION_GRANTED,而自己调用却返回-1.
private void checkPermission() {
int status = getContext().checkCallingOrSelfPermission(
"android.permission.WRITE_APN_SETTINGS");
if (status == PackageManager.PERMISSION_GRANTED) {
return; //settings的为真,此处返回
}
PackageManager packageManager = getContext().getPackageManager();
String[] packages = packageManager.getPackagesForUid(Binder.getCallingUid());
TelephonyManager telephonyManager =
(TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
for (String pkg : packages) {
if (telephonyManager.checkCarrierPrivilegesForPackage(pkg) ==
TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
return;
}
}
throw new SecurityException("No permission to write APN settings");
}
但查看清单文件,该权限确实存在。就怀疑phone进程对应的清单文件内是否添加对应的权限
就找到了alps/vendor/mediatek/proprietary/packages/services/Telephony/AndroidManifest.xml文件,该权限也存在。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
package="com.android.phone"
coreApp="true"
android:sharedUserId="android.uid.phone"
android:sharedUserLabel="@string/phoneAppLabel">
<uses-sdk
android:minSdkVersion="23"
android:targetSdkVersion="26" />
<original-package android:name="com.android.phone" />
<uses-permission android:name="android.permission.WRITE_APN_SETTINGS" />
</manifest>
不知为何???
把service强制分到phone进程后,可以得到变量mUiccController和r,但是无写apn权限,即无法查询数据库。
若service运行在默认进程settings进程中,此时查询数据库正常,但无法得到变量mUiccController和r,就无法精确筛选虚拟运营商。
故在该service处再开启一个服务,专门运行在phone进程中,用于获取变量mUiccController和r,同时提供了iccRecordsNotNull()方法,用于判断获取的r是否为空,isMvnoMatches()方法把匹配结果返回。
<service android:name="com.mediatek.settings.service.PhoneService" android:process="com.android.phone">
<intent-filter>
<action android:name="com.mediatek.settings.PhoneService.action"/>
</intent-filter>
</service>
public class PhoneService extends Service{
private static final String TAG = "PhoneService";
private static final boolean DEBUG = true;
private UiccController mUiccController;
private IccRecords r;
//private MyBinder myBinder = new MyBinder();
@Override
public void onCreate() {
debug("onCreate() : process = " + Utils.getAppName(PhoneService.this));
mUiccController = UiccController.getInstance();
debug("mUiccController = " + mUiccController);
}
@Override
public IBinder onBind(Intent intent) {
debug( "onBind()");
return myBinder;
}
@Override
public void onDestroy() {
debug( "onDestroy()");
super.onDestroy();
}
private IBinder myBinder = new IPhoneService.Stub(){
//class MyBinder extends Binder{ //client and service are in different process,
public boolean iccRecordsNotNull(int phoneId){
debug("enter iccRecordsIsNull() phoneId = " + phoneId);
if (null == mUiccController) {
debug("mUiccController = null, will return false!");
return false;
}
r = mUiccController.getIccRecords(phoneId, UiccController.APP_FAM_3GPP);
debug("r = " + r);
if(null == r){
debug("r = null, will return false!");
return false;
}
return true;
}
public boolean isMvnoMatches(String mvnoType, String mvnoMatchData){
debug("enter isMvnoMatches() mvnoType = " + mvnoType + ", mvnoMatchData = " + mvnoMatchData);
if(null == r){
debug("r = null, will return false!");
return false;
}
boolean isMatch = ApnSetting.mvnoMatches(r, mvnoType, mvnoMatchData);
debug("isMatch = " + isMatch);
return isMatch;
}
};
private void debug(String str) {
if (DEBUG) {
Log.d(TAG, str);
}
}
}
此时获取变量的地方为服务端,运行在phone进程,调用返回结果的地方为服务端,运行在settings进程。
对应客户端、服务端不在同一进程的情形,要通过aidl实现进程间通信。否则则会报错:“android.os.BinderProxy cannot be cast to xxx”。
至此,纠结了N久的问题,曲线解决了......