APN---Telephony data Part I

关于Telephony data 准备写三篇,本文是第一部分APN,根据Android O源码,简单总结了Android上APN相关的知识点。分以下三部分:
1. 预置APN数据加载
2. APN字段
3. APN的显示和编辑


APN(access point name)决定了我们手机接入网络的方式,一般我们手机都做了默认配置,也可以根据需要进行手动配置。APN存储在手机的“/data/user_de/0/com.android.providers.telephony/databases/”路径下的telephony.db中(多用户的情况下,只有owner才有这份数据),对应表是carriers。

1. 预置APN数据加载
关于db和表的创建,Subscription文章 TelephonyProvider部分已经写了,这里就不再赘述了。下面说下预置APN数据的加载过程。
我们预置的APN数据也是在db创建的过程中从配置文件读取然后加载到db的。
在db创建的过程中,DatabaseHelper.OnCreate方法会调用DatabaseHelper.initDatabase方法,该方法可以从配置文件中加载我们要预置的apn数据。

        /**
         *  This function adds APNs from xml file(s) to db. The db may or may not be empty to begin
         *  with.
         */
        private void initDatabase(SQLiteDatabase db) {
            if (VDBG) log("dbh.initDatabase:+ db=" + db);
            // Read internal APNS data
            Resources r = mContext.getResources();
            XmlResourceParser parser = r.getXml(com.android.internal.R.xml.apns);//加载frameworks/base/core/res/res/xml/apns.xml 中的配置
            int publicversion = -1;
            try {
                XmlUtils.beginDocument(parser, "apns");
                publicversion = Integer.parseInt(parser.getAttributeValue(null, "version"));
                loadApns(db, parser);//调用loadApns方法完成加载任务。
            } catch (Exception e) {
                loge("Got exception while loading APN database." + e);
            } finally {
                parser.close();
            }

            // Read external APNS data (partner-provided)
            XmlPullParser confparser = null;

            //下面的部分获取指定路径下的APN配置文件。
            //getApnConfFile方法用于获取配置文件,常用路径有三个:
            //1. etc/apns-conf.xml
            //2. telephony/apns-conf.xml
            //3. misc/apns-conf.xml
            //但是这三个文件并不会全部加载, 只加载最新修改的那个。
            File confFile = getApnConfFile();

            FileReader confreader = null;
            if (DBG) log("confFile = " + confFile);
            try {
                confreader = new FileReader(confFile);
                confparser = Xml.newPullParser();
                confparser.setInput(confreader);
                XmlUtils.beginDocument(confparser, "apns");

                // Sanity check. Force internal version and confidential versions to agree
                int confversion = Integer.parseInt(confparser.getAttributeValue(null, "version"));
                if (publicversion != confversion) {
                    log("initDatabase: throwing exception due to version mismatch");
                    throw new IllegalStateException("Internal APNS file version doesn't match "
                            + confFile.getAbsolutePath());
                }

                loadApns(db, confparser);//调用loadApns方法完成加载任务。
            } catch (FileNotFoundException e) {
                // It's ok if the file isn't found. It means there isn't a confidential file
                // Log.e(TAG, "File not found: '" + confFile.getAbsolutePath() + "'");
            } catch (Exception e) {
                loge("initDatabase: Exception while parsing '" + confFile.getAbsolutePath() + "'" +
                        e);
            } finally {
                // Get rid of user/carrier deleted entries that are not present in apn xml file.
                // Those entries have edited value USER_DELETED/CARRIER_DELETED.
                if (VDBG) {
                    log("initDatabase: deleting USER_DELETED and replacing "
                            + "DELETED_BUT_PRESENT_IN_XML with DELETED");
                }

                //到这里, 需要预置的APN已经全部加载到db中了, 但是db中可能存入了不必要的数据,
                //下面的code是清除,更新db里的数据。
                // Delete USER_DELETED
                db.delete(CARRIERS_TABLE, IS_USER_DELETED + " or " + IS_CARRIER_DELETED, null);

                //下面的code用于清除为了解决插入数据时的冲突而加了特殊标记的记录。
                // Change USER_DELETED_BUT_PRESENT_IN_XML to USER_DELETED
                ContentValues cv = new ContentValues();
                cv.put(EDITED, USER_DELETED);
                db.update(CARRIERS_TABLE, cv, IS_USER_DELETED_BUT_PRESENT_IN_XML, null);

                // Change CARRIER_DELETED_BUT_PRESENT_IN_XML to CARRIER_DELETED
                cv = new ContentValues();
                cv.put(EDITED, CARRIER_DELETED);
                db.update(CARRIERS_TABLE, cv, IS_CARRIER_DELETED_BUT_PRESENT_IN_XML, null);

                if (confreader != null) {
                    try {
                        confreader.close();
                    } catch (IOException e) {
                        // do nothing
                    }
                }

                // Update the stored checksum
                setApnConfChecksum(getChecksum(confFile));
            }
            if (VDBG) log("dbh.initDatabase:- db=" + db);

        }

getApnConfFile方法用于获取配置文件,常用路径有三个,但是这三个文件并不会全部加载, 只加载最新修改的那个。
1. etc/apns-conf.xml
2. telephony/apns-conf.xml
3. misc/apns-conf.xml

        private File getApnConfFile() {
            // Environment.getRootDirectory() is a fancy way of saying ANDROID_ROOT or "/system".
            File confFile = new File(Environment.getRootDirectory(), PARTNER_APNS_PATH);//etc/apns-conf.xml
            File oemConfFile =  new File(Environment.getOemDirectory(), OEM_APNS_PATH);//telephony/apns-conf.xml
            File updatedConfFile = new File(Environment.getDataDirectory(), OTA_UPDATED_APNS_PATH);//misc/apns-conf.xml
            confFile = getNewerFile(confFile, oemConfFile);
            confFile = getNewerFile(confFile, updatedConfFile);
            return confFile;
        }

2. APN字段

APN字段的定义在Telephony.java中的静态内部类Carriers中,总结如下:

字段名称描述
NAMEAPN记录的名字; 我们手机里内置的运营APN会使用运营商的名字,例如”中国移动(China Mobile)Net”
APNAPN的名字
PROXY代理地址
PORT代理端口
MMSPROXYMMS代理地址
MMSPORTMMS代理端口
SERVER服务器地址
USERAPN用户名
PASSWORDAPN用户名对应的密码
MMSC多媒体消息业务中心, 即彩信业务中心
MCC移动国家码
MNC移动网络码
NUMERIC运营商的数字代码,一般是MCC+MNC的形式,例如46001
AUTH_TYPE鉴权类型,一般使用的鉴权是PAP或CHAP
TYPEAPN的类型,如mms,dun,ims等; 一个APN配置可以支持多种APN type, 不同类型用”,”分割
PROTOCOL连接APN所使用的协议,例如IP,IPV6,IPV4V6,PPP等
ROAMING_PROTOCOL漫游时所使用的协议
CURRENT添加新APN时,如果新APN和当前SIM的mcc,mnc相同,这个字段会被设置为1。 没有看到其他地方使用这个字段, 所以不太清楚这个字段的作用
CARRIER_ENABLED用于标识这个APN是否可用
BEARERRadio Access Technology 信息, 当前可用的有LTE(14)和eHRPD(13)
BEARER_BITMASKRadio Access Technology bitmask, 用于标明当前APN可以包含的RAT; 0表示所有的RAT都可以,否则bitmask和RAT的关系是(1 << (RAT - 1))
MVNO_TYPE移动虚拟网络运营商(Mobile virtual network operator)的类型; 可用的数据有spn, IMSI和GID(Group Identifier Level 1)
MVNO_MATCH_DATAMVNO_TYPE数据, 这个值是和MVNO_TYPE对应的。 例如SPN: A MOBILE, BEN NL, …; IMSI: 302720x94, 2060188, …; GID: 4E, 33, …
SUBSCRIPTION_ID用于表明这个APN属于哪个subscription, 这个值是从siminfo表中获取的
PROFILE_IDprofile是modem侧存储信息的方式, 这个值将APN和modem侧的profile联系起来
MODEM_COGNITIVE用于标明这个APN是否会在modem侧设置(没用过这个字段)
MAX_CONNS该APN支持的最大连接数量
WAIT_TIME使用该APN进行数据连接时, 如果失败, retry要等待的时间
MAX_CONNS_TIME
MTU使用该APN建立的连接,可以传输的最大单元
EDITED该APN是否可以编辑
USER_VISIBLE是否可见,如果不可见, 那么我们在APN菜单里是看不到的

3. APN的显示和编辑

一般在手机的”settings->More->Mobile networks->Access point name”菜单中可以看到当前手机可用的Apn信息,也可以编辑添加Apn。进入菜单后的菜单如下图:
这里写图片描述
图例中显示了两个apn,点击任意一个可以进入编辑界面; 点击右上角的“+”号,可以添加新apn; 右上角的选项菜单用于将apn恢复成默认配置。
图片所示界面对应的代码是ApnSettings.java类,这是一个PreferenceFragment子类,中间有多层继承关系。ApnSettings作为一个fragment子类有自己的生命周期; 在启动时,OnResume()方法会调用fillList()放法,该方法完成了从db中查询Apn数据的任务,所以如果要完成一些APN的显示定制需求,可以在这个函数中修改sql语句。

    private void fillList() {
        final TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
        //mSubscriptionInfo是在onCreate()方法中根据intent中的参数从SubscriptionManager中获取的。
        //即指定SIM卡对应的Subscription信息。
        final int subId = mSubscriptionInfo != null ? mSubscriptionInfo.getSubscriptionId()
                : SubscriptionManager.INVALID_SUBSCRIPTION_ID;
        //获取mccmnc信息,这是查询APN信息的重要依据。
        final String mccmnc = mSubscriptionInfo == null ? "" : tm.getSimOperator(subId);
        Log.d(TAG, "mccmnc = " + mccmnc);
        StringBuilder where = new StringBuilder("numeric=\"" + mccmnc +
                "\" AND NOT (type='ia' AND (apn=\"\" OR apn IS NULL)) AND user_visible!=0");
        //IMS APN是否需要隐藏可以在CarrierConfig中配置,
        //mHideImsApn是在OnCreate方法中从CarrierConfigManager(CarrierConfigManager.KEY_HIDE_IMS_APN_BOOL)中获取的值
        if (mHideImsApn) {
            where.append(" AND NOT (type='ims')");
        }

        Cursor cursor = getContentResolver().query(Telephony.Carriers.CONTENT_URI, new String[] {
                "_id", "name", "apn", "type", "mvno_type", "mvno_match_data"}, where.toString(),
                null, Telephony.Carriers.DEFAULT_SORT_ORDER);

        if (cursor != null) {
            IccRecords r = null;
            if (mUiccController != null && mSubscriptionInfo != null) {
                //获取SIM卡对应的IcccRecords记录, 后面会用到。
                r = mUiccController.getIccRecords(
                        SubscriptionManager.getPhoneId(subId), UiccController.APP_FAM_3GPP);
            }
            PreferenceGroup apnList = (PreferenceGroup) findPreference("apn_list");
            apnList.removeAll();

            //下面创建了四个ArrayList<ApnPreference>对象, 用于存储不同类型的APN数据。
            ArrayList<ApnPreference> mnoApnList = new ArrayList<ApnPreference>();
            ArrayList<ApnPreference> mvnoApnList = new ArrayList<ApnPreference>();
            ArrayList<ApnPreference> mnoMmsApnList = new ArrayList<ApnPreference>();
            ArrayList<ApnPreference> mvnoMmsApnList = new ArrayList<ApnPreference>();

            mSelectedKey = getSelectedApnKey();
            cursor.moveToFirst();
            while (!cursor.isAfterLast()) {
                String name = cursor.getString(NAME_INDEX);//APN记录的名字
                String apn = cursor.getString(APN_INDEX);//APN名字
                String key = cursor.getString(ID_INDEX);//这个是数据库里每项记录的主键ID
                String type = cursor.getString(TYPES_INDEX);//类型
                String mvnoType = cursor.getString(MVNO_TYPE_INDEX);//MVNO 类型
                String mvnoMatchData = cursor.getString(MVNO_MATCH_DATA_INDEX);//MVNO 数据

                ApnPreference pref = new ApnPreference(getPrefContext());//创建ApnPreference对象保存APN数据

                pref.setKey(key);
                pref.setTitle(name);
                pref.setSummary(apn);
                pref.setPersistent(false);
                pref.setOnPreferenceChangeListener(this);
                pref.setSubId(subId);

                boolean selectable = ((type == null) || !type.equals("mms"));//将mms类型的APN和非mms类型的APN分开处理
                pref.setSelectable(selectable);
                if (selectable) {
                    if ((mSelectedKey != null) && mSelectedKey.equals(key)) {
                        pref.setChecked();
                    }
                    //addApnToList函数负责将不同类型的APN放进前面创建的四个不同ArrayList<ApnPreference>对象中。
                    addApnToList(pref, mnoApnList, mvnoApnList, r, mvnoType, mvnoMatchData);
                } else {
                    addApnToList(pref, mnoMmsApnList, mvnoMmsApnList, r, mvnoType, mvnoMatchData);
                }
                cursor.moveToNext();
            }
            cursor.close();

            //如果有MVNO类型的APN, mvnoApnList和mnoMmsApnList就分别重新指向保存了MVNO类型APN的mvnoApnList
            //和mvnoMmsApnList, 结果是mvnoApnList和mnoMmsApnList之前存储的APN记录不会被显示出来。
            if (!mvnoApnList.isEmpty()) {
                mnoApnList = mvnoApnList;
                mnoMmsApnList = mvnoMmsApnList;

                // Also save the mvno info
            }

            for (Preference preference : mnoApnList) {//添加显示mnoApnList中的APN记录
                apnList.addPreference(preference);
            }
            for (Preference preference : mnoMmsApnList) {//添加显示mnoMmsApnList中的APN记录
                apnList.addPreference(preference);
            }
        }
    }

addApnToList函数负责将不同类型的APN放进前面创建的四个不同ArrayList<ApnPreference>对象中; 下面是具体代码,相关逻辑比较简单, 在IccRecords,MVNO_type和MVNO_data数据都非空的时候调用ApnSetting.mvnoMatches去比较IccRecords和MVNO_data中的数据,如果匹配,那么这个APN是有效的,会被放进List中。

    private void addApnToList(ApnPreference pref, ArrayList<ApnPreference> mnoList,
                              ArrayList<ApnPreference> mvnoList, IccRecords r, String mvnoType,
                              String mvnoMatchData) {
        if (r != null && !TextUtils.isEmpty(mvnoType) && !TextUtils.isEmpty(mvnoMatchData)) {
            if (ApnSetting.mvnoMatches(r, mvnoType, mvnoMatchData)) {
                mvnoList.add(pref);
                // Since adding to mvno list, save mvno info
                mMvnoType = mvnoType;
                mMvnoMatchData = mvnoMatchData;
            }
        } else {
            mnoList.add(pref);
        }
    }

添加,编辑APN都是从ApnSettings跳转到ApnEditor,并由ApnEditor.validateAndSave方法将数据写入数据库。
当添加新APN进入ApnEditor时,ApnEditor.onCreate方法根据intent的action(ACTION_INSERT),先在数据库中插入了一条空数据,并将包含数据记录ID的uri保存在mUri中,以便在ApnEditor.validateAndSave中使用。所以ApnEditor.validateAndSave将数据写入数据库时可以使用update方法,这样也同时满足了编辑操作的需求。

“Restore to default”是什么实现的?
当点击”Restore to defualt”菜单时,ApnSettings.onOptionsItemSelected 方法会调用
ApnSettings.restoreDefaultApn方法。

    private boolean restoreDefaultApn() {
        showDialog(DIALOG_RESTORE_DEFAULTAPN);//显示一个友好的提示框
        mRestoreDefaultApnMode = true;//标识变量

        if (mRestoreApnUiHandler == null) {
            mRestoreApnUiHandler = new RestoreApnUiHandler();//这个是主线程中的handler
        }

        if (mRestoreApnProcessHandler == null ||
            mRestoreDefaultApnThread == null) {
            mRestoreDefaultApnThread = new HandlerThread(//创建了一个HandlerThread
                    "Restore default APN Handler: Process Thread");
            mRestoreDefaultApnThread.start();//启动新创建的HandlerThread
            mRestoreApnProcessHandler = new RestoreApnProcessHandler(
                    mRestoreDefaultApnThread.getLooper(), mRestoreApnUiHandler);//使用HandlerThread的looper创建了一个handler, mRestoreApnUiHandler用于通知UI。
        }

        mRestoreApnProcessHandler
                .sendEmptyMessage(EVENT_RESTORE_DEFAULTAPN_START);// 发送EVENT_RESTORE_DEFAULTAPN_START,开始恢复数据。
        return true;
    }

RestoreApnProcessHandler会处理EVENT_RESTORE_DEFAULTAPN_START,操作比较简单,只是通过ContentResolver调用delete去删除数据。下面我们看看Uri的内容,已经TelephonyProvider如何做的处理。
Uri处理是通过getUriForCurrSubId(Uri uri)方法,该方法是将SubId和传入的Uri拼接到一块; 而传入的参数是ApnSettings定义好的常量,如下:

    public static final String RESTORE_CARRIERS_URI =
        "content://telephony/carriers/restore";
    ...
    private static final Uri DEFAULTAPN_URI = Uri.parse(RESTORE_CARRIERS_URI);

所以完整的Uri是”content://telephony/carriers/restore/subId/*“。
下面的TelephonyProvider中Macher的定义:

    static {
        s_urlMatcher.addURI("telephony", "carriers", URL_TELEPHONY);
        s_urlMatcher.addURI("telephony", "carriers/current", URL_CURRENT);

         ----------省略------------

        /*restore 对应的Uri*/
        s_urlMatcher.addURI("telephony", "carriers/restore/subId/*", URL_RESTOREAPN_USING_SUBID);
        ...
        s_currentSetMap = new ContentValues(1);
        s_currentSetMap.put(CURRENT, "1");
    }

从上面代码中可以看出,ApnSettings中的Uri对应的是URL_RESTOREAPN_USING_SUBID,下面截取了delete方法中的相关code:

    @Override
    public synchronized int delete(Uri url, String where, String[] whereArgs)
    {
        int count = 0;
        int subId = SubscriptionManager.getDefaultSubscriptionId();
        String userOrCarrierEdited = ") and (" +
                EDITED + "=" + USER_EDITED +  " or " +
                EDITED + "=" + CARRIER_EDITED + ")";
        String notUserOrCarrierEdited = ") and (" +
                EDITED + "!=" + USER_EDITED +  " and " +
                EDITED + "!=" + CARRIER_EDITED + ")";
        ContentValues cv = new ContentValues();
        cv.put(EDITED, USER_DELETED);

        checkPermission();

        SQLiteDatabase db = getWritableDatabase();
        int match = s_urlMatcher.match(url);
        switch (match)
        {
            ----------省略------------
            case URL_RESTOREAPN_USING_SUBID: {
                String subIdString = url.getLastPathSegment();//解析subId数据
                try {
                    subId = Integer.parseInt(subIdString);//转换成int类型
                } catch (NumberFormatException e) {
                    loge("NumberFormatException" + e);
                    throw new IllegalArgumentException("Invalid subId " + url);
                }
                if (DBG) log("subIdString = " + subIdString + " subId = " + subId);
                // FIXME use subId in query
            }
            case URL_RESTOREAPN: {
                count = 1;
                restoreDefaultAPN(subId);//调用restoreDefaultAPN(int subId)方法完成restore操作
                break;
            }
            ----------省略------------
        }
        ----------省略------------
    }

restoreDefaultAPN方法会直接删除carrier表和 shared preferences 中的数据,然后调用DatabaseHelper.initDatabase方法重新初始数据。也就是说APN相关数据全部删除,重新从配置文件中初始化。

    private void restoreDefaultAPN(int subId) {
        SQLiteDatabase db = getWritableDatabase();

        try {
            db.delete(CARRIERS_TABLE, null, null);//直接删除carrier表中的全部数据
        } catch (SQLException e) {
            loge("got exception when deleting to restore: " + e);
        }

        //下面的code删除了shared preferences文件
        // delete preferred apn ids and preferred apns (both stored in diff SharedPref) for all
        // subIds
        SharedPreferences spApnId = getContext().getSharedPreferences(PREF_FILE_APN,
                Context.MODE_PRIVATE);
        SharedPreferences.Editor editorApnId = spApnId.edit();
        editorApnId.clear();
        editorApnId.apply();

        SharedPreferences spApn = getContext().getSharedPreferences(PREF_FILE_FULL_APN,
                Context.MODE_PRIVATE);
        SharedPreferences.Editor editorApn = spApn.edit();
        editorApn.clear();
        editorApn.apply();

        initDatabaseWithDatabaseHelper(db);//该方法会调用DatabaseHelper.initDatabase方法。
    }

结束!

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值