Merge "Remove obsolete getIsimChallengeResponse"
diff --git a/src/java/com/android/internal/telephony/BaseCommands.java b/src/java/com/android/internal/telephony/BaseCommands.java
index b70e800..8a0f918 100644
--- a/src/java/com/android/internal/telephony/BaseCommands.java
+++ b/src/java/com/android/internal/telephony/BaseCommands.java
@@ -74,7 +74,7 @@
     protected RegistrantList mCarrierInfoForImsiEncryptionRegistrants = new RegistrantList();
     protected RegistrantList mRilNetworkScanResultRegistrants = new RegistrantList();
     protected RegistrantList mModemResetRegistrants = new RegistrantList();
-
+    protected RegistrantList mNattKeepaliveStatusRegistrants = new RegistrantList();
 
     protected Registrant mGsmSmsRegistrant;
     protected Registrant mCdmaSmsRegistrant;
@@ -939,4 +939,20 @@
     public void unregisterForCarrierInfoForImsiEncryption(Handler h) {
         mCarrierInfoForImsiEncryptionRegistrants.remove(h);
     }
+
+    @Override
+    public void registerForNattKeepaliveStatus(Handler h, int what, Object obj) {
+        Registrant r = new Registrant(h, what, obj);
+
+        synchronized (mStateMonitor) {
+            mNattKeepaliveStatusRegistrants.add(r);
+        }
+    }
+
+    @Override
+    public void unregisterForNattKeepaliveStatus(Handler h) {
+        synchronized (mStateMonitor) {
+            mNattKeepaliveStatusRegistrants.remove(h);
+        }
+    }
 }
diff --git a/src/java/com/android/internal/telephony/CommandException.java b/src/java/com/android/internal/telephony/CommandException.java
index 1d2cc3a..fe9fa72 100644
--- a/src/java/com/android/internal/telephony/CommandException.java
+++ b/src/java/com/android/internal/telephony/CommandException.java
@@ -259,6 +259,8 @@
                 return new CommandException(Error.DEVICE_IN_USE);
             case RILConstants.ABORTED:
                 return new CommandException(Error.ABORTED);
+            case RILConstants.INVALID_RESPONSE:
+                return new CommandException(Error.INVALID_RESPONSE);
             case RILConstants.OEM_ERROR_1:
                 return new CommandException(Error.OEM_ERROR_1);
             case RILConstants.OEM_ERROR_2:
diff --git a/src/java/com/android/internal/telephony/CommandsInterface.java b/src/java/com/android/internal/telephony/CommandsInterface.java
index 483546f..ab12091 100644
--- a/src/java/com/android/internal/telephony/CommandsInterface.java
+++ b/src/java/com/android/internal/telephony/CommandsInterface.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony;
 
+import android.net.KeepalivePacketData;
 import android.os.Handler;
 import android.os.Message;
 import android.os.WorkSource;
@@ -2061,7 +2062,7 @@
      * Register for unsolicited PCO data.  This information is carrier-specific,
      * opaque binary blobs destined for carrier apps for interpretation.
      *
-     * @param h Handler for notificaiton message.
+     * @param h Handler for notification message.
      * @param what User-defined message code.
      * @param obj User object.
      */
@@ -2121,7 +2122,7 @@
     /**
      * Register for unsolicited Carrier Public Key.
      *
-     * @param h Handler for notificaiton message.
+     * @param h Handler for notification message.
      * @param what User-defined message code.
      * @param obj User object.
      */
@@ -2130,14 +2131,14 @@
     /**
      * DeRegister for unsolicited Carrier Public Key.
      *
-     * @param h Handler for notificaiton message.
+     * @param h Handler for notification message.
      */
     void unregisterForCarrierInfoForImsiEncryption(Handler h);
 
     /**
      * Register for unsolicited Network Scan result.
      *
-     * @param h Handler for notificaiton message.
+     * @param h Handler for notification message.
      * @param what User-defined message code.
      * @param obj User object.
      */
@@ -2146,10 +2147,45 @@
     /**
      * DeRegister for unsolicited Network Scan result.
      *
-     * @param h Handler for notificaiton message.
+     * @param h Handler for notification message.
      */
     void unregisterForNetworkScanResult(Handler h);
 
+    /**
+     * Register for unsolicited NATT Keepalive Status Indications
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    void registerForNattKeepaliveStatus(Handler h, int what, Object obj);
+
+    /**
+     * Deregister for unsolicited NATT Keepalive Status Indications.
+     *
+     * @param h Handler for notification message.
+     */
+    void unregisterForNattKeepaliveStatus(Handler h);
+
+    /**
+     * Start sending NATT Keepalive packets on a specified data connection
+     *
+     * @param contextId cid that identifies the data connection for this keepalive
+     * @param packetData the keepalive packet data description
+     * @param intervalMillis a time interval in ms between keepalive packet transmissions
+     * @param result a Message to return to the requester
+     */
+    void startNattKeepalive(
+            int contextId, KeepalivePacketData packetData, int intervalMillis, Message result);
+
+    /**
+     * Stop sending NATT Keepalive packets on a specified data connection
+     *
+     * @param sessionHandle the keepalive session handle (from the modem) to stop
+     * @param result a Message to return to the requester
+     */
+    void stopNattKeepalive(int sessionHandle, Message result);
+
     default public List<ClientRequestStats> getClientRequestStats() {
         return null;
     }
diff --git a/src/java/com/android/internal/telephony/RIL.java b/src/java/com/android/internal/telephony/RIL.java
index fab8c03..eefcdc8 100644
--- a/src/java/com/android/internal/telephony/RIL.java
+++ b/src/java/com/android/internal/telephony/RIL.java
@@ -53,6 +53,7 @@
 import android.hardware.radio.V1_0.SmsWriteArgs;
 import android.hardware.radio.V1_0.UusInfo;
 import android.net.ConnectivityManager;
+import android.net.KeepalivePacketData;
 import android.net.LinkAddress;
 import android.net.NetworkUtils;
 import android.os.AsyncResult;
@@ -106,6 +107,7 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.net.Inet4Address;
+import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -3739,6 +3741,93 @@
     }
 
     @Override
+    public void startNattKeepalive(
+            int contextId, KeepalivePacketData packetData, int intervalMillis, Message result) {
+        checkNotNull(packetData, "KeepaliveRequest cannot be null.");
+        IRadio radioProxy = getRadioProxy(result);
+        if (radioProxy == null) {
+            riljLoge("Radio Proxy object is null!");
+            return;
+        }
+
+        android.hardware.radio.V1_1.IRadio radioProxy11 =
+                android.hardware.radio.V1_1.IRadio.castFrom(radioProxy);
+        if (radioProxy11 == null) {
+            if (result != null) {
+                AsyncResult.forMessage(result, null,
+                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                result.sendToTarget();
+            }
+            return;
+        }
+
+        RILRequest rr = obtainRequest(
+                RIL_REQUEST_START_KEEPALIVE, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+        try {
+            android.hardware.radio.V1_1.KeepaliveRequest req =
+                    new android.hardware.radio.V1_1.KeepaliveRequest();
+
+            req.cid = contextId;
+
+            if (packetData.dstAddress instanceof Inet4Address) {
+                req.type = android.hardware.radio.V1_1.KeepaliveType.NATT_IPV4;
+            } else if (packetData.dstAddress instanceof Inet6Address) {
+                req.type = android.hardware.radio.V1_1.KeepaliveType.NATT_IPV6;
+            } else {
+                AsyncResult.forMessage(result, null,
+                        CommandException.fromRilErrno(INVALID_ARGUMENTS));
+                result.sendToTarget();
+                return;
+            }
+
+            appendPrimitiveArrayToArrayList(
+                    packetData.srcAddress.getAddress(), req.sourceAddress);
+            req.sourcePort = packetData.srcPort;
+            appendPrimitiveArrayToArrayList(
+                    packetData.dstAddress.getAddress(), req.destinationAddress);
+            req.destinationPort = packetData.dstPort;
+
+            radioProxy11.startKeepalive(rr.mSerial, req);
+        } catch (RemoteException | RuntimeException e) {
+            handleRadioProxyExceptionForRR(rr, "startNattKeepalive", e);
+        }
+    }
+
+    @Override
+    public void stopNattKeepalive(int sessionHandle, Message result) {
+        IRadio radioProxy = getRadioProxy(result);
+        if (radioProxy == null) {
+            Rlog.e(RIL.RILJ_LOG_TAG, "Radio Proxy object is null!");
+            return;
+        }
+
+        android.hardware.radio.V1_1.IRadio radioProxy11 =
+                android.hardware.radio.V1_1.IRadio.castFrom(radioProxy);
+        if (radioProxy11 == null) {
+            if (result != null) {
+                AsyncResult.forMessage(result, null,
+                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                result.sendToTarget();
+            }
+            return;
+        }
+
+        RILRequest rr = obtainRequest(
+                RIL_REQUEST_STOP_KEEPALIVE, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+        try {
+            radioProxy11.stopKeepalive(rr.mSerial, sessionHandle);
+        } catch (RemoteException | RuntimeException e) {
+            handleRadioProxyExceptionForRR(rr, "stopNattKeepalive", e);
+        }
+    }
+
+    @Override
     public void getIMEI(Message result) {
         throw new RuntimeException("getIMEI not expected to be called");
     }
@@ -4667,6 +4756,10 @@
                 return "RIL_REQUEST_GET_SLOT_STATUS";
             case RIL_REQUEST_SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING:
                 return "RIL_REQUEST_SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING";
+            case RIL_REQUEST_START_KEEPALIVE:
+                return "RIL_REQUEST_START_KEEPALIVE";
+            case RIL_REQUEST_STOP_KEEPALIVE:
+                return "RIL_REQUEST_STOP_KEEPALIVE";
             default: return "<unknown request>";
         }
     }
@@ -4771,6 +4864,8 @@
                 return "RIL_UNSOL_NETWORK_SCAN_RESULT";
             case RIL_UNSOL_ICC_SLOT_STATUS:
                 return "RIL_UNSOL_ICC_SLOT_STATUS";
+            case RIL_UNSOL_KEEPALIVE_STATUS:
+                return "RIL_UNSOL_KEEPALIVE_STATUS";
             default:
                 return "<unknown response>";
         }
@@ -4851,6 +4946,13 @@
         return mClientWakelockTracker.getClientRequestStats();
     }
 
+    /** Append the data to the end of an ArrayList */
+    public static void appendPrimitiveArrayToArrayList(byte[] src, ArrayList<Byte> dst) {
+        for (byte b : src) {
+            dst.add(b);
+        }
+    }
+
     public static ArrayList<Byte> primitiveArrayToArrayList(byte[] arr) {
         ArrayList<Byte> arrayList = new ArrayList<>(arr.length);
         for (byte b : arr) {
@@ -4859,6 +4961,7 @@
         return arrayList;
     }
 
+    /** Convert an ArrayList of Bytes to an exactly-sized primitive array */
     public static byte[] arrayListToPrimitiveArray(ArrayList<Byte> bytes) {
         byte[] ret = new byte[bytes.size()];
         for (int i = 0; i < ret.length; i++) {
diff --git a/src/java/com/android/internal/telephony/RadioIndication.java b/src/java/com/android/internal/telephony/RadioIndication.java
index 88382bc..8d77982 100644
--- a/src/java/com/android/internal/telephony/RadioIndication.java
+++ b/src/java/com/android/internal/telephony/RadioIndication.java
@@ -28,6 +28,7 @@
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_ENTER_EMERGENCY_CALLBACK_MODE;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_EXIT_EMERGENCY_CALLBACK_MODE;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_HARDWARE_CONFIG_CHANGED;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_KEEPALIVE_STATUS;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_LCEDATA_RECV;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_MODEM_RESTART;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_NETWORK_SCAN_RESULT;
@@ -80,7 +81,6 @@
 import android.hardware.radio.V1_0.SsInfoData;
 import android.hardware.radio.V1_0.StkCcUnsolSsResult;
 import android.hardware.radio.V1_0.SuppSvcNotification;
-import android.hardware.radio.V1_1.KeepaliveStatus;
 import android.hardware.radio.V1_2.IRadioIndication;
 import android.hardware.radio.V1_2.PhysicalChannelConfig;
 import android.os.AsyncResult;
@@ -94,6 +94,7 @@
 import com.android.internal.telephony.cdma.CdmaCallWaitingNotification;
 import com.android.internal.telephony.cdma.CdmaInformationRecords;
 import com.android.internal.telephony.cdma.SmsMessageConverter;
+import com.android.internal.telephony.dataconnection.KeepaliveStatus;
 import com.android.internal.telephony.gsm.SsData;
 import com.android.internal.telephony.gsm.SuppServiceNotification;
 import com.android.internal.telephony.nano.TelephonyProto.SmsSession;
@@ -843,10 +844,19 @@
     /**
      * Indicates a change in the status of an ongoing Keepalive session
      * @param indicationType RadioIndicationType
-     * @param keepaliveStatus Status of the ongoing Keepalive session
+     * @param halStatus Status of the ongoing Keepalive session
      */
-    public void keepaliveStatus(int indicationType, KeepaliveStatus keepaliveStatus) {
-        throw new UnsupportedOperationException("keepaliveStatus Indications are not implemented");
+    public void keepaliveStatus(
+            int indicationType, android.hardware.radio.V1_1.KeepaliveStatus halStatus) {
+        mRil.processIndication(indicationType);
+
+        if (RIL.RILJ_LOGD) {
+            mRil.unsljLogRet(RIL_UNSOL_KEEPALIVE_STATUS,
+                    "handle=" + halStatus.sessionHandle + " code=" +  halStatus.code);
+        }
+
+        KeepaliveStatus ks = new KeepaliveStatus(halStatus.sessionHandle, halStatus.code);
+        mRil.mNattKeepaliveStatusRegistrants.notifyRegistrants(new AsyncResult(null, ks, null));
     }
 
     private CommandsInterface.RadioState getRadioStateFromInt(int stateInt) {
diff --git a/src/java/com/android/internal/telephony/RadioResponse.java b/src/java/com/android/internal/telephony/RadioResponse.java
index c140933..d12e35b 100644
--- a/src/java/com/android/internal/telephony/RadioResponse.java
+++ b/src/java/com/android/internal/telephony/RadioResponse.java
@@ -33,7 +33,6 @@
 import android.hardware.radio.V1_0.SendSmsResult;
 import android.hardware.radio.V1_0.SetupDataCallResult;
 import android.hardware.radio.V1_0.VoiceRegStateResult;
-import android.hardware.radio.V1_1.KeepaliveStatus;
 import android.hardware.radio.V1_2.IRadioResponse;
 import android.os.AsyncResult;
 import android.os.Message;
@@ -49,6 +48,7 @@
 import android.telephony.data.DataCallResponse;
 import android.text.TextUtils;
 
+import com.android.internal.telephony.dataconnection.KeepaliveStatus;
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus;
 import com.android.internal.telephony.uicc.IccCardStatus;
@@ -1249,20 +1249,78 @@
         responseVoid(responseInfo);
     }
 
+
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param keepaliveStatus status of the keepalive with a handle for the session
      */
     public void startKeepaliveResponse(RadioResponseInfo responseInfo,
-            KeepaliveStatus keepaliveStatus) {
-        throw new UnsupportedOperationException("startKeepaliveResponse not implemented");
+            android.hardware.radio.V1_1.KeepaliveStatus keepaliveStatus) {
+
+        RILRequest rr = mRil.processResponse(responseInfo);
+
+        if (rr == null) {
+            return;
+        }
+
+        KeepaliveStatus ret = null;
+
+        switch(responseInfo.error) {
+            case RadioError.NONE:
+                int convertedStatus = convertHalKeepaliveStatusCode(keepaliveStatus.code);
+                if (convertedStatus < 0) {
+                    ret = new KeepaliveStatus(KeepaliveStatus.ERROR_UNSUPPORTED);
+                } else {
+                    ret = new KeepaliveStatus(keepaliveStatus.sessionHandle, convertedStatus);
+                }
+                break;
+            case RadioError.REQUEST_NOT_SUPPORTED:
+                ret = new KeepaliveStatus(KeepaliveStatus.ERROR_UNSUPPORTED);
+                // The request is unsupported, which is ok. We'll report it to the higher
+                // layer and treat it as acceptable in the RIL.
+                responseInfo.error = RadioError.NONE;
+                break;
+            case RadioError.NO_RESOURCES:
+                ret = new KeepaliveStatus(KeepaliveStatus.ERROR_NO_RESOURCES);
+                break;
+            default:
+                ret = new KeepaliveStatus(KeepaliveStatus.ERROR_UNKNOWN);
+                break;
+        }
+        sendMessageResponse(rr.mResult, ret);
+        mRil.processResponseDone(rr, responseInfo, ret);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void stopKeepaliveResponse(RadioResponseInfo responseInfo) {
-        throw new UnsupportedOperationException("stopKeepaliveResponse not implemented");
+        RILRequest rr = mRil.processResponse(responseInfo);
+
+        if (rr == null) {
+            return;
+        }
+
+        if (responseInfo.error == RadioError.NONE) {
+            sendMessageResponse(rr.mResult, null);
+            mRil.processResponseDone(rr, responseInfo, null);
+        } else {
+            //TODO: Error code translation
+        }
+    }
+
+    private int convertHalKeepaliveStatusCode(int halCode) {
+        switch (halCode) {
+            case android.hardware.radio.V1_1.KeepaliveStatusCode.ACTIVE:
+                return KeepaliveStatus.STATUS_ACTIVE;
+            case android.hardware.radio.V1_1.KeepaliveStatusCode.INACTIVE:
+                return KeepaliveStatus.STATUS_INACTIVE;
+            case android.hardware.radio.V1_1.KeepaliveStatusCode.PENDING:
+                return KeepaliveStatus.STATUS_PENDING;
+            default:
+                mRil.riljLog("Invalid Keepalive Status" + halCode);
+                return -1;
+        }
     }
 
     private IccCardStatus convertHalCardStatus(CardStatus cardStatus) {
diff --git a/src/java/com/android/internal/telephony/TransportManager.java b/src/java/com/android/internal/telephony/TransportManager.java
new file mode 100644
index 0000000..d81130a
--- /dev/null
+++ b/src/java/com/android/internal/telephony/TransportManager.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.dataconnection;
+
+import android.telephony.AccessNetworkConstants.TransportType;
+import android.telephony.Rlog;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class represents the transport manager which manages available transports and
+ * route requests to correct transport.
+ */
+public class TransportManager {
+    private static final String TAG = TransportManager.class.getSimpleName();
+
+    private List<Integer> mAvailableTransports = new ArrayList<>();
+
+    public TransportManager() {
+        // TODO: get transpot list from AccessNetworkManager.
+        mAvailableTransports.add(TransportType.WWAN);
+    }
+
+    public List<Integer> getAvailableTransports() {
+        return new ArrayList<>(mAvailableTransports);
+    }
+
+    private void log(String s) {
+        Rlog.d(TAG, s);
+    }
+
+    private void loge(String s) {
+        Rlog.e(TAG, s);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
index c7aa0b3..0cc846f 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
@@ -22,6 +22,8 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.PacketKeepalive;
+import android.net.KeepalivePacketData;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.NetworkAgent;
@@ -45,6 +47,7 @@
 import android.text.TextUtils;
 import android.util.LocalLog;
 import android.util.Pair;
+import android.util.SparseArray;
 import android.util.TimeUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -179,7 +182,7 @@
     private int mRilRat = Integer.MAX_VALUE;
     private int mDataRegState = Integer.MAX_VALUE;
     private NetworkInfo mNetworkInfo;
-    private NetworkAgent mNetworkAgent;
+    private DcNetworkAgent mNetworkAgent;
     private LocalLog mNetCapsLocalLog = new LocalLog(50);
 
     int mTag;
@@ -207,9 +210,14 @@
     static final int EVENT_DATA_CONNECTION_VOICE_CALL_STARTED = BASE + 15;
     static final int EVENT_DATA_CONNECTION_VOICE_CALL_ENDED = BASE + 16;
     static final int EVENT_DATA_CONNECTION_OVERRIDE_CHANGED = BASE + 17;
+    static final int EVENT_KEEPALIVE_STATUS = BASE + 18;
+    static final int EVENT_KEEPALIVE_STARTED = BASE + 19;
+    static final int EVENT_KEEPALIVE_STOPPED = BASE + 20;
+    static final int EVENT_KEEPALIVE_START_REQUEST = BASE + 21;
+    static final int EVENT_KEEPALIVE_STOP_REQUEST = BASE + 22;
 
     private static final int CMD_TO_STRING_COUNT =
-            EVENT_DATA_CONNECTION_OVERRIDE_CHANGED - BASE + 1;
+            EVENT_KEEPALIVE_STOP_REQUEST - BASE + 1;
 
     private static String[] sCmdToString = new String[CMD_TO_STRING_COUNT];
     static {
@@ -235,6 +243,11 @@
                 "EVENT_DATA_CONNECTION_VOICE_CALL_ENDED";
         sCmdToString[EVENT_DATA_CONNECTION_OVERRIDE_CHANGED - BASE] =
                 "EVENT_DATA_CONNECTION_OVERRIDE_CHANGED";
+        sCmdToString[EVENT_KEEPALIVE_STATUS - BASE] = "EVENT_KEEPALIVE_STATUS";
+        sCmdToString[EVENT_KEEPALIVE_STARTED - BASE] = "EVENT_KEEPALIVE_STARTED";
+        sCmdToString[EVENT_KEEPALIVE_STOPPED - BASE] = "EVENT_KEEPALIVE_STOPPED";
+        sCmdToString[EVENT_KEEPALIVE_START_REQUEST - BASE] = "EVENT_KEEPALIVE_START_REQUEST";
+        sCmdToString[EVENT_KEEPALIVE_STOP_REQUEST - BASE] = "EVENT_KEEPALIVE_STOP_REQUEST";
     }
     // Convert cmd to string or null if unknown
     static String cmdToString(int cmd) {
@@ -1362,6 +1375,14 @@
                         mNetworkAgent.sendNetworkInfo(mNetworkInfo);
                     }
                     break;
+                case EVENT_KEEPALIVE_START_REQUEST:
+                case EVENT_KEEPALIVE_STOP_REQUEST:
+                    if (mNetworkAgent != null) {
+                        mNetworkAgent.onPacketKeepaliveEvent(
+                                msg.arg1,
+                                ConnectivityManager.PacketKeepalive.ERROR_INVALID_NETWORK);
+                    }
+                    break;
                 default:
                     if (DBG) {
                         log("DcDefaultState: shouldn't happen but ignore msg.what="
@@ -1672,6 +1693,7 @@
      * The state machine is connected, expecting an EVENT_DISCONNECT.
      */
     private class DcActiveState extends State {
+
         @Override public void enter() {
             if (DBG) log("DcActiveState: enter dc=" + DataConnection.this);
 
@@ -1707,6 +1729,8 @@
             mNetworkAgent = new DcNetworkAgent(getHandler().getLooper(), mPhone.getContext(),
                     "DcNetworkAgent", mNetworkInfo, getNetworkCapabilities(), mLinkProperties,
                     50, misc);
+            mPhone.mCi.registerForNattKeepaliveStatus(
+                    getHandler(), DataConnection.EVENT_KEEPALIVE_STATUS, null);
         }
 
         @Override
@@ -1725,6 +1749,7 @@
 
             mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED,
                     reason, mNetworkInfo.getExtraInfo());
+            mPhone.mCi.unregisterForNattKeepaliveStatus(getHandler());
             if (mNetworkAgent != null) {
                 mNetworkAgent.sendNetworkInfo(mNetworkInfo);
                 mNetworkAgent = null;
@@ -1843,6 +1868,85 @@
                     retVal = HANDLED;
                     break;
                 }
+                case EVENT_KEEPALIVE_START_REQUEST: {
+                    KeepalivePacketData pkt = (KeepalivePacketData) msg.obj;
+                    int slotId = msg.arg1;
+                    int intervalMillis = msg.arg2 * 1000;
+                    mPhone.mCi.startNattKeepalive(
+                            DataConnection.this.mCid, pkt, intervalMillis,
+                            DataConnection.this.obtainMessage(
+                                    EVENT_KEEPALIVE_STARTED, slotId, 0, null));
+                    retVal = HANDLED;
+                    break;
+                }
+                case EVENT_KEEPALIVE_STOP_REQUEST: {
+                    int slotId = msg.arg1;
+                    int handle = mNetworkAgent.keepaliveTracker.getHandleForSlot(slotId);
+                    if (handle < 0) {
+                        loge("No slot found for stopPacketKeepalive! " + slotId);
+                        retVal = HANDLED;
+                        break;
+                    } else {
+                        logd("Stopping keepalive with handle: " + handle);
+                    }
+
+                    mPhone.mCi.stopNattKeepalive(
+                            handle, DataConnection.this.obtainMessage(
+                                    EVENT_KEEPALIVE_STOPPED, handle, slotId, null));
+                    retVal = HANDLED;
+                    break;
+                }
+                case EVENT_KEEPALIVE_STARTED: {
+                    AsyncResult ar = (AsyncResult) msg.obj;
+                    final int slot = msg.arg1;
+                    if (ar.exception != null || ar.result == null) {
+                        loge("EVENT_KEEPALIVE_STARTED: error starting keepalive, e="
+                                + ar.exception);
+                        mNetworkAgent.onPacketKeepaliveEvent(
+                                slot, ConnectivityManager.PacketKeepalive.ERROR_HARDWARE_ERROR);
+                    } else {
+                        KeepaliveStatus ks = (KeepaliveStatus) ar.result;
+                        if (ks == null) {
+                            loge("Null KeepaliveStatus received!");
+                        } else {
+                            mNetworkAgent.keepaliveTracker.handleKeepaliveStarted(slot, ks);
+                        }
+                    }
+                    retVal = HANDLED;
+                    break;
+                }
+                case EVENT_KEEPALIVE_STATUS: {
+                    AsyncResult ar = (AsyncResult) msg.obj;
+                    if (ar.exception != null) {
+                        loge("EVENT_KEEPALIVE_STATUS: error in keepalive, e=" + ar.exception);
+                        // We have no way to notify connectivity in this case.
+                    }
+                    if (ar.result != null) {
+                        KeepaliveStatus ks = (KeepaliveStatus) ar.result;
+                        mNetworkAgent.keepaliveTracker.handleKeepaliveStatus(ks);
+                    }
+
+                    retVal = HANDLED;
+                    break;
+                }
+                case EVENT_KEEPALIVE_STOPPED: {
+                    AsyncResult ar = (AsyncResult) msg.obj;
+                    final int handle = msg.arg1;
+                    final int slotId = msg.arg2;
+
+                    if (ar.exception != null) {
+                        loge("EVENT_KEEPALIVE_STOPPED: error stopping keepalive for handle="
+                                + handle + " e=" + ar.exception);
+                        mNetworkAgent.keepaliveTracker.handleKeepaliveStatus(
+                                new KeepaliveStatus(KeepaliveStatus.ERROR_UNKNOWN));
+                    } else {
+                        log("Keepalive Stop Requested for handle=" + handle);
+                        mNetworkAgent.keepaliveTracker.handleKeepaliveStatus(
+                                new KeepaliveStatus(handle, KeepaliveStatus.STATUS_INACTIVE));
+                    }
+                    retVal = HANDLED;
+                    break;
+                }
                 default:
                     if (VDBG) {
                         log("DcActiveState not handled msg.what=" + getWhatToString(msg.what));
@@ -1956,11 +2060,15 @@
 
         private NetworkCapabilities mNetworkCapabilities;
 
+        public final DcKeepaliveTracker keepaliveTracker = new DcKeepaliveTracker();
+
         public DcNetworkAgent(Looper l, Context c, String TAG, NetworkInfo ni,
                 NetworkCapabilities nc, LinkProperties lp, int score, NetworkMisc misc) {
             super(l, c, TAG, ni, nc, lp, score, misc);
             mNetCapsLocalLog.log("New network agent created. capabilities=" + nc);
             mNetworkCapabilities = nc;
+            mPhone.mCi.registerForNattKeepaliveStatus(
+                    getHandler(), EVENT_KEEPALIVE_STATUS, null);
         }
 
         @Override
@@ -2018,6 +2126,137 @@
             }
             super.sendNetworkCapabilities(networkCapabilities);
         }
+
+        @Override
+        protected void startPacketKeepalive(Message msg) {
+            DataConnection.this.obtainMessage(EVENT_KEEPALIVE_START_REQUEST,
+                    msg.arg1, msg.arg2, msg.obj).sendToTarget();
+        }
+
+        @Override
+        protected void stopPacketKeepalive(Message msg) {
+            DataConnection.this.obtainMessage(EVENT_KEEPALIVE_STOP_REQUEST,
+                    msg.arg1, msg.arg2, msg.obj).sendToTarget();
+        }
+
+        private class DcKeepaliveTracker {
+            private class KeepaliveRecord {
+                public int slotId;
+                public int currentStatus;
+
+                KeepaliveRecord(int slotId, int status) {
+                    this.slotId = slotId;
+                    this.currentStatus = status;
+                }
+            };
+
+            private final SparseArray<KeepaliveRecord> mKeepalives = new SparseArray();
+
+            int getHandleForSlot(int slotId) {
+                for (int i = 0; i < mKeepalives.size(); i++) {
+                    KeepaliveRecord kr = mKeepalives.valueAt(i);
+                    if (kr.slotId == slotId) return mKeepalives.keyAt(i);
+                }
+                return -1;
+            }
+
+            int keepaliveStatusErrorToPacketKeepaliveError(int error) {
+                switch(error) {
+                    case KeepaliveStatus.ERROR_NONE:
+                        return PacketKeepalive.SUCCESS;
+                    case KeepaliveStatus.ERROR_UNSUPPORTED:
+                        return PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED;
+                    case KeepaliveStatus.ERROR_NO_RESOURCES:
+                    case KeepaliveStatus.ERROR_UNKNOWN:
+                    default:
+                        return PacketKeepalive.ERROR_HARDWARE_ERROR;
+                }
+            }
+
+            void handleKeepaliveStarted(final int slot, KeepaliveStatus ks) {
+                switch (ks.statusCode) {
+                    case KeepaliveStatus.STATUS_INACTIVE:
+                        DcNetworkAgent.this.onPacketKeepaliveEvent(slot,
+                                keepaliveStatusErrorToPacketKeepaliveError(ks.errorCode));
+                        break;
+                    case KeepaliveStatus.STATUS_ACTIVE:
+                        DcNetworkAgent.this.onPacketKeepaliveEvent(
+                                slot, PacketKeepalive.SUCCESS);
+                        // fall through to add record
+                    case KeepaliveStatus.STATUS_PENDING:
+                        log("Adding keepalive handle="
+                                + ks.sessionHandle + " slot = " + slot);
+                        mKeepalives.put(ks.sessionHandle,
+                                new KeepaliveRecord(
+                                        slot, ks.statusCode));
+                        break;
+                    default:
+                        loge("Invalid KeepaliveStatus Code: " + ks.statusCode);
+                        break;
+                }
+            }
+
+            void handleKeepaliveStatus(KeepaliveStatus ks) {
+                final KeepaliveRecord kr;
+                kr = mKeepalives.get(ks.sessionHandle);
+
+                if (kr == null) {
+                    // If there is no slot for the session handle, we received an event
+                    // for a different data connection. This is not an error because the
+                    // keepalive session events are broadcast to all listeners.
+                    log("Discarding keepalive event for different data connection:" + ks);
+                    return;
+                }
+                // Switch on the current state, to see what we do with the status update
+                switch (kr.currentStatus) {
+                    case KeepaliveStatus.STATUS_INACTIVE:
+                        loge("Inactive Keepalive received status!");
+                        DcNetworkAgent.this.onPacketKeepaliveEvent(
+                                kr.slotId, PacketKeepalive.ERROR_HARDWARE_ERROR);
+                        break;
+                    case KeepaliveStatus.STATUS_PENDING:
+                        switch (ks.statusCode) {
+                            case KeepaliveStatus.STATUS_INACTIVE:
+                                DcNetworkAgent.this.onPacketKeepaliveEvent(kr.slotId,
+                                        keepaliveStatusErrorToPacketKeepaliveError(ks.errorCode));
+                                kr.currentStatus = KeepaliveStatus.STATUS_INACTIVE;
+                                mKeepalives.remove(ks.sessionHandle);
+                                break;
+                            case KeepaliveStatus.STATUS_ACTIVE:
+                                log("Pending Keepalive received active status!");
+                                kr.currentStatus = KeepaliveStatus.STATUS_ACTIVE;
+                                DcNetworkAgent.this.onPacketKeepaliveEvent(
+                                        kr.slotId, PacketKeepalive.SUCCESS);
+                                break;
+                            case KeepaliveStatus.STATUS_PENDING:
+                                loge("Invalid unsolicied Keepalive Pending Status!");
+                                break;
+                            default:
+                                loge("Invalid Keepalive Status received, " + ks.statusCode);
+                        }
+                        break;
+                    case KeepaliveStatus.STATUS_ACTIVE:
+                        switch (ks.statusCode) {
+                            case KeepaliveStatus.STATUS_INACTIVE:
+                                loge("Keepalive received stopped status!");
+                                DcNetworkAgent.this.onPacketKeepaliveEvent(
+                                        kr.slotId, PacketKeepalive.SUCCESS);
+                                kr.currentStatus = KeepaliveStatus.STATUS_INACTIVE;
+                                mKeepalives.remove(ks.sessionHandle);
+                                break;
+                            case KeepaliveStatus.STATUS_PENDING:
+                            case KeepaliveStatus.STATUS_ACTIVE:
+                                loge("Active Keepalive received invalid status!");
+                                break;
+                            default:
+                                loge("Invalid Keepalive Status received, " + ks.statusCode);
+                        }
+                        break;
+                    default:
+                        loge("Invalid Keepalive Status received, " + kr.currentStatus);
+                }
+            }
+        };
     }
 
     // ******* "public" interface
diff --git a/src/java/com/android/internal/telephony/dataconnection/KeepaliveStatus.java b/src/java/com/android/internal/telephony/dataconnection/KeepaliveStatus.java
new file mode 100644
index 0000000..8b0ec4f
--- /dev/null
+++ b/src/java/com/android/internal/telephony/dataconnection/KeepaliveStatus.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.dataconnection;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class serves to pass around the parameters of Keepalive session
+ * status within the telephony framework.
+ *
+ * {@hide}
+ */
+public class KeepaliveStatus implements Parcelable {
+    private static final String LOG_TAG = "KeepaliveStatus";
+
+    /** This should match the HAL Radio::1_1::KeepaliveStatusCode */
+    public static final int STATUS_ACTIVE = 0;
+    public static final int STATUS_INACTIVE = 1;
+    public static final int STATUS_PENDING = 2;
+
+    public static final int ERROR_NONE = 0;
+    public static final int ERROR_UNSUPPORTED = 1;
+    public static final int ERROR_NO_RESOURCES = 2;
+    public static final int ERROR_UNKNOWN = 3;
+
+    public static final int INVALID_HANDLE = Integer.MAX_VALUE;
+
+    /** An opaque value that identifies this Keepalive status to the modem */
+    public final int sessionHandle;
+
+    /**
+     * A status code indicating whether this Keepalive session is
+     * active, inactive, or pending activation
+     */
+    public final int statusCode;
+
+    /** An error code indicating a lower layer failure, if any */
+    public final int errorCode;
+
+    public KeepaliveStatus(int error) {
+        sessionHandle = INVALID_HANDLE;
+        statusCode = STATUS_INACTIVE;
+        errorCode = error;
+    }
+
+    public KeepaliveStatus(int handle, int code) {
+        sessionHandle = handle;
+        statusCode = code;
+        errorCode = ERROR_NONE;
+    }
+
+
+    @Override
+    public String toString() {
+        return String.format("{errorCode=%d, sessionHandle=%d, statusCode=%d}",
+                errorCode, sessionHandle, statusCode);
+    }
+
+    // Parcelable Implementation
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(errorCode);
+        dest.writeInt(sessionHandle);
+        dest.writeInt(statusCode);
+    }
+
+    private KeepaliveStatus(Parcel p) {
+        errorCode = p.readInt();
+        sessionHandle = p.readInt();
+        statusCode = p.readInt();
+    }
+
+    public static final Parcelable.Creator<KeepaliveStatus> CREATOR =
+            new Parcelable.Creator<KeepaliveStatus>() {
+                @Override
+                public KeepaliveStatus createFromParcel(Parcel source) {
+                    return new KeepaliveStatus(source);
+                }
+
+                @Override
+                public KeepaliveStatus[] newArray(int size) {
+                    return new KeepaliveStatus[size];
+                }
+            };
+}
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
index 4b1e2a0..770fbb1 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony.imsphone;
 
 import android.content.Context;
+import android.net.KeepalivePacketData;
 import android.os.Handler;
 import android.os.Message;
 import android.service.carrier.CarrierIdentifier;
@@ -644,4 +645,13 @@
     @Override
     public void setSimCardPower(int state, Message result) {
     }
+
+    @Override
+    public void startNattKeepalive(
+            int contextId, KeepalivePacketData packetData, int intervalMillis, Message result) {
+    }
+
+    @Override
+    public void stopNattKeepalive(int sessionHandle, Message result) {
+    }
 }
diff --git a/src/java/com/android/internal/telephony/sip/SipCommandInterface.java b/src/java/com/android/internal/telephony/sip/SipCommandInterface.java
index 2bbc65c..9fd22ed 100644
--- a/src/java/com/android/internal/telephony/sip/SipCommandInterface.java
+++ b/src/java/com/android/internal/telephony/sip/SipCommandInterface.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony.sip;
 
 import android.content.Context;
+import android.net.KeepalivePacketData;
 import android.os.Handler;
 import android.os.Message;
 import android.service.carrier.CarrierIdentifier;
@@ -646,4 +647,13 @@
     @Override
     public void setSimCardPower(int state, Message result) {
     }
+
+    @Override
+    public void startNattKeepalive(
+            int contextId, KeepalivePacketData packetData, int intervalMillis, Message result) {
+    }
+
+    @Override
+    public void stopNattKeepalive(int sessionHandle, Message result) {
+    }
 }
diff --git a/src/java/com/android/internal/telephony/test/SimulatedCommands.java b/src/java/com/android/internal/telephony/test/SimulatedCommands.java
index 44b7745..277fbd6 100644
--- a/src/java/com/android/internal/telephony/test/SimulatedCommands.java
+++ b/src/java/com/android/internal/telephony/test/SimulatedCommands.java
@@ -18,6 +18,7 @@
 
 import android.hardware.radio.V1_0.DataRegStateResult;
 import android.hardware.radio.V1_0.VoiceRegStateResult;
+import android.net.KeepalivePacketData;
 import android.net.LinkAddress;
 import android.net.NetworkUtils;
 import android.os.AsyncResult;
@@ -2200,4 +2201,13 @@
         super.unregisterForIccRefresh(h);
         SimulatedCommandsVerifier.getInstance().unregisterForIccRefresh(h);
     }
+
+    @Override
+    public void startNattKeepalive(
+            int contextId, KeepalivePacketData packetData, int intervalMillis, Message result) {
+    }
+
+    @Override
+    public void stopNattKeepalive(int sessionHandle, Message result) {
+    }
 }
diff --git a/src/java/com/android/internal/telephony/test/SimulatedCommandsVerifier.java b/src/java/com/android/internal/telephony/test/SimulatedCommandsVerifier.java
index fb69333..4a3939b 100644
--- a/src/java/com/android/internal/telephony/test/SimulatedCommandsVerifier.java
+++ b/src/java/com/android/internal/telephony/test/SimulatedCommandsVerifier.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony.test;
 
+import android.net.KeepalivePacketData;
 import android.os.Handler;
 import android.os.Message;
 import android.service.carrier.CarrierIdentifier;
@@ -1401,4 +1402,21 @@
     @Override
     public void unregisterForCarrierInfoForImsiEncryption(Handler h) {
     }
+
+    @Override
+    public void registerForNattKeepaliveStatus(Handler h, int what, Object obj) {
+    }
+
+    @Override
+    public void unregisterForNattKeepaliveStatus(Handler h) {
+    }
+
+    @Override
+    public void startNattKeepalive(
+            int contextId, KeepalivePacketData packetData, int intervalMillis, Message result) {
+    }
+
+    @Override
+    public void stopNattKeepalive(int sessionHandle, Message result)  {
+    }
 }
diff --git a/src/java/com/android/internal/telephony/uicc/UiccProfile.java b/src/java/com/android/internal/telephony/uicc/UiccProfile.java
index 7e59e9f..a425d9e 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccProfile.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccProfile.java
@@ -222,56 +222,15 @@
             return;
         }
         switch (msg.what) {
-            case EVENT_RADIO_OFF_OR_UNAVAILABLE:
-                updateExternalState();
-                break;
-
-            case EVENT_ICC_LOCKED:
-                processLockedState();
-                break;
-
-            case EVENT_APP_READY:
-                if (VDBG) log("EVENT_APP_READY");
-                if (areAllApplicationsReady()) {
-                    if (areAllRecordsLoaded() && areCarrierPriviligeRulesLoaded()) {
-                        setExternalState(IccCardConstants.State.LOADED);
-                    } else {
-                        setExternalState(IccCardConstants.State.READY);
-                    }
-                }
-                break;
-
-            case EVENT_RECORDS_LOADED:
-                if (VDBG) log("EVENT_RECORDS_LOADED");
-                if (!areAllRecordsLoaded()) {
-                    break;
-                }
-                // Update the MCC/MNC.
-                if (mIccRecords != null) {
-                    String operator = mIccRecords.getOperatorNumeric();
-                    log("operator=" + operator + " mPhoneId=" + mPhoneId);
-
-                    if (!TextUtils.isEmpty(operator)) {
-                        mTelephonyManager.setSimOperatorNumericForPhone(mPhoneId, operator);
-                        String countryCode = operator.substring(0, 3);
-                        if (countryCode != null) {
-                            mTelephonyManager.setSimCountryIsoForPhone(mPhoneId,
-                                    MccTable.countryCodeForMcc(Integer.parseInt(countryCode)));
-                        } else {
-                            loge("EVENT_RECORDS_LOADED Country code is null");
-                        }
-                    } else {
-                        loge("EVENT_RECORDS_LOADED Operator name is null");
-                    }
-                }
-                if (areCarrierPriviligeRulesLoaded()) {
-                    setExternalState(IccCardConstants.State.LOADED);
-                }
-                break;
-
             case EVENT_NETWORK_LOCKED:
                 mNetworkLockedRegistrants.notifyRegistrants();
-                setExternalState(IccCardConstants.State.NETWORK_LOCKED);
+                // intentional fall through
+            case EVENT_RADIO_OFF_OR_UNAVAILABLE:
+            case EVENT_ICC_LOCKED:
+            case EVENT_APP_READY:
+            case EVENT_RECORDS_LOADED:
+                if (VDBG) log("handleMessage: Received " + msg.what);
+                updateExternalState();
                 break;
 
             case EVENT_ICC_RECORD_EVENTS:
@@ -288,9 +247,7 @@
             case EVENT_CARRIER_PRIVILEGES_LOADED:
                 if (VDBG) log("EVENT_CARRIER_PRIVILEGES_LOADED");
                 onCarrierPriviligesLoadedMessage();
-                if (areAllRecordsLoaded()) {
-                    setExternalState(IccCardConstants.State.LOADED);
-                }
+                updateExternalState();
                 break;
 
             case EVENT_OPEN_LOGICAL_CHANNEL_DONE:
@@ -348,7 +305,7 @@
     }
 
     private void updateExternalState() {
-
+        // First check if card state is IO_ERROR or RESTRICTED
         if (mUiccCard.getCardState() == IccCardStatus.CardState.CARDSTATE_ERROR) {
             setExternalState(IccCardConstants.State.CARD_IO_ERROR);
             return;
@@ -359,13 +316,65 @@
             return;
         }
 
-        if (mUiccApplication == null || !areAllApplicationsReady()) {
+        // By process of elimination, the UICC Card State = PRESENT and state needs to be decided
+        // based on apps
+        if (mUiccApplication == null) {
+            loge("updateExternalState: setting state to NOT_READY because mUiccApplication is "
+                    + "null");
             setExternalState(IccCardConstants.State.NOT_READY);
             return;
         }
 
-        // By process of elimination, the UICC Card State = PRESENT
-        switch (mUiccApplication.getState()) {
+        // Check if SIM is locked
+        boolean cardLocked = false;
+        IccCardConstants.State lockedState = null;
+        IccCardApplicationStatus.AppState appState = mUiccApplication.getState();
+
+        PinState pin1State = mUiccApplication.getPin1State();
+        if (pin1State == PinState.PINSTATE_ENABLED_PERM_BLOCKED) {
+            if (VDBG) log("updateExternalState: PERM_DISABLED");
+            cardLocked = true;
+            lockedState = IccCardConstants.State.PERM_DISABLED;
+        } else {
+            if (appState == IccCardApplicationStatus.AppState.APPSTATE_PIN) {
+                if (VDBG) log("updateExternalState: PIN_REQUIRED");
+                cardLocked = true;
+                lockedState = IccCardConstants.State.PIN_REQUIRED;
+            } else if (appState == IccCardApplicationStatus.AppState.APPSTATE_PUK) {
+                if (VDBG) log("updateExternalState: PUK_REQUIRED");
+                cardLocked = true;
+                lockedState = IccCardConstants.State.PUK_REQUIRED;
+            } else if (appState == IccCardApplicationStatus.AppState.APPSTATE_SUBSCRIPTION_PERSO) {
+                if (mUiccApplication.getPersoSubState()
+                        == IccCardApplicationStatus.PersoSubState.PERSOSUBSTATE_SIM_NETWORK) {
+                    if (VDBG) log("updateExternalState: PERSOSUBSTATE_SIM_NETWORK");
+                    cardLocked = true;
+                    lockedState = IccCardConstants.State.NETWORK_LOCKED;
+                }
+            }
+        }
+
+        // If SIM is locked, broadcast state as NOT_READY/LOCKED depending on if records are loaded
+        if (cardLocked) {
+            if (mIccRecords != null && (mIccRecords.getLockedRecordsLoaded()
+                    || mIccRecords.getNetworkLockedRecordsLoaded())) { // locked records loaded
+                if (VDBG) {
+                    log("updateExternalState: card locked and records loaded; "
+                            + "setting state to locked");
+                }
+                setExternalState(lockedState);
+            } else {
+                if (VDBG) {
+                    log("updateExternalState: card locked but records not loaded; "
+                            + "setting state to NOT_READY");
+                }
+                setExternalState(IccCardConstants.State.NOT_READY);
+            }
+            return;
+        }
+
+        // Check for remaining app states
+        switch (appState) {
             case APPSTATE_UNKNOWN:
                 /*
                  * APPSTATE_UNKNOWN is a catch-all state reported whenever the app
@@ -377,23 +386,29 @@
                  * 2) There is no valid App on the SIM to load, which can be the case with an
                  *    eSIM/soft SIM.
                  */
-                setExternalState(IccCardConstants.State.NOT_READY);
-                break;
-            case APPSTATE_SUBSCRIPTION_PERSO:
-                if (mUiccApplication.getPersoSubState()
-                        == IccCardApplicationStatus.PersoSubState.PERSOSUBSTATE_SIM_NETWORK) {
-                    setExternalState(IccCardConstants.State.NETWORK_LOCKED);
+                if (VDBG) {
+                    log("updateExternalState: app state is unknown; setting state to NOT_READY");
                 }
-                // Otherwise don't change external SIM state.
+                setExternalState(IccCardConstants.State.NOT_READY);
                 break;
             case APPSTATE_READY:
                 if (areAllApplicationsReady()) {
                     if (areAllRecordsLoaded() && areCarrierPriviligeRulesLoaded()) {
+                        if (VDBG) log("updateExternalState: setting state to LOADED");
                         setExternalState(IccCardConstants.State.LOADED);
                     } else {
+                        if (VDBG) {
+                            log("updateExternalState: setting state to READY; records loaded "
+                                    + areAllRecordsLoaded() + ", carrier privilige rules loaded "
+                                    + areCarrierPriviligeRulesLoaded());
+                        }
                         setExternalState(IccCardConstants.State.READY);
                     }
                 } else {
+                    if (VDBG) {
+                        log("updateExternalState: app state is READY but not for all apps; "
+                                + "setting state to NOT_READY");
+                    }
                     setExternalState(IccCardConstants.State.NOT_READY);
                 }
                 break;
@@ -444,19 +459,17 @@
         }
     }
 
-    private void broadcastInternalIccStateChangedIntent(String value, String reason) {
-        synchronized (mLock) {
-            Intent intent = new Intent(ACTION_INTERNAL_SIM_STATE_CHANGED);
-            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
-                    | Intent.FLAG_RECEIVER_FOREGROUND);
-            intent.putExtra(PhoneConstants.PHONE_NAME_KEY, "Phone");
-            intent.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE, value);
-            intent.putExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON, reason);
-            intent.putExtra(PhoneConstants.PHONE_KEY, mPhoneId);  // SubId may not be valid.
-            log("Sending intent ACTION_INTERNAL_SIM_STATE_CHANGED value=" + value
-                    + " for mPhoneId : " + mPhoneId);
-            ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL);
-        }
+    static void broadcastInternalIccStateChangedIntent(String value, String reason, int phoneId) {
+        Intent intent = new Intent(ACTION_INTERNAL_SIM_STATE_CHANGED);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+                | Intent.FLAG_RECEIVER_FOREGROUND);
+        intent.putExtra(PhoneConstants.PHONE_NAME_KEY, "Phone");
+        intent.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE, value);
+        intent.putExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON, reason);
+        intent.putExtra(PhoneConstants.PHONE_KEY, phoneId);  // SubId may not be valid.
+        log("Sending intent ACTION_INTERNAL_SIM_STATE_CHANGED value=" + value
+                + " for mPhoneId : " + phoneId);
+        ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL);
     }
 
     private void setExternalState(IccCardConstants.State newState, boolean override) {
@@ -471,41 +484,31 @@
                 return;
             }
             mExternalState = newState;
+            if (mExternalState == IccCardConstants.State.LOADED) {
+                // Update the MCC/MNC.
+                if (mIccRecords != null) {
+                    String operator = mIccRecords.getOperatorNumeric();
+                    log("operator=" + operator + " mPhoneId=" + mPhoneId);
+
+                    if (!TextUtils.isEmpty(operator)) {
+                        mTelephonyManager.setSimOperatorNumericForPhone(mPhoneId, operator);
+                        String countryCode = operator.substring(0, 3);
+                        if (countryCode != null) {
+                            mTelephonyManager.setSimCountryIsoForPhone(mPhoneId,
+                                    MccTable.countryCodeForMcc(Integer.parseInt(countryCode)));
+                        } else {
+                            loge("EVENT_RECORDS_LOADED Country code is null");
+                        }
+                    } else {
+                        loge("EVENT_RECORDS_LOADED Operator name is null");
+                    }
+                }
+            }
             log("setExternalState: set mPhoneId=" + mPhoneId + " mExternalState=" + mExternalState);
             mTelephonyManager.setSimStateForPhone(mPhoneId, getState().toString());
 
             broadcastInternalIccStateChangedIntent(getIccStateIntentString(mExternalState),
-                    getIccStateReason(mExternalState));
-        }
-    }
-
-    private void processLockedState() {
-        synchronized (mLock) {
-            if (mUiccApplication == null) {
-                //Don't need to do anything if non-existent application is locked
-                return;
-            }
-            PinState pin1State = mUiccApplication.getPin1State();
-            if (pin1State == PinState.PINSTATE_ENABLED_PERM_BLOCKED) {
-                setExternalState(IccCardConstants.State.PERM_DISABLED);
-                return;
-            }
-
-            IccCardApplicationStatus.AppState appState = mUiccApplication.getState();
-            switch (appState) {
-                case APPSTATE_PIN:
-                    setExternalState(IccCardConstants.State.PIN_REQUIRED);
-                    break;
-                case APPSTATE_PUK:
-                    setExternalState(IccCardConstants.State.PUK_REQUIRED);
-                    break;
-                case APPSTATE_DETECTED:
-                case APPSTATE_READY:
-                case APPSTATE_SUBSCRIPTION_PERSO:
-                case APPSTATE_UNKNOWN:
-                    // Neither required
-                    break;
-            }
+                    getIccStateReason(mExternalState), mPhoneId);
         }
     }
 
@@ -1361,7 +1364,7 @@
         return null;
     }
 
-    private void log(String msg) {
+    private static void log(String msg) {
         Rlog.d(LOG_TAG, msg);
     }
 
diff --git a/src/java/com/android/internal/telephony/uicc/UiccSlot.java b/src/java/com/android/internal/telephony/uicc/UiccSlot.java
index 9319b4b..4fabf0b 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccSlot.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccSlot.java
@@ -35,6 +35,7 @@
 import com.android.internal.R;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.CommandsInterface.RadioState;
+import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.telephony.uicc.IccCardStatus.CardState;
 
 import java.io.FileDescriptor;
@@ -59,7 +60,9 @@
     private RadioState mLastRadioState = RadioState.RADIO_UNAVAILABLE;
     private boolean mIsEuicc;
     private String mIccId;
+    private Integer mPhoneId = null;
 
+    // todo: remove if this is not needed
     private RegistrantList mAbsentRegistrants = new RegistrantList();
 
     private static final int EVENT_CARD_REMOVED = 13;
@@ -81,6 +84,7 @@
         synchronized (mLock) {
             CardState oldState = mCardState;
             mCardState = ics.mCardState;
+            mPhoneId = phoneId;
             parseAtr(ics.atr);
             mCi = ci;
 
@@ -98,7 +102,8 @@
                     sendMessage(obtainMessage(EVENT_CARD_REMOVED, null));
                 }
 
-                // todo: broadcast sim state changed for absent/unknown when IccCardProxy is removed
+                UiccProfile.broadcastInternalIccStateChangedIntent(
+                        IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, mPhoneId);
 
                 // no card present in the slot now; dispose card and make mUiccCard null
                 mUiccCard.dispose();
@@ -118,7 +123,7 @@
                 }
 
                 if (!mIsEuicc) {
-                    mUiccCard = new UiccCard(mContext, mCi, ics, phoneId);
+                    mUiccCard = new UiccCard(mContext, mCi, ics, mPhoneId);
                 } else {
                     // todo: initialize new EuiccCard object here
                     //mUiccCard = new EuiccCard();
@@ -144,6 +149,9 @@
                     mActive = false;
                     // treat as radio state unavailable
                     onRadioStateUnavailable();
+                    // set mPhoneId to null only after sim state changed broadcast is sent as it
+                    // needs the phoneId. The broadcast is sent from onRadioStateUnavailable()
+                    mPhoneId = null;
                 }
                 parseAtr(iss.atr);
                 mCardState = iss.cardState;
@@ -304,7 +312,10 @@
         }
         mUiccCard = null;
 
-        // todo: broadcast sim state changed for absent/unknown when IccCardProxy is removed
+        if (mPhoneId != null) {
+            UiccProfile.broadcastInternalIccStateChangedIntent(
+                    IccCardConstants.INTENT_VALUE_ICC_UNKNOWN, null, mPhoneId);
+        }
 
         mCardState = CardState.CARDSTATE_ABSENT;
         mLastRadioState = RadioState.RADIO_UNAVAILABLE;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
index 41ff61d..443e427 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
@@ -27,6 +27,7 @@
 
 import android.app.ActivityManager;
 import android.app.IActivityManager;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.IIntentSender;
 import android.content.Intent;
@@ -41,6 +42,7 @@
 import android.os.RegistrantList;
 import android.os.ServiceManager;
 import android.provider.BlockedNumberContract;
+import android.provider.Settings;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -208,7 +210,7 @@
     private Object mLock = new Object();
     private boolean mReady;
     protected HashMap<String, IBinder> mServiceManagerMockedServices = new HashMap<>();
-    private Phone[] mPhones = new Phone[] {mPhone};
+    private Phone[] mPhones;
 
 
     protected HashMap<Integer, ImsManager> mImsManagerInstances = new HashMap<>();
@@ -307,6 +309,7 @@
         TAG = tag;
         MockitoAnnotations.initMocks(this);
 
+        mPhones = new Phone[] {mPhone};
         mSimulatedCommands = new SimulatedCommands();
         mContextFixture = new ContextFixture();
         mContext = mContextFixture.getTestDouble();
@@ -459,6 +462,15 @@
 
         //SIM
         doReturn(1).when(mTelephonyManager).getSimCount();
+        doReturn(1).when(mTelephonyManager).getPhoneCount();
+
+        //Data
+        //Initial state is: userData enabled, provisioned.
+        ContentResolver resolver = mContext.getContentResolver();
+        Settings.Global.putInt(resolver, Settings.Global.MOBILE_DATA, 1);
+        Settings.Global.putInt(resolver, Settings.Global.DEVICE_PROVISIONED, 1);
+        Settings.Global.putInt(resolver,
+                Settings.Global.DEVICE_PROVISIONING_MOBILE_DATA_ENABLED, 1);
 
         //Use reflection to mock singletons
         replaceInstance(CallManager.class, "INSTANCE", null, mCallManager);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java
index d733e02..7434abf 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java
@@ -1247,12 +1247,7 @@
     @Test
     @SmallTest
     public void testDataEnableInProvisioning() throws Exception {
-        // Initial state is: userData enabled, provisioned.
         ContentResolver resolver = mContext.getContentResolver();
-        Settings.Global.putInt(resolver, Settings.Global.MOBILE_DATA, 1);
-        Settings.Global.putInt(resolver, Settings.Global.DEVICE_PROVISIONED, 1);
-        Settings.Global.putInt(resolver,
-                Settings.Global.DEVICE_PROVISIONING_MOBILE_DATA_ENABLED, 1);
 
         assertEquals(1, Settings.Global.getInt(resolver, Settings.Global.MOBILE_DATA));
         assertTrue(mDct.isDataEnabled());
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccProfileTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccProfileTest.java
index 01c807d..bbe2ce9 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccProfileTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccProfileTest.java
@@ -16,7 +16,6 @@
 package com.android.internal.telephony.uicc;
 
 import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
@@ -33,7 +32,6 @@
 import android.os.Message;
 import android.test.suitebuilder.annotation.SmallTest;
 
-import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.IccCardConstants.State;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.cat.CatService;
@@ -52,8 +50,6 @@
     }
 
     private IccIoResult mIccIoResult;
-    // Must match UiccProfile.EVENT_RADIO_ON
-    private static final int EVENT_RADIO_ON = 2;
     // Must match UiccProfile.EVENT_APP_READY
     private static final int EVENT_APP_READY = 6;
     private static final int SCARY_SLEEP_MS = 200;
@@ -254,18 +250,7 @@
 
     @Test
     @SmallTest
-    public void testPowerOn() {
-        mSimulatedCommands.setRadioPower(true, null);
-        mUiccProfile.sendMessage(mUiccProfile.obtainMessage(EVENT_RADIO_ON));
-        waitForMs(SCARY_SLEEP_MS);
-        assertEquals(CommandsInterface.RadioState.RADIO_ON, mSimulatedCommands.getRadioState());
-        assertEquals(mUiccProfile.getState(), State.NOT_READY);
-    }
-
-    @Test
-    @SmallTest
     public void testUpdateUiccProfileApplicationNotReady() {
-        mUiccProfile.sendMessage(mUiccProfile.obtainMessage(EVENT_RADIO_ON));
         /* update app status and index */
         IccCardApplicationStatus cdmaApp = composeUiccApplicationStatus(
                 IccCardApplicationStatus.AppType.APPTYPE_CSIM,
@@ -298,7 +283,6 @@
     @Test
     @SmallTest
     public void testUpdateUiccProfileApplicationAllReady() {
-        mUiccProfile.sendMessage(mUiccProfile.obtainMessage(EVENT_RADIO_ON));
         /* update app status and index */
         IccCardApplicationStatus cdmaApp = composeUiccApplicationStatus(
                 IccCardApplicationStatus.AppType.APPTYPE_CSIM,
@@ -333,21 +317,20 @@
     @Test
     @SmallTest
     public void testUpdateUiccProfileApplicationAllSupportedAppsReady() {
-        mUiccProfile.sendMessage(mUiccProfile.obtainMessage(EVENT_RADIO_ON));
         /* update app status and index */
-        IccCardApplicationStatus cdmaApp = composeUiccApplicationStatus(
-                IccCardApplicationStatus.AppType.APPTYPE_CSIM,
-                IccCardApplicationStatus.AppState.APPSTATE_READY, "0xA0");
+        IccCardApplicationStatus umtsApp = composeUiccApplicationStatus(
+                IccCardApplicationStatus.AppType.APPTYPE_USIM,
+                IccCardApplicationStatus.AppState.APPSTATE_READY, "0xA2");
         IccCardApplicationStatus imsApp = composeUiccApplicationStatus(
                 IccCardApplicationStatus.AppType.APPTYPE_ISIM,
                 IccCardApplicationStatus.AppState.APPSTATE_READY, "0xA1");
-        IccCardApplicationStatus umtsApp = composeUiccApplicationStatus(
+        IccCardApplicationStatus unknownApp = composeUiccApplicationStatus(
                 IccCardApplicationStatus.AppType.APPTYPE_UNKNOWN,
                 IccCardApplicationStatus.AppState.APPSTATE_UNKNOWN, "0xA2");
-        mIccCardStatus.mApplications = new IccCardApplicationStatus[]{cdmaApp, imsApp, umtsApp};
-        mIccCardStatus.mCdmaSubscriptionAppIndex = 0;
-        mIccCardStatus.mImsSubscriptionAppIndex = 1;
-        mIccCardStatus.mGsmUmtsSubscriptionAppIndex = 2;
+        mIccCardStatus.mApplications = new IccCardApplicationStatus[]{imsApp, umtsApp, unknownApp};
+        mIccCardStatus.mCdmaSubscriptionAppIndex = -1;
+        mIccCardStatus.mImsSubscriptionAppIndex = 0;
+        mIccCardStatus.mGsmUmtsSubscriptionAppIndex = 1;
         Message mProfileUpdate = mHandler.obtainMessage(UICCPROFILE_UPDATE_APPLICATION_EVENT);
         setReady(false);
         mProfileUpdate.sendToTarget();