Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 85201911 authored by Mingming Cai's avatar Mingming Cai Committed by Automerger Merge Worker
Browse files

Merge "Read PNN and OPL from SIM" am: 63ea9af9

Change-Id: I5167a5781778f71010eb176ad591aa5d1fd4dfa3
parents b761a3ab 63ea9af9
Loading
Loading
Loading
Loading
+140 −13
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.internal.telephony.uicc;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.os.AsyncResult;
@@ -25,6 +26,7 @@ import android.os.Message;
import android.os.Registrant;
import android.os.RegistrantList;
import android.os.SystemClock;
import android.telephony.CellIdentity;
import android.telephony.SubscriptionInfo;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -53,6 +55,11 @@ public abstract class IccRecords extends Handler implements IccConstants {
    protected static final boolean DBG = true;
    protected static final boolean VDBG = false; // STOPSHIP if true

    public static final int PLMN_MIN_LENGTH = CellIdentity.MCC_LENGTH
            + CellIdentity.MNC_MIN_LENGTH;
    public static final int PLMN_MAX_LENGTH = CellIdentity.MCC_LENGTH
            + CellIdentity.MNC_MAX_LENGTH;

    // Lookup table for carriers known to produce SIMs which incorrectly indicate MNC length.
    private static final String[] MCCMNC_CODES_HAVING_3DIGITS_MNC = {
        "302370", "302720", "310260",
@@ -148,6 +155,14 @@ public abstract class IccRecords extends Handler implements IccConstants {
    // Reference: 3GPP TS 31.102 Section 4.2.66
    protected String[] mSpdi;

    // A list of PLMN Network Name (PNN).
    // Reference: 3GPP TS 31.102 Section 4.2.58
    protected PlmnNetworkName[] mPnns;

    // Operator PLMN List (OPL).
    // Reference: 3GPP TS 31.102 Section 4.2.59
    protected OperatorPlmnInfo[] mOpl;


    // Carrier name display condition bitmask
    // Reference: 3GPP TS 131.102 section 4.2.12 EF_SPN Display Condition
@@ -697,6 +712,14 @@ public abstract class IccRecords extends Handler implements IccConstants {
        return mPnnHomeName;
    }

    public PlmnNetworkName[] getPnns() {
        return mPnns;
    }

    public OperatorPlmnInfo[] getOpl() {
        return mOpl;
    }

    public void setMsisdnNumber(String alphaTag, String number,
            Message onComplete) {
        loge("setMsisdn() should not be invoked on base IccRecords");
@@ -1353,6 +1376,36 @@ public abstract class IccRecords extends Handler implements IccConstants {
        pw.flush();
    }

    /**
     * Get network name in PNN for the provided PLMN and LAC/TAC.
     *
     * @param opls OPL.
     * @param pnns PNN list.
     * @param plmn PLMN.
     * @param lacTac LAC/TAC
     * @return network Name for the provided PLMN and LAC/TAC.
     */
    @Nullable public static String getNetworkNameForPlmnFromPnnOpl(PlmnNetworkName[] pnns,
            OperatorPlmnInfo[] opls, @Nullable String plmn, int lacTac) {
        if (opls == null || pnns == null || plmn == null || plmn.length() < PLMN_MIN_LENGTH
                || plmn.length() > PLMN_MAX_LENGTH) {
            return null;
        }

        for (OperatorPlmnInfo operatorPlmnInfo: opls) {
            int pnnIdx = operatorPlmnInfo.getPnnIdx(plmn, lacTac);
            if (pnnIdx >= 0) {
                if (pnnIdx < pnns.length && pnns[pnnIdx] != null) {
                    return pnns[pnnIdx].getName();
                } else {
                    Rlog.e("IccRecords", "Invalid PNN record for Record" + pnnIdx);
                    break;
                }
            }
        }
        return null;
    }

    /**
     * Operator PLMN information. This contains the location area information or tracking area
     * that are used to associate a specific name contained in EF_PNN.
@@ -1360,28 +1413,75 @@ public abstract class IccRecords extends Handler implements IccConstants {
     * Reference: 3GPP TS 31.102 section 4.2.59 EF_OPL
     */
    public static final class OperatorPlmnInfo {
        // PLMN numeric that may contains wildcard character ".".
        // For example, the pattern "123..." could match all PLMN which mcc is 123.
        // PLMN numeric that may contains wildcard character "D".
        // A BCD value of 'D' in any of the MCC and/or MNC digits shall be used to indicate
        // a "wild" value for that corresponding MCC/MNC digit.
        // For example, the pattern "123DDD" could match all PLMN which mcc is 123.
        public final String plmnNumericPattern;

        public final int lacTacStart;
        public final int lacTacEnd;
        // Identifier of operator name in PNN to be displayed.
        // 0 indicates that the name is to be taken from other sources, see 3GPP TS 22.101.
        // pnnRecordId > 0 indicates record # (pnnRecordId - 1) in PNNs.
        public final int pnnRecordId;

        public final int plmnNetworkNameIndex;
        public OperatorPlmnInfo(String plmnNumericPattern, int lacTacStart, int lacTacEnd,
                                int plmnNetworkNameIndex) {
        public OperatorPlmnInfo(@NonNull String plmnNumericPattern, int lacTacStart, int lacTacEnd,
                                int pnnRecordId) {
            this.plmnNumericPattern = plmnNumericPattern;
            this.lacTacStart = lacTacStart;
            this.lacTacEnd = lacTacEnd;
            this.plmnNetworkNameIndex = plmnNetworkNameIndex;
            this.pnnRecordId = pnnRecordId;
        }

        /**
         * Check whether provided plmn and lacTac matches the stored OperatorPlmnInfo.
         *
         * @return -1 if not matching.
         */
        public int getPnnIdx(@Nullable String plmn, int lacTac) {
            if (plmn == null || plmn.length() != plmnNumericPattern.length()) return -1;

            // Check whether PLMN matches with the plmnNumericPattern
            // Character-by-character check is for performance reasons.
            for (int i = 0; i < plmn.length(); i++) {
                if (plmn.charAt(i) != plmnNumericPattern.charAt(i)
                        && plmnNumericPattern.charAt(i) != 'D') {
                    return -1;
                }
            }
            // As defiend in 3GPP TS 31.102 section 4.2.59 , lacTacStart = 0 and lacTacEnd = 0xFFFE
            // are used to indicate the entire range of LACs/TACs for the given PLMN.
            if (lacTacStart == 0 && lacTacEnd == 0xFFFE) {
                return pnnRecordId - 1;
            }
            if (lacTac < lacTacStart || lacTac > lacTacEnd) return -1;
            return pnnRecordId - 1;
        }

        @Override
        public int hashCode() {
            return Objects.hash(plmnNumericPattern, lacTacStart, lacTacEnd,
                    pnnRecordId);
        }

        @Override
        public boolean equals(Object other) {
            if (this == other) return true;
            if (!(other instanceof OperatorPlmnInfo)) return false;

            OperatorPlmnInfo opi = (OperatorPlmnInfo) other;
            return TextUtils.equals(plmnNumericPattern, opi.plmnNumericPattern)
                    && lacTacStart == opi.lacTacStart
                    && lacTacEnd == opi.lacTacEnd
                    && pnnRecordId == opi.pnnRecordId;
        }

        @Override
        public String toString() {
            return "{ plmnNumericPattern = " + plmnNumericPattern
                    + "lacTacStart = " + lacTacStart
                    + "lacTacEnd = " + lacTacEnd
                    + "plmnNetworkNameIndex = " + plmnNetworkNameIndex
            return "{plmnNumericPattern = " + plmnNumericPattern + ", "
                    + "lacTacStart = " + lacTacStart + ", "
                    + "lacTacEnd = " + lacTacEnd + ", "
                    + "pnnRecordId = " + pnnRecordId
                    + "}";
        }
    }
@@ -1398,9 +1498,36 @@ public abstract class IccRecords extends Handler implements IccConstants {
            this.shortName = shortName;
        }

        /**
         * Get the name stored in the PlmnNetworkName.
         * @return the full name if it's available; otherwise, short Name.
         */
        @Nullable public String getName() {
            if (!TextUtils.isEmpty(fullName)) {
                return fullName;
            } else {
                return shortName;
            }
        }

        @Override
        public int hashCode() {
            return Objects.hash(fullName, shortName);
        }

        @Override
        public boolean equals(Object other) {
            if (this == other) return true;
            if (!(other instanceof PlmnNetworkName)) return false;

            PlmnNetworkName pnn = (PlmnNetworkName) other;
            return TextUtils.equals(fullName, pnn.fullName)
                    && TextUtils.equals(shortName, pnn.shortName);
        }

        @Override
        public String toString() {
            return "{ fullName = " + fullName + " shortName = " + shortName + " }";
            return "{fullName = " + fullName + ", shortName = " + shortName + "}";
        }
    }
}
+105 −14
Original line number Diff line number Diff line
@@ -122,6 +122,9 @@ public class SIMRecords extends IccRecords {
    // Short Name IEI from TS 24.008
    static final int TAG_SHORT_NETWORK_NAME = 0x45;

    // PLMN Additional Information tag from from TS 24.008
    static final int TAG_PLMN_ADDITIONAL_INFORMATION = 0x80;

    // active CFF from CPHS 4.2 B.4.5
    static final int CFF_UNCONDITIONAL_ACTIVE = 0x0a;
    static final int CFF_UNCONDITIONAL_DEACTIVE = 0x05;
@@ -157,7 +160,8 @@ public class SIMRecords extends IccRecords {
    private static final int EVENT_GET_SPN_DONE = 12 + SIM_RECORD_EVENT_BASE;
    private static final int EVENT_GET_SPDI_DONE = 13 + SIM_RECORD_EVENT_BASE;
    private static final int EVENT_UPDATE_DONE = 14 + SIM_RECORD_EVENT_BASE;
    private static final int EVENT_GET_PNN_DONE = 15 + SIM_RECORD_EVENT_BASE;
    protected static final int EVENT_GET_PNN_DONE = 15 + SIM_RECORD_EVENT_BASE;
    protected static final int EVENT_GET_OPL_DONE = 16 + SIM_RECORD_EVENT_BASE;
    private static final int EVENT_GET_SST_DONE = 17 + SIM_RECORD_EVENT_BASE;
    private static final int EVENT_GET_ALL_SMS_DONE = 18 + SIM_RECORD_EVENT_BASE;
    private static final int EVENT_MARK_SMS_READ_DONE = 19 + SIM_RECORD_EVENT_BASE;
@@ -228,6 +232,8 @@ public class SIMRecords extends IccRecords {
        mEfCPHS_MWI = null;
        mSpdi = null;
        mPnnHomeName = null;
        mPnns = null;
        mOpl = null;
        mGid1 = null;
        mGid2 = null;
        mPlmnActRecords = null;
@@ -902,22 +908,22 @@ public class SIMRecords extends IccRecords {
                    isRecordLoadResponse = true;

                    ar = (AsyncResult) msg.obj;
                    data = (byte[]) ar.result;

                    if (ar.exception != null) {
                        break;
                    }

                    SimTlv tlv = new SimTlv(data, 0, data.length);
                    parseEfPnn((ArrayList<byte[]>) ar.result);
                    break;

                    for (; tlv.isValidObject(); tlv.nextObject()) {
                        if (tlv.getTag() == TAG_FULL_NETWORK_NAME) {
                            mPnnHomeName = IccUtils.networkNameToString(
                                    tlv.getData(), 0, tlv.getData().length);
                            log("PNN: " + mPnnHomeName);
                case EVENT_GET_OPL_DONE:
                    isRecordLoadResponse = true;

                    ar = (AsyncResult) msg.obj;
                    if (ar.exception != null) {
                        break;
                    }
                    }

                    parseEfOpl((ArrayList<byte[]>) ar.result);
                    break;

                case EVENT_GET_ALL_SMS_DONE:
@@ -1618,7 +1624,10 @@ public class SIMRecords extends IccRecords {
        mFh.loadEFTransparent(EF_SPDI, obtainMessage(EVENT_GET_SPDI_DONE));
        mRecordsToLoad++;

        mFh.loadEFLinearFixed(EF_PNN, 1, obtainMessage(EVENT_GET_PNN_DONE));
        mFh.loadEFLinearFixedAll(EF_PNN, obtainMessage(EVENT_GET_PNN_DONE));
        mRecordsToLoad++;

        mFh.loadEFLinearFixedAll(EF_OPL, obtainMessage(EVENT_GET_OPL_DONE));
        mRecordsToLoad++;

        mFh.loadEFTransparent(EF_SST, obtainMessage(EVENT_GET_SST_DONE));
@@ -1878,13 +1887,95 @@ public class SIMRecords extends IccRecords {
        for (int i = 0; i + 2 < plmnEntries.length; i += 3) {
            String plmnCode = IccUtils.bcdPlmnToString(plmnEntries, i);
            if (!TextUtils.isEmpty(plmnCode)) {
                log("EF_SPDI PLMN: " + plmnCode);
                tmpSpdi.add(plmnCode);
            }
        }
        log("parseEfSpdi: " + tmpSpdi);

        mSpdi = tmpSpdi.toArray(new String[tmpSpdi.size()]);
    }

    /**
     * Parse EF PLMN Network Name (PNN) record from SIM
     * Reference: 3GPP TS 31.102 Section 4.2.58.
     */
    private void parseEfPnn(ArrayList<byte[]> dataArray) {
        if (dataArray == null) return;

        final int count = dataArray.size();
        List<PlmnNetworkName> tmpPnns = new ArrayList<>(count);
        for (int i = 0; i < count; i++) {
            byte[] data = dataArray.get(i);
            SimTlv tlv = new SimTlv(data, 0, data.length);

            String longName = null;
            String shortName = null;
            for (; tlv.isValidObject(); tlv.nextObject()) {
                switch (tlv.getTag()) {
                    case TAG_FULL_NETWORK_NAME:
                        longName = IccUtils.networkNameToString(tlv.getData(), 0,
                                tlv.getData().length);
                        break;

                    case TAG_SHORT_NETWORK_NAME:
                        shortName = IccUtils.networkNameToString(tlv.getData(), 0,
                                tlv.getData().length);
                        break;

                    case TAG_PLMN_ADDITIONAL_INFORMATION:
                        // TODO(b/154300344): read PLMN Additional Information.
                        break;
                }
            }
            // PNNs must maintain their original indices. They will be referred to by index in OPL.
            tmpPnns.add(new PlmnNetworkName(longName, shortName));
        }
        log("parseEfPnn: " + tmpPnns);

        mPnns = tmpPnns.toArray(new PlmnNetworkName[0]);

        // For compatiblility with legacy code.
        if (mPnns.length > 0) mPnnHomeName = mPnns[0].getName();
    }

    /**
     * Parse EF Operator PLMN List (OPL) record from SIM
     * Reference: 3GPP TS 31.102 Section 4.2.59.
     */
    private void parseEfOpl(ArrayList<byte[]> dataArray) {
        if (dataArray == null) return;

        final int count = dataArray.size();
        List<OperatorPlmnInfo> tmpOpl = new ArrayList<>(count);
        for (int i = 0; i < count; i++) {
            byte[] data = dataArray.get(i);
            // data.length is 8 as defined in 3GPP TS 31.102 Section 4.2.59.
            // Byte 0 to 2 are for PLMN.
            // Byte 3 and 4 are for lacTacStart.
            // Byte 5 and 6 are for lacTacEnd.
            // Byte 7 is for PNN Record Identifier.
            if (data.length != 8) {
                loge("Invalid length for OPL record " + data);
                continue;
            }

            // A BCD value of 'D' in any of the MCC and/or MNC digits shall be used to indicate
            // a "wild" value for that corresponding MCC/MNC digit.
            String plmn = IccUtils.bcdPlmnToString(data, 0);
            if (plmn.length() < PLMN_MIN_LENGTH) {
                loge("Invalid length for decoded PLMN " + plmn);
                continue;
            }
            int lacTacStart = IccUtils.bytesToInt(data, 3, 2);
            int lacTacEnd = IccUtils.bytesToInt(data, 5, 2);
            int pnnRecordId = IccUtils.bytesToInt(data, 7, 1);

            tmpOpl.add(new OperatorPlmnInfo(plmn, lacTacStart, lacTacEnd, pnnRecordId));
        }
        log("parseEfOpl: " + tmpOpl);
        mOpl = tmpOpl.toArray(new OperatorPlmnInfo[0]);
    }

    /**
     * convert a byte array of packed plmns to an array of strings
     */
+103 −0
Original line number Diff line number Diff line
@@ -48,11 +48,16 @@ import android.util.Log;
import android.util.Pair;

import com.android.internal.telephony.TelephonyTest;
import com.android.internal.telephony.uicc.IccRecords.OperatorPlmnInfo;
import com.android.internal.telephony.uicc.IccRecords.PlmnNetworkName;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.util.ArrayList;
import java.util.List;

public class IccRecordsTest extends TelephonyTest {

    private IccRecords mIccRecords;
@@ -244,4 +249,102 @@ public class IccRecordsTest extends TelephonyTest {
        assertTrue("Two responses must be different.", !response.equals(response2));
    }

    @Test
    public void testOperatorPlmnInfo() {
        String plmn = "123456";
        int lacTacStart = 0x0000;
        int lacTacEnd = 0xFFFE;
        int pnnIndex = 2;

        OperatorPlmnInfo opi = new OperatorPlmnInfo(plmn, lacTacStart, lacTacEnd, pnnIndex);
        assertEquals(opi.getPnnIdx(plmn, lacTacStart), pnnIndex - 1);
        assertEquals(opi.getPnnIdx(plmn, lacTacEnd), pnnIndex - 1);
        assertEquals(opi.getPnnIdx(plmn, 0xFFFF), pnnIndex - 1);
        assertEquals(opi.getPnnIdx("654321", 0xFFFF), -1);
        assertEquals(opi.getPnnIdx("12345", 0xFFFF), -1);

        lacTacStart = 0x0001;
        lacTacEnd = 0x1FFF;
        opi = new OperatorPlmnInfo(plmn, lacTacStart, lacTacEnd, pnnIndex);
        assertEquals(opi.getPnnIdx(plmn, 2), pnnIndex - 1);
        assertEquals(opi.getPnnIdx(plmn, 0x2FFF), -1);
        assertEquals(opi.getPnnIdx(plmn, 0xFFFF), -1);

        plmn = "123DDD";
        opi = new OperatorPlmnInfo(plmn, lacTacStart, lacTacEnd, pnnIndex);
        assertEquals(opi.getPnnIdx("123123", lacTacStart), pnnIndex - 1);
        assertEquals(opi.getPnnIdx("12345", lacTacStart), -1);
    }

    @Test
    public void testGetNetworkNameForPlmn() {
        // Set up PNN
        String fullName1 = "Name full 1";
        String shortName1 = "Name short 1";
        String fullName2 = "Name full 2";
        String shortName2 = "Name short 2";
        List<PlmnNetworkName> pnns = new ArrayList<PlmnNetworkName>();
        pnns.add(new PlmnNetworkName(fullName1, shortName1));
        pnns.add(new PlmnNetworkName(fullName2, shortName2));
        pnns.add(new PlmnNetworkName(null, shortName2));
        PlmnNetworkName[] pnnsArray = pnns.toArray(new PlmnNetworkName[0]);

        // Set up OPL
        String plmn1 = "345678";
        String plmn2 = "456789";
        String plmn3 = "567890";
        int lacTacStart = 0x0000;
        int lacTacEnd = 0xFFFE;
        int pnnIndex = 1;
        List<OperatorPlmnInfo> opl = new ArrayList<OperatorPlmnInfo>();
        opl.add(new OperatorPlmnInfo(plmn1, lacTacStart, lacTacEnd, pnnIndex));
        opl.add(new OperatorPlmnInfo(plmn2, lacTacStart, lacTacEnd, pnnIndex + 1));
        opl.add(new OperatorPlmnInfo(plmn3, lacTacStart, lacTacEnd, pnnIndex + 2));
        OperatorPlmnInfo[] oplArray = opl.toArray(new OperatorPlmnInfo[0]);

        // Test
        assertNull(IccRecords.getNetworkNameForPlmnFromPnnOpl(pnnsArray, oplArray, null, 0));
        assertEquals(fullName1, IccRecords.getNetworkNameForPlmnFromPnnOpl(pnnsArray, oplArray,
                plmn1, 0));
        assertEquals(fullName2, IccRecords.getNetworkNameForPlmnFromPnnOpl(pnnsArray, oplArray,
                plmn2, 0));
        assertEquals(shortName2, IccRecords.getNetworkNameForPlmnFromPnnOpl(pnnsArray, oplArray,
                plmn3, 0));
    }

    @Test
    public void testGetNetworkNameForPlmnFromPnnOpl() {
        // Set up PNN
        String fullName1 = "Name full 1";
        String shortName1 = "Name short 1";
        String fullName2 = "Name full 2";
        String shortName2 = "Name short 2";
        List<PlmnNetworkName> pnns = new ArrayList<PlmnNetworkName>();
        pnns.add(new PlmnNetworkName(fullName1, shortName1));
        pnns.add(new PlmnNetworkName(fullName2, shortName2));
        pnns.add(new PlmnNetworkName(null, shortName2));
        PlmnNetworkName[] pnnsArray = pnns.toArray(new PlmnNetworkName[0]);

        // Set up OPL
        String plmn1 = "345678";
        String plmn2 = "456789";
        String plmn3 = "567890";
        int lacTacStart = 0x0000;
        int lacTacEnd = 0xFFFE;
        int pnnIndex = 1;
        List<OperatorPlmnInfo> opl = new ArrayList<OperatorPlmnInfo>();
        opl.add(new OperatorPlmnInfo(plmn1, lacTacStart, lacTacEnd, pnnIndex));
        opl.add(new OperatorPlmnInfo(plmn2, lacTacStart, lacTacEnd, pnnIndex + 1));
        opl.add(new OperatorPlmnInfo(plmn3, lacTacStart, lacTacEnd, pnnIndex + 2));
        OperatorPlmnInfo[] oplArray = opl.toArray(new OperatorPlmnInfo[0]);

        // Test
        assertNull(IccRecords.getNetworkNameForPlmnFromPnnOpl(pnnsArray, oplArray, null, 0));
        assertEquals(fullName1, IccRecords.getNetworkNameForPlmnFromPnnOpl(pnnsArray, oplArray,
                plmn1, 0));
        assertEquals(fullName2, IccRecords.getNetworkNameForPlmnFromPnnOpl(pnnsArray, oplArray,
                plmn2, 0));
        assertEquals(shortName2, IccRecords.getNetworkNameForPlmnFromPnnOpl(pnnsArray, oplArray,
                plmn3, 0));
    }
}
+113 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.internal.telephony.uicc;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Matchers.eq;
@@ -35,7 +36,10 @@ import androidx.test.runner.AndroidJUnit4;

import com.android.internal.telephony.CommandException;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.GsmAlphabet;
import com.android.internal.telephony.TelephonyTest;
import com.android.internal.telephony.uicc.IccRecords.OperatorPlmnInfo;
import com.android.internal.telephony.uicc.IccRecords.PlmnNetworkName;

import org.junit.After;
import org.junit.Before;
@@ -286,4 +290,113 @@ public class SIMRecordsTest extends TelephonyTest {
        assertNull(ar.exception);
        assertNull(ar.result);
    }

    @Test
    public void testGetEfPnn() {
        ArrayList<byte[]> rawPnns = new ArrayList<byte[]>();
        List<PlmnNetworkName> targetPnns = new ArrayList<PlmnNetworkName>();

        String name = "Test 1";
        rawPnns.add(encodePnn(name));
        targetPnns.add(new PlmnNetworkName(name, null));
        name = "Test 2";
        rawPnns.add(encodePnn(name));
        targetPnns.add(new PlmnNetworkName(name, null));

        Message message = mSIMRecordsUT.obtainMessage(SIMRecords.EVENT_GET_PNN_DONE);
        AsyncResult ar = AsyncResult.forMessage(message, rawPnns, null);
        mSIMRecordsUT.handleMessage(message);
        List<PlmnNetworkName> parsedPnns = Arrays.asList(mSIMRecordsUT.getPnns());

        assertEquals(parsedPnns, targetPnns);
    }

    private static byte[] encodePnn(String name) {
        byte[] gsm7BitName = new byte[] {};
        try {
            gsm7BitName = GsmAlphabet.stringToGsm7BitPacked(name);
            gsm7BitName[0] = (byte) (name.length() % 8 | 0x80);
        } catch (Exception ex) {
            fail("SimRecordsTest: GsmAlphabet.stringToGsm7BitPacked() exception:" + ex);
        }

        byte[] encodedName = new byte[gsm7BitName.length + 2];
        encodedName[0] = 0x43;
        encodedName[1] = (byte) gsm7BitName.length;
        System.arraycopy(gsm7BitName, 0, encodedName, 2, gsm7BitName.length);

        return encodedName;
    }

    @Test
    public void testGetEfOpl() {
        ArrayList<byte[]> rawOpl = new ArrayList<byte[]>();
        List<OperatorPlmnInfo> targetOpl = new ArrayList<OperatorPlmnInfo>();

        // OperatorPlmnInfo 1
        String plmn = "123456";
        int lacTacStart = 0x0000;
        int lacTacEnd = 0xFFFE;
        int pnnIndex = 0;

        rawOpl.add(encodeOpl(plmn, lacTacStart, lacTacEnd, pnnIndex));
        targetOpl.add(new OperatorPlmnInfo(plmn, lacTacStart, lacTacEnd, pnnIndex));

        Message message = mSIMRecordsUT.obtainMessage(SIMRecords.EVENT_GET_OPL_DONE);
        AsyncResult ar = AsyncResult.forMessage(message, rawOpl, null);
        mSIMRecordsUT.handleMessage(message);
        List<OperatorPlmnInfo> parsedOpl = Arrays.asList(mSIMRecordsUT.getOpl());

        assertEquals(targetOpl, parsedOpl);

        // OperatorPlmnInfo 2
        plmn = "123DDD";
        lacTacStart = 0x0000;
        lacTacEnd = 0xFFFE;
        pnnIndex = 123;

        rawOpl.add(encodeOpl(plmn, lacTacStart, lacTacEnd, pnnIndex));
        targetOpl.add(new OperatorPlmnInfo(plmn, lacTacStart, lacTacEnd, pnnIndex));

        message = mSIMRecordsUT.obtainMessage(SIMRecords.EVENT_GET_OPL_DONE);
        ar = AsyncResult.forMessage(message, rawOpl, null);
        mSIMRecordsUT.handleMessage(message);
        parsedOpl = Arrays.asList(mSIMRecordsUT.getOpl());

        assertEquals(targetOpl, parsedOpl);

        // OperatorPlmnInfo 3
        plmn = "123";
        lacTacStart = 0x0000;
        lacTacEnd = 0xFFFE;
        pnnIndex = 123;

        rawOpl.add(encodeOpl(plmn, lacTacStart, lacTacEnd, pnnIndex));

        message = mSIMRecordsUT.obtainMessage(SIMRecords.EVENT_GET_OPL_DONE);
        ar = AsyncResult.forMessage(message, rawOpl, null);
        mSIMRecordsUT.handleMessage(message);
        parsedOpl = Arrays.asList(mSIMRecordsUT.getOpl());

        assertEquals(targetOpl, parsedOpl);
    }

    private byte[] encodeOpl(String plmn, int lacTacStart, int lacTacEnd, int pnnIndex) {
        byte[] data = new byte[8];

        if (plmn.length() == 5 || plmn.length() == 6) {
            IccUtils.stringToBcdPlmn(plmn, data, 0);
        } else {
            data[0] = (byte) 0xFF;
            data[1] = (byte) 0xFF;
            data[2] = (byte) 0xFF;
        }
        data[3] = (byte) (lacTacStart >>> 8);
        data[4] = (byte) lacTacStart;
        data[5] = (byte) (lacTacEnd >>> 8);
        data[6] = (byte) lacTacEnd;
        data[7] = (byte) pnnIndex;

        return data;
    }
}