Subscription--Android O

Subscription

关于subscription,主要从下面两个方面学习:
1.Subscription的获取和更新。
2.Subscription的存储。

1.Subscription的获取和更新

Subscription的获取和更新主要依靠SubscriptionInfoUpdater类。在PhoneApp启动的过程中会创建SubscriptionInfoUpdater类的对象,从下面的构造函数里可以看出该类接收了ACTION_SIM_STATE_CHANGED和ACTION_INTERNAL_SIM_STATE_CHANGED两个广播,通过这两个广播中的SIM状态信息,来获取或更新subscription信息。

 public SubscriptionInfoUpdater(Context context, Phone[] phone, CommandsInterface[] ci) {
        logd("Constructor invoked");

        mContext = context;
        mPhone = phone;
        mSubscriptionManager = SubscriptionManager.from(mContext);
        mPackageManager = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
        mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);

        /*创建接收TelephonyIntents.ACTION_SIM_STATE_CHANGED广播的intent filter*/
        IntentFilter intentFilter = new IntentFilter(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
        /*添加接收TelephonyIntents.ACTION_INTERNAL_SIM_STATE_CHANGED广播的intent filter。*/
        intentFilter.addAction(IccCardProxy.ACTION_INTERNAL_SIM_STATE_CHANGED);
        intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
        mContext.registerReceiver(sReceiver, intentFilter);

        mCarrierServiceBindHelper = new CarrierServiceBindHelper(mContext);
        initializeCarrierApps();
    }

下面是sReceiver的实现code:

private final BroadcastReceiver sReceiver = new  BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            logd("[Receiver]+");
            String action = intent.getAction();
            logd("Action: " + action);

            ...

            String simStatus = intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE);
            logd("simStatus: " + simStatus);
            if (action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) {//处理ACTION_SIM_STATE_CHANGED广播
                rebroadcastIntentsOnUnlock.put(slotIndex, intent);
                "/**处理ABSENT状态*/"
                if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(simStatus)) {
                    sendMessage(obtainMessage(EVENT_SIM_ABSENT, slotIndex, -1));
                } else if (IccCardConstants.INTENT_VALUE_ICC_UNKNOWN.equals(simStatus)) {//处理UNKNOWN状态
                    sendMessage(obtainMessage(EVENT_SIM_UNKNOWN, slotIndex, -1));
                } else if (IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR.equals(simStatus)) {//处理IO_ERROR状态
                    sendMessage(obtainMessage(EVENT_SIM_IO_ERROR, slotIndex, -1));
                } else if (IccCardConstants.INTENT_VALUE_ICC_CARD_RESTRICTED.equals(simStatus)) {//处理RESTRICTED状态
                    sendMessage(obtainMessage(EVENT_SIM_RESTRICTED, slotIndex, -1));
                } else {
                    logd("Ignoring simStatus: " + simStatus);
                }
            } else if (action.equals(IccCardProxy.ACTION_INTERNAL_SIM_STATE_CHANGED)) {//处理ACTION_INTERNAL_SIM_STATE_CHANGED广播
                if (IccCardConstants.INTENT_VALUE_ICC_LOCKED.equals(simStatus)) {//处理ICC_LOCKED状态
                    String reason = intent.getStringExtra(
                        IccCardConstants.INTENT_KEY_LOCKED_REASON);
                    sendMessage(obtainMessage(EVENT_SIM_LOCKED, slotIndex, -1, reason));
                } else if (IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(simStatus)) {//ICC_LOADED状态
                    sendMessage(obtainMessage(EVENT_SIM_LOADED, slotIndex, -1));
                } else {
                    logd("Ignoring simStatus: " + simStatus);
                }
            }
            logd("[Receiver]-");
        }
    };

重点分析下面的handleSimLoaded方法:

private void handleSimLoaded(int slotId) {
        logd("handleSimStateLoadedInternal: slotId: " + slotId);

        // The SIM should be loaded at this state, but it is possible in cases such as SIM being
        // removed or a refresh RESET that the IccRecords could be null. The right behavior is to
        // not broadcast the SIM loaded.
        IccRecords records = mPhone[slotId].getIccCard().getIccRecords();
        if (records == null) {  // Possibly a race condition.
            logd("onRecieve: IccRecords null");
            return;
        }
        if (records.getIccId() == null) {
            logd("onRecieve: IccID null");
            return;
        }
        mIccId[slotId] = records.getIccId();

        if (isAllIccIdQueryDone()) {/*在调用handleSimLoaded之前已经调用了handleSimAbsent, 会对mIccid赋值,所以这个判断是可以通过的。*/
            updateSubscriptionInfoByIccId();
            int[] subIds = mSubscriptionManager.getActiveSubscriptionIdList();
            for (int subId : subIds) {
                TelephonyManager tm = TelephonyManager.getDefault();

                String operator = tm.getSimOperatorNumeric(subId);
                slotId = SubscriptionController.getInstance().getPhoneId(subId);

                if (!TextUtils.isEmpty(operator)) {
                    if (subId == SubscriptionController.getInstance().getDefaultSubId()) {
                        MccTable.updateMccMncConfiguration(mContext, operator, false);
                    }
                    SubscriptionController.getInstance().setMccMnc(operator, subId);
                } else {
                    logd("EVENT_RECORDS_LOADED Operator name is null");
                }

                String msisdn = tm.getLine1Number(subId);
                ContentResolver contentResolver = mContext.getContentResolver();

                if (msisdn != null) {/*更新每张卡在DB中的的number字段。*/
                    ContentValues number = new ContentValues(1);
                    number.put(SubscriptionManager.NUMBER, msisdn);
                    contentResolver.update(SubscriptionManager.CONTENT_URI, number,
                            SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "="
                                    + Long.toString(subId), null);
                }

                SubscriptionInfo subInfo = mSubscriptionManager.getActiveSubscriptionInfo(subId);
                String nameToSet;
                String simCarrierName = tm.getSimOperatorName(subId);
                ContentValues name = new ContentValues(1);

                if (subInfo != null && subInfo.getNameSource() !=
                        SubscriptionManager.NAME_SOURCE_USER_INPUT) {/*更新每张卡在DB中的的display_name字段。*/
                    if (!TextUtils.isEmpty(simCarrierName)) {
                        nameToSet = simCarrierName;
                    } else {
                        nameToSet = "CARD " + Integer.toString(slotId + 1);
                    }
                    name.put(SubscriptionManager.DISPLAY_NAME, nameToSet);
                    logd("sim name = " + nameToSet);
                    contentResolver.update(SubscriptionManager.CONTENT_URI, name,
                            SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID
                                    + "=" + Long.toString(subId), null);
                }

                /* Update preferred network type and network selection mode on SIM change.
                 * Storing last subId in SharedPreference for now to detect SIM change. */
                SharedPreferences sp =
                        PreferenceManager.getDefaultSharedPreferences(mContext);
                int storedSubId = sp.getInt(CURR_SUBID + slotId, -1);

                if (storedSubId != subId) {
                    int networkType = RILConstants.PREFERRED_NETWORK_MODE;

                    // Set the modem network mode
                    mPhone[slotId].setPreferredNetworkType(networkType, null);
                    Settings.Global.putInt(mPhone[slotId].getContext().getContentResolver(),
                            Settings.Global.PREFERRED_NETWORK_MODE + subId,
                            networkType);

                    // Only support automatic selection mode on SIM change.
                    mPhone[slotId].getNetworkSelectionMode(
                            obtainMessage(EVENT_GET_NETWORK_SELECTION_MODE_DONE,
                                    new Integer(slotId)));

                    // Update stored subId
                    SharedPreferences.Editor editor = sp.edit();
                    editor.putInt(CURR_SUBID + slotId, subId);
                    editor.apply();
                }

                // Update set of enabled carrier apps now that the privilege rules may have changed.
                CarrierAppUtils.disableCarrierAppsUntilPrivileged(mContext.getOpPackageName(),
                        mPackageManager, TelephonyManager.getDefault(),
                        mContext.getContentResolver(), mCurrentlyActiveUserId);

                broadcastSimStateChanged(slotId, IccCardConstants.INTENT_VALUE_ICC_LOADED, null);/*广播ACTION_SIM_STATE_CHANGED*/
                updateCarrierServices(slotId, IccCardConstants.INTENT_VALUE_ICC_LOADED);//通知CarrierConfig加载相应资源。
            }
        }
    }

updateSubscriptionInfoByIccId()方法:

    synchronized private void updateSubscriptionInfoByIccId() {
        logd("updateSubscriptionInfoByIccId:+ Start");

        mSubscriptionManager.clearSubscriptionInfo();

        for (int i = 0; i < PROJECT_SIM_NUM; i++) {
            mInsertSimState[i] = SIM_NOT_CHANGE;//初始值
        }

        int insertedSimCount = PROJECT_SIM_NUM;
        for (int i = 0; i < PROJECT_SIM_NUM; i++) {
            if (ICCID_STRING_FOR_NO_SIM.equals(mIccId[i])) {
                insertedSimCount--;
                mInsertSimState[i] = SIM_NOT_INSERT;//没有插卡的情况
            }
        }
        logd("insertedSimCount = " + insertedSimCount);

        int index = 0;
        for (int i = 0; i < PROJECT_SIM_NUM; i++) {//这段for循环用来做什么,处理卡ICCID相同的情况。
            if (mInsertSimState[i] == SIM_NOT_INSERT) {
                continue;//过滤掉无卡部分
            }
            index = 2;
            for (int j = i + 1; j < PROJECT_SIM_NUM; j++) {
                if (mInsertSimState[j] == SIM_NOT_CHANGE && mIccId[i].equals(mIccId[j])) {
                    mInsertSimState[i] = 1;
                    mInsertSimState[j] = index;
                    index++;
                }
            }
        }

        ContentResolver contentResolver = mContext.getContentResolver();
        String[] oldIccId = new String[PROJECT_SIM_NUM];
        for (int i = 0; i < PROJECT_SIM_NUM; i++) {
            oldIccId[i] = null;
            List<SubscriptionInfo> oldSubInfo =
                    SubscriptionController.getInstance().getSubInfoUsingSlotIndexWithCheck(i, false,
                    mContext.getOpPackageName());//从DB中查询卡槽i上所保存过的信息。
            if (oldSubInfo != null && oldSubInfo.size() > 0) {
                oldIccId[i] = oldSubInfo.get(0).getIccId();//取第一条
                logd("updateSubscriptionInfoByIccId: oldSubId = "
                        + oldSubInfo.get(0).getSubscriptionId());
                if (mInsertSimState[i] == SIM_NOT_CHANGE && !mIccId[i].equals(oldIccId[i])) {//卡槽i中当前卡的iccid和之前保存的iccid不同,所以卡槽i中的SIM卡发生了改变。
                    mInsertSimState[i] = SIM_CHANGED;
                }
                if (mInsertSimState[i] != SIM_NOT_CHANGE) {//根据oldSubinfo.get(0)的subID,更新DB中的对应信息,将对应的SIM_SLOT_INDEX设置为Invalid。
                    ContentValues value = new ContentValues(1);
                    value.put(SubscriptionManager.SIM_SLOT_INDEX,
                            SubscriptionManager.INVALID_SIM_SLOT_INDEX);
                    contentResolver.update(SubscriptionManager.CONTENT_URI, value,
                            SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "="
                            + Integer.toString(oldSubInfo.get(0).getSubscriptionId()), null);
                }
            } else {
                if (mInsertSimState[i] == SIM_NOT_CHANGE) {
                    // no SIM inserted last time, but there is one SIM inserted now
                    mInsertSimState[i] = SIM_CHANGED;
                }
                oldIccId[i] = ICCID_STRING_FOR_NO_SIM;
                logd("updateSubscriptionInfoByIccId: No SIM in slot " + i + " last time");
            }
        }

        for (int i = 0; i < PROJECT_SIM_NUM; i++) {
            logd("updateSubscriptionInfoByIccId: oldIccId[" + i + "] = " + oldIccId[i] +
                    ", sIccId[" + i + "] = " + mIccId[i]);
        }

        //check if the inserted SIM is new SIM
        int nNewCardCount = 0;
        int nNewSimStatus = 0;
        for (int i = 0; i < PROJECT_SIM_NUM; i++) {
            if (mInsertSimState[i] == SIM_NOT_INSERT) {
                logd("updateSubscriptionInfoByIccId: No SIM inserted in slot " + i + " this time");
            } else {
                if (mInsertSimState[i] > 0) {
                    //some special SIMs may have the same IccIds, add suffix to distinguish them
                    //FIXME: addSubInfoRecord can return an error.
                    mSubscriptionManager.addSubscriptionInfoRecord(mIccId[i]
                            + Integer.toString(mInsertSimState[i]), i);//将subscription 信息放进DB。
                    logd("SUB" + (i + 1) + " has invalid IccId");
                } else /*if (sInsertSimState[i] != SIM_NOT_INSERT)*/ {
                    mSubscriptionManager.addSubscriptionInfoRecord(mIccId[i], i);//将subscription 信息放进DB。
                }
                if (isNewSim(mIccId[i], oldIccId)) {
                    nNewCardCount++;
                    switch (i) {
                        case PhoneConstants.SUB1:
                            nNewSimStatus |= STATUS_SIM1_INSERTED;
                            break;
                        case PhoneConstants.SUB2:
                            nNewSimStatus |= STATUS_SIM2_INSERTED;
                            break;
                        case PhoneConstants.SUB3:
                            nNewSimStatus |= STATUS_SIM3_INSERTED;
                            break;
                        //case PhoneConstants.SUB3:
                        //    nNewSimStatus |= STATUS_SIM4_INSERTED;
                        //    break;
                    }

                    mInsertSimState[i] = SIM_NEW;
                }
            }
        }

        for (int i = 0; i < PROJECT_SIM_NUM; i++) {/*处理到这里,所有SIM_CHANGED状态的SIM卡都是SIM_REPOSITION。*/
            if (mInsertSimState[i] == SIM_CHANGED) {
                mInsertSimState[i] = SIM_REPOSITION;
            }
            logd("updateSubscriptionInfoByIccId: sInsertSimState[" + i + "] = "
                    + mInsertSimState[i]);
        }

        List<SubscriptionInfo> subInfos = mSubscriptionManager.getActiveSubscriptionInfoList();
        int nSubCount = (subInfos == null) ? 0 : subInfos.size();
        logd("updateSubscriptionInfoByIccId: nSubCount = " + nSubCount);
        for (int i=0; i < nSubCount; i++) {/*更新每张卡在DB中的的number字段。*/
            SubscriptionInfo temp = subInfos.get(i);

            String msisdn = TelephonyManager.getDefault().getLine1Number(
                    temp.getSubscriptionId());

            if (msisdn != null) {
                ContentValues value = new ContentValues(1);
                value.put(SubscriptionManager.NUMBER, msisdn);
                contentResolver.update(SubscriptionManager.CONTENT_URI, value,
                        SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "="
                        + Integer.toString(temp.getSubscriptionId()), null);
            }
        }

        // Ensure the modems are mapped correctly
        mSubscriptionManager.setDefaultDataSubId(
                mSubscriptionManager.getDefaultDataSubscriptionId());

        SubscriptionController.getInstance().notifySubscriptionInfoChanged();
        logd("updateSubscriptionInfoByIccId:- SsubscriptionInfo update complete");
    }

SubscriptionMonitor.java
这个类的功能比较简单,主要是下面两点:
1.在SubscriptionController添加了Listener:mSubscriptionsChangedListener,监听subscription 的变动,并且
对外提供了registerForSubscriptionChanged(…)和unregisterForSubscriptionChanged(…)两个相关API。
2.监听了广播TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED,对应的reveiver为mDefaultDataSubscriptionChangedReceiver,并且对外提供了registerForDefaultDataSubscriptionChanged(…)和unregisterForDefaultDataSubscriptionChanged(…)两个相关API。


2.Subscription的存储
TelephonyProvider

TelephonyProvider是在PhoneApp启动的过程中创建的, 因为TelephonyProvider将AndroidManifest.xml文件中将process配置成了"com.android.phone".
具体启动过程需要看ActivityManagerService,Activit的yThread等应用启动相关的code,不是本文重点。

    <application android:process="com.android.phone"
                 android:allowClearUserData="false"
                 android:fullBackupOnly="true"
                 android:backupInForeground="true"
                 android:backupAgent="TelephonyBackupAgent"
                 android:restoreAnyVersion="true"
                 android:label="@string/app_label"
                 android:icon="@mipmap/ic_launcher_phone"
                 android:usesCleartextTraffic="true"
                 android:defaultToDeviceProtectedStorage="true"
                 android:directBootAware="true">

        <provider android:name="TelephonyProvider"
                  android:authorities="telephony"
                  android:exported="true"
                  android:singleUser="true"
                  android:multiprocess="false" />
   ...

Telephonyprovider的oncreate()方法,首先new 了一个DatabaseHelper对象。

 @Override
    public boolean onCreate() {
        mOpenHelper = new DatabaseHelper(getContext());//new一个DatabaseHelper对象,DatabaseHelper继承自SQLiteOpenHelper。

        // Call getReadableDatabase() to make sure onUpgrade is called
        if (VDBG) log("onCreate: calling getReadableDatabase to trigger onUpgrade");
        SQLiteDatabase db = getReadableDatabase();
        ......
    }

创建完DatabaseHelper对象后,便调用了getReadableDatabase()方法,从这里开始,我们的注意点就要转移到DatabaseHelper和SQLiteOpenHelper这两个类中,TelephonyProvider中的getReadableDatabase()方法会调用DatabaseHelper的getReadableDatabase()方法,DatabaseHelper并没有重写getReadableDatabase()方法,所以最终会调用到SQLiteOpenHelper中的getReadableDatabase()方法,该方法又调用内部private方法getDatabaseLocked。
下面就来看看getDatabaseLocked(boolean writable)方法都做了什么工作:

private SQLiteDatabase getDatabaseLocked(boolean writable) {
        if (mDatabase != null) {//刚开机,mDatabase由于没有指向有效对象,当然为null。
            if (!mDatabase.isOpen()) {
                // Darn!  The user closed the database by calling mDatabase.close().
                mDatabase = null;
            } else if (!writable || !mDatabase.isReadOnly()) {//不为null时,将在database 非只读或者用于只读的情况时返回mDatabase对象。
                // The database is already open for business.
                return mDatabase;
            }
        }

        if (mIsInitializing) {
            throw new IllegalStateException("getDatabase called recursively");
        }

        SQLiteDatabase db = mDatabase;
        try {
            mIsInitializing = true;

            if (db != null) {
                if (writable && db.isReadOnly()) {
                    db.reopenReadWrite();
                }
            } else if (mName == null) {
                db = SQLiteDatabase.create(null);
            } else {
                try {
                    if (DEBUG_STRICT_READONLY && !writable) {//获取用于debug的只读database。
                        final String path = mContext.getDatabasePath(mName).getPath();
                        db = SQLiteDatabase.openDatabase(path, mFactory,
                                SQLiteDatabase.OPEN_READONLY, mErrorHandler);
                    } else {//打开或者创建一个可写的database。
                        db = mContext.openOrCreateDatabase(mName, mEnableWriteAheadLogging ?
                                Context.MODE_ENABLE_WRITE_AHEAD_LOGGING : 0,
                                mFactory, mErrorHandler);
                    }
                } catch (SQLiteException ex) {
                    if (writable) {
                        throw ex;
                    }
                    Log.e(TAG, "Couldn't open " + mName
                            + " for writing (will try read-only):", ex);
                    final String path = mContext.getDatabasePath(mName).getPath();
                    db = SQLiteDatabase.openDatabase(path, mFactory,
                            SQLiteDatabase.OPEN_READONLY, mErrorHandler);
                }
            }

            onConfigure(db);

            final int version = db.getVersion();//获取DB的version。
            if (version != mNewVersion) {//对于刚创建的db,version的值是0,而mNewVersion是我们在创建DatabaseHelper时作为参数传入到DatabaseHelper对象的。这个参数的获取是通过
                if (db.isReadOnly()) {//Telephonyprovider.gerVersion()方法,方法内会从“com.android.internal.R.xml.apns”获取version信息然后和DATABASE_VERSION做按位或运算,结果作为version。
                    throw new SQLiteException("Can't upgrade read-only database from version " +
                            db.getVersion() + " to " + mNewVersion + ": " + mName);
                }

                if (version > 0 && version < mMinimumSupportedVersion) {
                    File databaseFile = new File(db.getPath());
                    onBeforeDelete(db);
                    db.close();
                    if (SQLiteDatabase.deleteDatabase(databaseFile)) {
                        mIsInitializing = false;
                        return getDatabaseLocked(writable);
                    } else {
                        throw new IllegalStateException("Unable to delete obsolete database "
                                + mName + " with version " + version);
                    }
                } else {
                    db.beginTransaction();
                    try {
                        if (version == 0) {//刚创建的DB,调用onCreate(...)方法
                            onCreate(db);
                        } else {
                            if (version > mNewVersion) {//db的version比当前版本新,调用onDowngrade方法
                                onDowngrade(db, version, mNewVersion);
                            } else {//否则调用onUpgrade方法。
                                onUpgrade(db, version, mNewVersion);
                            }
                        }
                        db.setVersion(mNewVersion);
                        db.setTransactionSuccessful();
                    } finally {
                        db.endTransaction();
                    }
                }
            }
            ......
    }

数据表的创建在DatabaseHelper.onCreate方法里面,

        @Override
        public void onCreate(SQLiteDatabase db) {
            if (DBG) log("dbh.onCreate:+ db=" + db);
            createSimInfoTable(db);//创建siminfo表
            createCarriersTable(db, CARRIERS_TABLE);//创建carriers表,存放Apn信息。
            initDatabase(db);//将配置的apn数据放进数据表。
            if (DBG) log("dbh.onCreate:- db=" + db);
        }

siminfo表中的主要字段:

  /**
     * TelephonyProvider unique key column name is the subscription id.
     * <P>Type: TEXT (String)</P>
     * 主键,也就是我们所使用的subscription id。
     */
    /** @hide */
    public static final String UNIQUE_KEY_SUBSCRIPTION_ID = "_id";

    /**
     * TelephonyProvider column name for SIM ICC Identifier
     * <P>Type: TEXT (String)</P>
     * SIM卡的ICCID。
     */
    /** @hide */
    public static final String ICC_ID = "icc_id";

    /**
     * TelephonyProvider column name for user SIM_SlOT_INDEX
     * <P>Type: INTEGER (int)</P>
     * 卡槽ID。
     */
    /** @hide */
    public static final String SIM_SLOT_INDEX = "sim_id";
    ...
    /**
     * TelephonyProvider column name for user displayed name.
     * <P>Type: TEXT (String)</P>
     * 可能是SIM卡里的spn,手机配置好的spn 或者CARD1/CARD2;也可能是用户输入的名称。
     */
    /** @hide */
    public static final String DISPLAY_NAME = "display_name";

    /**
     * TelephonyProvider column name for the service provider name for the SIM.
     * <P>Type: TEXT (String)</P>
     * 这个值可能是spn 也可能是registered plmn,和状态栏显示是一致的。
     */
    /** @hide */
    public static final String CARRIER_NAME = "carrier_name";

    /**
     * Default name resource
     * @hide
     */
    public static final int DEFAULT_NAME_RES = com.android.internal.R.string.unknownName;

    /**
     * TelephonyProvider column name for source of the user displayed name.
     * <P>Type: INT (int)</P> with one of the NAME_SOURCE_XXXX values below
     * 用来说明display_name字段内容的来源; 分为四类:NAME_SOURCE_UNDEFINDED(-1), NAME_SOURCE_DEFAULT_SOURCE(0), NAME_SOURCE_SIM_SOURCE(1),       
     *  NAME_SOURCE_USER_INPUT(2)
     * @hide
     */
    public static final String NAME_SOURCE = "name_source";
   ...... 
  /**
     * TelephonyProvider column name for the color of a SIM.
     * <P>Type: INTEGER (int)</P>
     */ 为SIM卡分配的颜色值
    /** @hide */
    public static final String COLOR = "color";

    /** @hide */
    public static final int COLOR_1 = 0;

    /** @hide */
    public static final int COLOR_2 = 1;

    /** @hide */
    public static final int COLOR_3 = 2;

    /** @hide */
    public static final int COLOR_4 = 3;

    /** @hide */
    public static final int COLOR_DEFAULT = COLOR_1;

    /**
     * TelephonyProvider column name for the phone number of a SIM.
     * <P>Type: TEXT (String)</P>
     * 手机号码
     */
    /** @hide */
    public static final String NUMBER = "number";

    /**
     * TelephonyProvider column name for the number display format of a SIM.
     * <P>Type: INTEGER (int)</P>
     */
    /** @hide */
    public static final String DISPLAY_NUMBER_FORMAT = "display_number_format";

    /** @hide */
    public static final int DISPLAY_NUMBER_NONE = 0;

    /** @hide */
    public static final int DISPLAY_NUMBER_FIRST = 1;

    /** @hide */
    public static final int DISPLAY_NUMBER_LAST = 2;

    /** @hide */
    public static final int DISPLAY_NUMBER_DEFAULT = DISPLAY_NUMBER_FIRST;

    /**
     * TelephonyProvider column name for permission for data roaming of a SIM.
     * <P>Type: INTEGER (int)</P>
     * 标识data roaming状态,默认值为DATA_ROAMING_DISABLE(0)。
     */
    /** @hide */
    public static final String DATA_ROAMING = "data_roaming";

    /** Indicates that data roaming is enabled for a subscription */
    public static final int DATA_ROAMING_ENABLE = 1;

    /** Indicates that data roaming is disabled for a subscription */
    public static final int DATA_ROAMING_DISABLE = 0;

    /** @hide */
    public static final int DATA_ROAMING_DEFAULT = DATA_ROAMING_DISABLE;

    /** @hide */
    public static final int SIM_PROVISIONED = 0;

    /**
     * TelephonyProvider column name for the MCC associated with a SIM.
     * <P>Type: INTEGER (int)</P>
     * MCC
     * @hide
     */
    public static final String MCC = "mcc";

    /**
     * TelephonyProvider column name for the MNC associated with a SIM.
     * <P>Type: INTEGER (int)</P>
     * MNC
     * @hide
     */
    public static final String MNC = "mnc";

剩下的几个字段都是cell broadcast相关的了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值