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

Commit 63ea9af9 authored by Mingming Cai's avatar Mingming Cai Committed by Gerrit Code Review
Browse files

Merge "Read PNN and OPL from SIM"

parents 9d7ed998 e3eee099
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;
    }
}