获取apn列表时遇到的一个多进程通信问题

手机的设置中可以获取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久的问题,曲线解决了......

猜你喜欢

转载自blog.csdn.net/lyl0530/article/details/81385749