Merge 8d94d4a0cd488be50e7590c50180be8caa4f354e on remote branch

Change-Id: I7d965b83ab41050e70313901e6bb074614a6eb4c
diff --git a/Android.bp b/Android.bp
index 7268d27..fe33720 100644
--- a/Android.bp
+++ b/Android.bp
@@ -27,10 +27,20 @@
         "telephony-common",
         "app-compat-annotations",
     ],
-    srcs: ["src/**/*.java"],
+    srcs: [
+        "src/**/*.java",
+        ":statslog-ons-java-gen",
+    ],
     aaptflags: ["--auto-add-overlay"],
     certificate: "platform",
     optimize: {
         proguard_flags_files: ["proguard.flags"],
     },
 }
+
+genrule {
+    name: "statslog-ons-java-gen",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --java $(out) --module ons --javaPackage com.android.ons --javaClass OnsStatsLog",
+    out: ["com/android/ons/OnsStatsLog.java"],
+}
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 0141961..fb2346f 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -21,8 +21,8 @@
           android:process="com.android.phone"
           android:sharedUserId="android.uid.phone">
 
-    <protected-broadcast android:name="com.android.ons.action.DOWNLOAD" />
-    <protected-broadcast android:name="com.android.ons.action.CONFIG" />
+    <protected-broadcast android:name="com.android.ons.action.ESIM_DOWNLOAD" />
+    <protected-broadcast android:name="com.android.ons.action.ESIM_CONFIG" />
 
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
     <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
diff --git a/src/com/android/ons/ONSNetworkScanCtlr.java b/src/com/android/ons/ONSNetworkScanCtlr.java
index 97ef770..e14148f 100644
--- a/src/com/android/ons/ONSNetworkScanCtlr.java
+++ b/src/com/android/ons/ONSNetworkScanCtlr.java
@@ -40,10 +40,10 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.stream.Collectors;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 /**
  * Network Scan controller class which will scan for the specific bands as requested and
@@ -266,11 +266,16 @@
     }
 
     /* get mcc mnc from cell info if the cell is for LTE */
-    private String getMccMnc(CellInfo cellInfo) {
+    @VisibleForTesting
+    protected String getMccMnc(CellInfo cellInfo) {
         if (cellInfo instanceof CellInfoLte) {
             return ((CellInfoLte) cellInfo).getCellIdentity().getMccString()
                     + ((CellInfoLte) cellInfo).getCellIdentity().getMncString();
         }
+        else if (cellInfo instanceof CellInfoNr) {
+            return ((CellInfoNr) cellInfo).getCellIdentity().getMccString()
+                    + ((CellInfoNr) cellInfo).getCellIdentity().getMncString();
+        }
 
         return null;
     }
diff --git a/src/com/android/ons/ONSProfileActivator.java b/src/com/android/ons/ONSProfileActivator.java
index 5325b8f..1c16c53 100644
--- a/src/com/android/ons/ONSProfileActivator.java
+++ b/src/com/android/ons/ONSProfileActivator.java
@@ -35,6 +35,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.ons.ONSProfileDownloader.DownloadRetryResultCode;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -48,8 +49,6 @@
 public class ONSProfileActivator implements ONSProfileConfigurator.ONSProfConfigListener,
         ONSProfileDownloader.IONSProfileDownloaderListener {
 
-    public static final String ACTION_CARRIER_CONFIG_CHANGED =
-            "android.telephony.action.CARRIER_CONFIG_CHANGED";
     private static final String TAG = ONSProfileActivator.class.getName();
     private final Context mContext;
     private final SubscriptionManager mSubManager;
@@ -59,13 +58,14 @@
     private final ONSProfileConfigurator mONSProfileConfig;
     private final ONSProfileDownloader mONSProfileDownloader;
     private final ConnectivityManager mConnectivityManager;
+    private final ONSStats mONSStats;
     @VisibleForTesting protected boolean mIsInternetConnAvailable = false;
     @VisibleForTesting protected boolean mRetryDownloadWhenNWConnected = false;
-    private int mDownloadRetryCount = 0;
+    @VisibleForTesting protected int mDownloadRetryCount = 0;
 
     @VisibleForTesting protected static final int REQUEST_CODE_DOWNLOAD_RETRY = 2;
 
-    public ONSProfileActivator(Context context) {
+    public ONSProfileActivator(Context context, ONSStats onsStats) {
         mContext = context;
         mSubManager = mContext.getSystemService(SubscriptionManager.class);
         mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
@@ -78,7 +78,7 @@
 
         //Monitor internet connection.
         mConnectivityManager = context.getSystemService(ConnectivityManager.class);
-
+        mONSStats = onsStats;
         NetworkRequest request = new NetworkRequest.Builder().addCapability(
                 NetworkCapabilities.NET_CAPABILITY_VALIDATED).build();
         mConnectivityManager.registerNetworkCallback(request, new NetworkCallback());
@@ -92,7 +92,7 @@
                         TelephonyManager telephonyManager, CarrierConfigManager carrierConfigMgr,
                         EuiccManager euiccManager, ConnectivityManager connManager,
                         ONSProfileConfigurator onsProfileConfigurator,
-                        ONSProfileDownloader onsProfileDownloader) {
+                        ONSProfileDownloader onsProfileDownloader, ONSStats onsStats) {
         mContext = mockContext;
         mSubManager = subscriptionManager;
         mTelephonyManager = telephonyManager;
@@ -101,6 +101,7 @@
         mConnectivityManager = connManager;
         mONSProfileConfig = onsProfileConfigurator;
         mONSProfileDownloader = onsProfileDownloader;
+        mONSStats = onsStats;
     }
 
     ONSProfileConfigurator getONSProfileConfigurator() {
@@ -116,7 +117,9 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case REQUEST_CODE_DOWNLOAD_RETRY: {
-                    provisionCBRS();
+                    Result res = provisionCBRS();
+                    Log.d(TAG, res.toString());
+                    mONSStats.logEvent(new ONSStatsInfo().setProvisioningResult(res));
                 }
                 break;
             }
@@ -127,19 +130,24 @@
      * Called when SIM state changes. Triggers CBRS Auto provisioning.
      */
     public Result handleCarrierConfigChange() {
-        /*final int simState = mTelephonyManager.getSimState();
-        if (simState != TelephonyManager.SIM_STATE_READY) {
-            return Result.ERR_SIM_NOT_READY;
-        }*/
-
         Result res = provisionCBRS();
         Log.d(TAG, res.toString());
+        mONSStats.logEvent(new ONSStatsInfo().setProvisioningResult(res));
+
+        // Reset mDownloadRetryCount as carrier config change event is received. Either new SIM card
+        // is inserted or carrier config values are updated.
+        if (res == Result.DOWNLOAD_REQUESTED || res == Result.SUCCESS) {
+            mDownloadRetryCount = 0;
+        }
+
         return res;
     }
 
     @Override
     public void onOppSubscriptionDeleted(int pSIMId) {
-        provisionCBRS();
+        Result res = provisionCBRS();
+        Log.d(TAG, res.toString());
+        mONSStats.logEvent(new ONSStatsInfo().setProvisioningResult(res));
     }
 
     /**
@@ -273,8 +281,16 @@
         }
 
         //Opportunistic subscription not found. Trigger Download.
-        mONSProfileDownloader.downloadProfile(primaryCBRSSubInfo.getSubscriptionId());
-        return Result.SUCCESS;
+        ONSProfileDownloader.DownloadProfileResult res = mONSProfileDownloader.downloadProfile(
+                primaryCBRSSubInfo.getSubscriptionId());
+
+        switch (res) {
+            case DUPLICATE_REQUEST: return Result.ERR_DUPLICATE_DOWNLOAD_REQUEST;
+            case INVALID_SMDP_ADDRESS: return Result.ERR_INVALID_CARRIER_CONFIG;
+            case SUCCESS: return Result.DOWNLOAD_REQUESTED;
+        }
+
+        return Result.ERR_UNKNOWN;
     }
 
     @Override
@@ -284,23 +300,37 @@
                 primarySubId);
         if (opportunisticESIM == null) {
             Log.e(TAG, "Downloaded Opportunistic eSIM not found. Unable to group with pSIM");
+            mONSStats.logEvent(new ONSStatsInfo()
+                    .setProvisioningResult(Result.ERR_DOWNLOADED_ESIM_NOT_FOUND)
+                    .setPrimarySimSubId(primarySubId)
+                    .setWifiConnected(isWiFiConnected()));
             return;
         }
 
         SubscriptionInfo pSIMSubInfo = mSubManager.getActiveSubscriptionInfo(primarySubId);
         if (pSIMSubInfo != null) {
+            // Group with same Primary SIM for which eSIM is downloaded.
             mONSProfileConfig.groupWithPSIMAndSetOpportunistic(
                     opportunisticESIM, pSIMSubInfo.getGroupUuid());
             Log.d(TAG, "eSIM downloaded and configured successfully");
+            mONSStats.logEvent(new ONSStatsInfo()
+                    .setProvisioningResult(Result.SUCCESS)
+                    .setRetryCount(mDownloadRetryCount)
+                    .setWifiConnected(isWiFiConnected()));
         } else {
             Log.d(TAG, "ESIM downloaded but pSIM is not active or removed");
+            mONSStats.logEvent(new ONSStatsInfo()
+                    .setProvisioningResult(Result.ERR_PSIM_NOT_FOUND)
+                    .setOppSimCarrierId(opportunisticESIM.getCarrierId())
+                    .setWifiConnected(isWiFiConnected()));
         }
     }
 
     @Override
-    public void onDownloadError(ONSProfileDownloader.DownloadRetryOperationCode operationCode,
-                                int pSIMSubId) {
-        switch (operationCode) {
+    public void onDownloadError(int pSIMSubId, DownloadRetryResultCode resultCode,
+            int detailedErrorCode) {
+        boolean logStats = true;
+        switch (resultCode) {
             case ERR_MEMORY_FULL: {
                 //eUICC Memory full occurred while downloading opportunistic eSIM.
 
@@ -340,34 +370,48 @@
             break;
 
             case ERR_RETRY_DOWNLOAD: {
-                startBackoffTimer(pSIMSubId, mDownloadRetryCount);
+                if (startBackoffTimer(pSIMSubId)) {
+                    // do not log the atom if download retry has not reached max limit.
+                    logStats = false;
+                }
             }
             break;
-
-            case ERR_UNRESOLVABLE: {
-                //Stop download until SIM change or device reboot.
+            default: {
+                // Stop download until SIM change or device reboot.
+                Log.e(TAG, "Download failed with cause=" + resultCode);
             }
         }
+        if (logStats) {
+            mONSStats.logEvent(new ONSStatsInfo()
+                    .setDownloadResult(resultCode)
+                    .setPrimarySimSubId(pSIMSubId)
+                    .setRetryCount(mDownloadRetryCount)
+                    .setDetailedErrCode(detailedErrorCode)
+                    .setWifiConnected(isWiFiConnected()));
+        }
     }
 
     /**
      * Called when eSIM download fails. Listener is called after a delay based on retry count with
      * the error code: BACKOFF_TIMER_EXPIRED
-     * @param pSIMSubId
-     * @param retryCount
+     *
+     * @param pSIMSubId Primary Subscription ID
+     * @return true if backoff timer starts; otherwise false.
      */
     @VisibleForTesting
-    protected void startBackoffTimer(int pSIMSubId, int retryCount) {
+    protected boolean startBackoffTimer(int pSIMSubId) {
         //retry logic
-        retryCount++;
-        Log.e(TAG, "Download retry count :" + retryCount);
-        if (retryCount >= getDownloadRetryMaxAttemptsVal(pSIMSubId)) {
+        mDownloadRetryCount++;
+        Log.e(TAG, "Download retry count :" + mDownloadRetryCount);
+
+        //Stop download retry if number of retries exceeded max configured value.
+        if (mDownloadRetryCount > getDownloadRetryMaxAttemptsVal(pSIMSubId)) {
             Log.e(TAG, "Max download retry attempted. Stopping retry");
-            return;
+            return false;
         }
 
         int backoffTimerVal = getDownloadRetryBackOffTimerVal(pSIMSubId);
-        int delay = calculateBackoffDelay(retryCount, backoffTimerVal);
+        int delay = calculateBackoffDelay(mDownloadRetryCount, backoffTimerVal);
 
         Message retryMsg = new Message();
         retryMsg.what = REQUEST_CODE_DOWNLOAD_RETRY;
@@ -375,6 +419,7 @@
         mHandler.sendMessageDelayed(retryMsg, delay);
 
         Log.d(TAG, "Download failed. Retry after :" + delay + "MilliSecs");
+        return true;
     }
 
     @VisibleForTesting
@@ -405,7 +450,7 @@
      * attempts will not be made until next device reboot.
      *
      * @param subscriptionId subscription Id of the primary SIM.
-     * @return
+     * @return integer value for maximum allowed retry attempts.
      */
     private int getDownloadRetryMaxAttemptsVal(int subscriptionId) {
         PersistableBundle config = mCarrierConfigMgr.getConfigForSubId(subscriptionId);
@@ -506,7 +551,9 @@
             Log.d(TAG, "Internet connection available");
             mIsInternetConnAvailable = true;
             if (mRetryDownloadWhenNWConnected) {
-                provisionCBRS();
+                Result res = provisionCBRS();
+                Log.d(TAG, res.toString());
+                mONSStats.logEvent(new ONSStatsInfo().setProvisioningResult(res));
             }
         }
 
@@ -518,20 +565,28 @@
         }
     }
 
+    /**
+     * Enum to map the results of the CBRS provisioning. The order of the defined enums must be kept
+     * intact and new entries should be appended at the end of the list.
+     */
     public enum Result {
         SUCCESS,
+        DOWNLOAD_REQUESTED,
         ERR_SWITCHING_TO_DUAL_SIM_MODE,
         ERR_AUTO_PROVISIONING_DISABLED,
         ERR_ESIM_NOT_SUPPORTED,
         ERR_MULTISIM_NOT_SUPPORTED,
         ERR_CARRIER_DOESNT_SUPPORT_CBRS,
-        ERR_DUAL_ACTIVE_SUBSCRIPTIONS,//Both the slots have primary SIMs
+        ERR_DUAL_ACTIVE_SUBSCRIPTIONS,
         ERR_NO_SIM_INSERTED,
         ERR_SINGLE_ACTIVE_OPPORTUNISTIC_SIM,
         ERR_CANNOT_SWITCH_TO_DUAL_SIM_MODE,
-        ERR_SIM_NOT_READY,
         ERR_WAITING_FOR_INTERNET_CONNECTION,
         ERR_WAITING_FOR_WIFI_CONNECTION,
+        ERR_DUPLICATE_DOWNLOAD_REQUEST,
+        ERR_INVALID_CARRIER_CONFIG,
+        ERR_DOWNLOADED_ESIM_NOT_FOUND,
+        ERR_PSIM_NOT_FOUND,
         ERR_UNKNOWN;
     }
 }
diff --git a/src/com/android/ons/ONSProfileConfigurator.java b/src/com/android/ons/ONSProfileConfigurator.java
index 6026a95..09e31f9 100644
--- a/src/com/android/ons/ONSProfileConfigurator.java
+++ b/src/com/android/ons/ONSProfileConfigurator.java
@@ -127,10 +127,15 @@
     @VisibleForTesting
     protected void groupWithPSIMAndSetOpportunistic(
             SubscriptionInfo opportunisticESIM, ParcelUuid groupUuid) {
-        Log.d(TAG, "Grouping opportunistc eSIM and CBRS pSIM");
-        ArrayList<Integer> subList = new ArrayList<>();
-        subList.add(opportunisticESIM.getSubscriptionId());
-        mSubscriptionManager.addSubscriptionsIntoGroup(subList, groupUuid);
+        if (groupUuid != null && groupUuid.equals(opportunisticESIM.getGroupUuid())) {
+            Log.d(TAG, "opportunistc eSIM and CBRS pSIM already grouped");
+        } else {
+            Log.d(TAG, "Grouping opportunistc eSIM and CBRS pSIM");
+            ArrayList<Integer> subList = new ArrayList<>();
+            subList.add(opportunisticESIM.getSubscriptionId());
+            mSubscriptionManager.addSubscriptionsIntoGroup(subList, groupUuid);
+        }
+
         if (!opportunisticESIM.isOpportunistic()) {
             Log.d(TAG, "set Opportunistic to TRUE");
             mSubscriptionManager.setOpportunistic(true,
diff --git a/src/com/android/ons/ONSProfileDownloader.java b/src/com/android/ons/ONSProfileDownloader.java
index f83aab9..11c6237 100644
--- a/src/com/android/ons/ONSProfileDownloader.java
+++ b/src/com/android/ons/ONSProfileDownloader.java
@@ -37,7 +37,7 @@
 
     interface IONSProfileDownloaderListener {
         void onDownloadComplete(int primarySubId);
-        void onDownloadError(DownloadRetryOperationCode operationCode, int pSIMSubId);
+        void onDownloadError(int pSIMSubId, DownloadRetryResultCode resultCode, int detailErrCode);
     }
 
     private static final String TAG = ONSProfileDownloader.class.getName();
@@ -54,14 +54,16 @@
     private final ONSProfileConfigurator mONSProfileConfig;
     private IONSProfileDownloaderListener mListener;
 
-    @VisibleForTesting
-    protected enum DownloadRetryOperationCode{
+    // Subscription Id of the CBRS PSIM for which opportunistic eSIM is being downloaded. Used to
+    // ignore duplicate download requests when download is in progress.
+    private int mDownloadingPSimSubId;
+
+    protected enum DownloadRetryResultCode {
         DOWNLOAD_SUCCESSFUL,
         ERR_UNRESOLVABLE,
         ERR_MEMORY_FULL,
         ERR_INSTALL_ESIM_PROFILE_FAILED,
-        ERR_RETRY_DOWNLOAD,
-        BACKOFF_TIMER_EXPIRED
+        ERR_RETRY_DOWNLOAD
     };
 
     public ONSProfileDownloader(Context context, CarrierConfigManager carrierConfigManager,
@@ -85,8 +87,16 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-                case REQUEST_CODE_DOWNLOAD_SUB: { //arg1 -> ResultCode
+                // Received Response for download request. REQUEST_CODE_DOWNLOAD_SUB was sent to LPA
+                // as part of request intent.
+                case REQUEST_CODE_DOWNLOAD_SUB: {
                     Log.d(TAG, "REQUEST_CODE_DOWNLOAD_SUB callback received");
+
+                    //Clear downloading subscription flag. Indicates no download in progress.
+                    synchronized (this) {
+                        mDownloadingPSimSubId = -1;
+                    }
+
                     int pSIMSubId = ((Intent) msg.obj).getIntExtra(PARAM_PRIMARY_SUBID, 0);
                     int detailedErrCode = ((Intent) msg.obj).getIntExtra(
                             EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE, 0);
@@ -99,23 +109,23 @@
                     Log.d(TAG, "Operation Code : " + operationCode);
                     Log.d(TAG, "Error Code : " + errorCode);
 
-                    DownloadRetryOperationCode opCode = mapDownloaderErrorCode(msg.arg1,
+                    DownloadRetryResultCode resultCode = mapDownloaderErrorCode(msg.arg1,
                             detailedErrCode, operationCode, errorCode);
-                    Log.d(TAG, "DownloadRetryOperationCode: " + opCode);
+                    Log.d(TAG, "DownloadRetryResultCode: " + resultCode);
 
-                    switch (opCode) {
+                    switch (resultCode) {
                         case DOWNLOAD_SUCCESSFUL:
                             mListener.onDownloadComplete(pSIMSubId);
                             break;
 
                         case ERR_UNRESOLVABLE:
-                            mListener.onDownloadError(opCode, pSIMSubId);
+                            mListener.onDownloadError(pSIMSubId, resultCode, detailedErrCode);
                             Log.e(TAG, "Unresolvable download error: "
                                     + getUnresolvableErrorDescription(errorCode));
                             break;
 
                         default:
-                            mListener.onDownloadError(opCode, pSIMSubId);
+                            mListener.onDownloadError(pSIMSubId, resultCode, detailedErrCode);
                             break;
                     }
                 }
@@ -124,7 +134,7 @@
         }
 
         @VisibleForTesting
-        protected DownloadRetryOperationCode mapDownloaderErrorCode(int resultCode,
+        protected DownloadRetryResultCode mapDownloaderErrorCode(int resultCode,
                                                                     int detailedErrCode,
                                                                     int operationCode,
                                                                     int errorCode) {
@@ -138,17 +148,17 @@
                 //8.1 - eUICC, 4.8 - Insufficient Memory
                 // eUICC does not have sufficient space for this Profile.
                 if (errCode.equals(Pair.create("8.1.0", "4.8"))) {
-                    return DownloadRetryOperationCode.ERR_MEMORY_FULL;
+                    return DownloadRetryResultCode.ERR_MEMORY_FULL;
                 }
 
                 //8.8.5 - Download order, 4.10 - Time to Live Expired
                 //The Download order has expired
                 if (errCode.equals(Pair.create("8.8.5", "4.10"))) {
-                    return DownloadRetryOperationCode.ERR_RETRY_DOWNLOAD;
+                    return DownloadRetryResultCode.ERR_RETRY_DOWNLOAD;
                 }
 
                 //All other errors are unresolvable or retry after SIM State Change
-                return DownloadRetryOperationCode.ERR_UNRESOLVABLE;
+                return DownloadRetryResultCode.ERR_UNRESOLVABLE;
 
             }
 
@@ -156,29 +166,29 @@
 
                 //Success Cases
                 case EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK: {
-                    return DownloadRetryOperationCode.DOWNLOAD_SUCCESSFUL;
+                    return DownloadRetryResultCode.DOWNLOAD_SUCCESSFUL;
                 }
 
                 //Low eUICC memory cases
                 case EuiccManager.ERROR_EUICC_INSUFFICIENT_MEMORY: {
                     Log.d(TAG, "Download ERR: EUICC_INSUFFICIENT_MEMORY");
-                    return DownloadRetryOperationCode.ERR_MEMORY_FULL;
+                    return DownloadRetryResultCode.ERR_MEMORY_FULL;
                 }
 
                 //Temporary download error cases
                 case EuiccManager.ERROR_TIME_OUT:
                 case EuiccManager.ERROR_CONNECTION_ERROR:
                 case EuiccManager.ERROR_OPERATION_BUSY: {
-                    return DownloadRetryOperationCode.ERR_RETRY_DOWNLOAD;
+                    return DownloadRetryResultCode.ERR_RETRY_DOWNLOAD;
                 }
 
                 //Profile installation failure cases
                 case EuiccManager.ERROR_INSTALL_PROFILE: {
-                    return DownloadRetryOperationCode.ERR_INSTALL_ESIM_PROFILE_FAILED;
+                    return DownloadRetryResultCode.ERR_INSTALL_ESIM_PROFILE_FAILED;
                 }
 
                 default: {
-                    return DownloadRetryOperationCode.ERR_UNRESOLVABLE;
+                    return DownloadRetryResultCode.ERR_UNRESOLVABLE;
                 }
             }
         }
@@ -214,14 +224,28 @@
         return "Unknown";
     }
 
-    @VisibleForTesting
-    protected void downloadProfile(int primarySubId) {
+    protected enum DownloadProfileResult {
+        SUCCESS,
+        DUPLICATE_REQUEST,
+        INVALID_SMDP_ADDRESS
+    }
+
+    protected DownloadProfileResult downloadProfile(int primarySubId) {
         Log.d(TAG, "downloadProfile");
 
         //Get SMDP address from carrier configuration.
         String smdpAddress = getSMDPServerAddress(primarySubId);
         if (smdpAddress == null || smdpAddress.length() <= 0) {
-            return;
+            return DownloadProfileResult.INVALID_SMDP_ADDRESS;
+        }
+
+        synchronized (this) {
+            if (mDownloadingPSimSubId == primarySubId) {
+                Log.d(TAG, "Download already in progress.");
+                return DownloadProfileResult.DUPLICATE_REQUEST;
+            }
+
+            mDownloadingPSimSubId = primarySubId;
         }
 
         //Generate Activation code 1${SM-DP+ FQDN}$
@@ -236,6 +260,8 @@
         Log.d(TAG, "Download Request sent to EUICC Manager");
         mEuiccManager.downloadSubscription(DownloadableSubscription.forActivationCode(
                 activationCode), true, callbackIntent);
+
+        return DownloadProfileResult.SUCCESS;
     }
 
     /**
diff --git a/src/com/android/ons/ONSProfileSelector.java b/src/com/android/ons/ONSProfileSelector.java
index ab9168d..e6480c5 100644
--- a/src/com/android/ons/ONSProfileSelector.java
+++ b/src/com/android/ons/ONSProfileSelector.java
@@ -31,6 +31,7 @@
 import android.telephony.AvailableNetworkInfo;
 import android.telephony.CellInfo;
 import android.telephony.CellInfoLte;
+import android.telephony.CellInfoNr;
 import android.telephony.SignalStrength;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
@@ -195,7 +196,7 @@
                         }
                     } else {
                         logDebug("switch to sub:" + subId);
-                        switchToSubscription(subId);
+                        switchToSubscription(subId, getAvailableESIMPortIndex());
                     }
                 }
             };
@@ -267,20 +268,28 @@
         }
     }
 
-    private String getMcc(CellInfo cellInfo) {
+    @VisibleForTesting
+    protected String getMcc(CellInfo cellInfo) {
         String mcc = "";
         if (cellInfo instanceof CellInfoLte) {
             mcc = ((CellInfoLte) cellInfo).getCellIdentity().getMccString();
         }
+        else if (cellInfo instanceof CellInfoNr) {
+            mcc = ((CellInfoNr) cellInfo).getCellIdentity().getMccString();
+        }
 
         return mcc;
     }
 
-    private String getMnc(CellInfo cellInfo) {
+    @VisibleForTesting
+    protected String getMnc(CellInfo cellInfo) {
         String mnc = "";
         if (cellInfo instanceof CellInfoLte) {
             mnc = ((CellInfoLte) cellInfo).getCellIdentity().getMncString();
         }
+        else if (cellInfo instanceof CellInfoNr) {
+            mnc = ((CellInfoNr) cellInfo).getCellIdentity().getMncString();
+        }
 
         return mnc;
     }
@@ -350,7 +359,7 @@
 
     private HashMap<Integer, IUpdateAvailableNetworksCallback> callbackStubs = new HashMap<>();
 
-    private void switchToSubscription(int subId) {
+    private void switchToSubscription(int subId, int availableSIMPortIndex) {
         Intent callbackIntent = new Intent(ACTION_SUB_SWITCH);
         callbackIntent.setClass(mContext, OpportunisticNetworkService.class);
         updateToken();
@@ -359,21 +368,22 @@
         mSubId = subId;
         PendingIntent replyIntent = PendingIntent.getService(mContext,
                 1, callbackIntent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
-        int eSIMPortIndex = getAvailableESIMPortIndex();
-        if (eSIMPortIndex == TelephonyManager.INVALID_PORT_INDEX) {
+        if (availableSIMPortIndex == TelephonyManager.INVALID_PORT_INDEX) {
             sendUpdateNetworksCallbackHelper(mNetworkScanCallback,
                     TelephonyManager.UPDATE_AVAILABLE_NETWORKS_SIM_PORT_NOT_AVAILABLE);
             return;
         }
-        mEuiccManager.switchToSubscription(subId, eSIMPortIndex, replyIntent);
+        mEuiccManager.switchToSubscription(subId, availableSIMPortIndex, replyIntent);
     }
 
-    private int getAvailableESIMPortIndex() {
-        //Check if an opportunistic subscription is already active. If yes then, use the same port.
+    @VisibleForTesting
+    protected int getAvailableESIMPortIndex() {
         //Check if an opportunistic subscription is already active. If yes then, use the same port.
         List<SubscriptionInfo> subscriptionInfos = mSubscriptionManager
-                .getActiveSubscriptionInfoList();
+                .getCompleteActiveSubscriptionInfoList();
         if (subscriptionInfos != null) {
+            logDebug("[getAvailableESIMPortIndex] subscriptionInfos size:"
+                    + subscriptionInfos.size());
             for (SubscriptionInfo subscriptionInfo : subscriptionInfos) {
                 if (subscriptionInfo.isEmbedded() && subscriptionInfo.isOpportunistic()) {
                     return subscriptionInfo.getPortIndex();
@@ -383,12 +393,14 @@
 
         //Look for available port.
         for (UiccCardInfo uiccCardInfo : mTelephonyManager.getUiccCardsInfo()) {
+            logDebug("[getAvailableESIMPortIndex] CardInfo: " + uiccCardInfo.toString());
             if (!uiccCardInfo.isEuicc()) {
                 continue;
             }
 
             EuiccManager euiccManager = mEuiccManager.createForCardId(uiccCardInfo.getCardId());
             for (UiccPortInfo uiccPortInfo : uiccCardInfo.getPorts()) {
+                logDebug("[getAvailableESIMPortIndex] PortInfo: " + uiccPortInfo.toString());
                 //Port is available if no profiles enabled on it.
                 if (euiccManager.isSimPortAvailable(uiccPortInfo.getPortIndex())) {
                     return uiccPortInfo.getPortIndex();
@@ -396,6 +408,7 @@
             }
         }
 
+        logDebug("[getAvailableESIMPortIndex] No Port is available.");
         return TelephonyManager.INVALID_PORT_INDEX;
     }
 
@@ -481,31 +494,6 @@
         return new HashSet<>(availableNetworks1).equals(new HashSet<>(availableNetworks2));
     }
 
-    private boolean isPrimaryActiveOnOpportunisticSlot(
-            ArrayList<AvailableNetworkInfo> availableNetworks) {
-        /* Check if any of the available network is an embedded profile. if none are embedded,
-         * return false
-         * Todo <b/130535071> */
-        if (!isOpportunisticSubEmbedded(availableNetworks)) {
-            return false;
-        }
-
-        List<SubscriptionInfo> subscriptionInfos =
-            mSubscriptionManager.getActiveSubscriptionInfoList(false);
-        if (subscriptionInfos == null) {
-            return false;
-        }
-
-        /* if there is a primary subscription active on the eSIM, return true */
-        for (SubscriptionInfo subscriptionInfo : subscriptionInfos) {
-            if (!subscriptionInfo.isOpportunistic() && subscriptionInfo.isEmbedded()) {
-                return true;
-            }
-        }
-
-        return false;
-
-    }
     private void sendUpdateNetworksCallbackHelper(IUpdateAvailableNetworksCallback callback,
             int result) {
         if (callback == null) {
@@ -537,11 +525,12 @@
             return;
         }
 
-        /* if primary subscription is active on opportunistic slot, do not switch out the same. */
-        if (isPrimaryActiveOnOpportunisticSlot(availableNetworks)) {
-            logDebug("primary subscription active on opportunistic sub");
+        /* Check if ports are available on the embedded slot */
+        int availSIMPortIndex = getAvailableESIMPortIndex();
+        if (availSIMPortIndex == TelephonyManager.INVALID_PORT_INDEX) {
+            logDebug("SIM port not available.");
             sendUpdateNetworksCallbackHelper(callbackStub,
-                TelephonyManager.UPDATE_AVAILABLE_NETWORKS_INVALID_ARGUMENTS);
+                    TelephonyManager.UPDATE_AVAILABLE_NETWORKS_SIM_PORT_NOT_AVAILABLE);
             return;
         }
 
@@ -574,7 +563,8 @@
                 /* if subscription is not active, activate the sub */
                 if (!mSubscriptionManager.isActiveSubId(filteredAvailableNetworks.get(0).getSubId())) {
                     mNetworkScanCallback = callbackStub;
-                    switchToSubscription(filteredAvailableNetworks.get(0).getSubId());
+                    switchToSubscription(filteredAvailableNetworks.get(0).getSubId(),
+                            availSIMPortIndex);
                 } else {
                     if (enableModem(filteredAvailableNetworks.get(0).getSubId(), true)) {
                         sendUpdateNetworksCallbackHelper(callbackStub,
diff --git a/src/com/android/ons/ONSStats.java b/src/com/android/ons/ONSStats.java
new file mode 100644
index 0000000..21ce607
--- /dev/null
+++ b/src/com/android/ons/ONSStats.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2022 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.ons;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+
+import com.android.ons.ONSProfileActivator.Result;
+import com.android.ons.ONSProfileDownloader.DownloadRetryResultCode;
+
+import java.util.List;
+
+public class ONSStats {
+    private static final String ONS_ATOM_LOG_FILE = "ons_atom_log_info";
+    private static final String KEY_PROVISIONING_RESULT = "_provisioning_result";
+    private static final String KEY_DOWNLOAD_RESULT = "_download_result";
+    private static final String KEY_RETRY_COUNT = "_retry_count";
+    private static final String KEY_DETAILED_ERROR_CODE = "_detailed_error_code";
+    private static final String KEY_OPP_CARRIER_ID = "_opportunistic_carrier_id";
+    private static final String KEY_PRIMARY_CARRIER_ID = "_primary_sim_carrier_id";
+    private final Context mContext;
+    private final SubscriptionManager mSubscriptionManager;
+
+    /** Constructor to create instance for ONSStats. */
+    public ONSStats(Context context, SubscriptionManager subscriptionManager) {
+        mContext = context;
+        mSubscriptionManager = subscriptionManager;
+    }
+
+    /**
+     * It logs the ONS atom with the info passed as ONSStatsInfo. If the information is already
+     * logged, it will be skipped.
+     *
+     * @param info information to be logged.
+     * @return returns true if information is logged, otherwise false.
+     */
+    public boolean logEvent(ONSStatsInfo info) {
+        // check if the info needs to be ignored.
+        if (ignoreEvent(info)) {
+            return false;
+        }
+        int statsCode = OnsStatsLog.ONS_OPPORTUNISTIC_ESIM_PROVISIONING_COMPLETE__ERROR_CODE__RESULT_UNKNOWN;
+        if (info.isProvisioningResultUpdated()) {
+            switch (info.getProvisioningResult()) {
+                case SUCCESS:
+                    statsCode = OnsStatsLog.ONS_OPPORTUNISTIC_ESIM_PROVISIONING_COMPLETE__ERROR_CODE__RESULT_SUCCESS;
+                    break;
+                case ERR_CANNOT_SWITCH_TO_DUAL_SIM_MODE:
+                    statsCode = OnsStatsLog.ONS_OPPORTUNISTIC_ESIM_PROVISIONING_COMPLETE__ERROR_CODE__RESULT_SWITCH_TO_MULTISIM_FAILED;
+                    break;
+                case ERR_CARRIER_DOESNT_SUPPORT_CBRS:
+                case ERR_AUTO_PROVISIONING_DISABLED:
+                    statsCode = OnsStatsLog.ONS_OPPORTUNISTIC_ESIM_PROVISIONING_COMPLETE__ERROR_CODE__RESULT_AUTO_PROVISIONING_DISABLED;
+                    break;
+                case ERR_ESIM_NOT_SUPPORTED:
+                case ERR_MULTISIM_NOT_SUPPORTED:
+                    statsCode = OnsStatsLog.ONS_OPPORTUNISTIC_ESIM_PROVISIONING_COMPLETE__ERROR_CODE__RESULT_DEVICE_NOT_CAPABLE;
+                    break;
+                case ERR_SINGLE_ACTIVE_OPPORTUNISTIC_SIM:
+                case ERR_DUAL_ACTIVE_SUBSCRIPTIONS:
+                case ERR_PSIM_NOT_FOUND:
+                case ERR_DOWNLOADED_ESIM_NOT_FOUND:
+                    statsCode = OnsStatsLog.ONS_OPPORTUNISTIC_ESIM_PROVISIONING_COMPLETE__ERROR_CODE__RESULT_ESIM_PROVISIONING_FAILED;
+                    break;
+                case ERR_WAITING_FOR_INTERNET_CONNECTION:
+                case ERR_WAITING_FOR_WIFI_CONNECTION:
+                    statsCode = OnsStatsLog.ONS_OPPORTUNISTIC_ESIM_PROVISIONING_COMPLETE__ERROR_CODE__RESULT_INTERNET_NOT_AVAILABLE;
+                    break;
+                case ERR_INVALID_CARRIER_CONFIG:
+                    statsCode = OnsStatsLog.ONS_OPPORTUNISTIC_ESIM_PROVISIONING_COMPLETE__ERROR_CODE__RESULT_UNRESOLVABLE_ERROR;
+                    break;
+                default:
+                    break;
+            }
+        } else {
+            switch (info.getDownloadResult()) {
+                case ERR_UNRESOLVABLE:
+                    statsCode = OnsStatsLog.ONS_OPPORTUNISTIC_ESIM_PROVISIONING_COMPLETE__ERROR_CODE__RESULT_UNRESOLVABLE_ERROR;
+                    break;
+                case ERR_MEMORY_FULL:
+                    statsCode = OnsStatsLog.ONS_OPPORTUNISTIC_ESIM_PROVISIONING_COMPLETE__ERROR_CODE__RESULT_MEMORY_FULL;
+                    break;
+                case ERR_INSTALL_ESIM_PROFILE_FAILED:
+                    statsCode = OnsStatsLog.ONS_OPPORTUNISTIC_ESIM_PROVISIONING_COMPLETE__ERROR_CODE__RESULT_INSTALL_ESIM_PROFILE_FAILED;
+                    break;
+                case ERR_RETRY_DOWNLOAD:
+                    statsCode = OnsStatsLog.ONS_OPPORTUNISTIC_ESIM_PROVISIONING_COMPLETE__ERROR_CODE__RESULT_CONNECTION_ERROR;
+                    break;
+                default:
+                    break;
+            }
+        }
+        OnsStatsLog.write(
+                OnsStatsLog.ONS_OPPORTUNISTIC_ESIM_PROVISIONING_COMPLETE,
+                getSimCarrierId(info.getPrimarySimSubId()),
+                info.getOppSimCarrierId(),
+                info.isWifiConnected(),
+                statsCode,
+                info.getRetryCount(),
+                info.getDetailedErrCode());
+        updateSharedPreferences(info);
+        return true;
+    }
+
+    private void updateSharedPreferences(ONSStatsInfo info) {
+        SharedPreferences sharedPref =
+                mContext.getSharedPreferences(ONS_ATOM_LOG_FILE, Context.MODE_PRIVATE);
+        SharedPreferences.Editor editor = sharedPref.edit();
+        if (info.isProvisioningResultUpdated()) {
+            editor.putInt(KEY_PROVISIONING_RESULT, info.getProvisioningResult().ordinal());
+            editor.remove(KEY_DOWNLOAD_RESULT);
+        } else {
+            editor.putInt(KEY_DOWNLOAD_RESULT, info.getDownloadResult().ordinal());
+            editor.remove(KEY_PROVISIONING_RESULT);
+        }
+        editor.putInt(KEY_PRIMARY_CARRIER_ID, getSimCarrierId(info.getPrimarySimSubId()))
+                .putInt(KEY_RETRY_COUNT, info.getRetryCount())
+                .putInt(KEY_OPP_CARRIER_ID, info.getOppSimCarrierId())
+                .putInt(KEY_DETAILED_ERROR_CODE, info.getDetailedErrCode())
+                .apply();
+    }
+
+    private boolean ignoreEvent(ONSStatsInfo info) {
+        Result result = info.getProvisioningResult();
+        if (info.isProvisioningResultUpdated()) {
+            info.setDetailedErrCode(result.ordinal());
+            // Codes are ignored since they are intermediate state of CBRS provisioning check.
+            if ((result == Result.DOWNLOAD_REQUESTED)
+                    || result == Result.ERR_NO_SIM_INSERTED
+                    || result == Result.ERR_DUPLICATE_DOWNLOAD_REQUEST
+                    || result == Result.ERR_SWITCHING_TO_DUAL_SIM_MODE) {
+                return true;
+            }
+
+            // add subscription id for carrier if it doesn't support CBRS.
+            if (result == Result.ERR_CARRIER_DOESNT_SUPPORT_CBRS) {
+                List<SubscriptionInfo> subInfos =
+                        mSubscriptionManager.getAvailableSubscriptionInfoList();
+                info.setPrimarySimSubId(
+                        (subInfos != null && !subInfos.isEmpty())
+                                ? subInfos.get(0).getSubscriptionId()
+                                : -1);
+            }
+        }
+
+        SharedPreferences sharedPref =
+                mContext.getSharedPreferences(ONS_ATOM_LOG_FILE, Context.MODE_PRIVATE);
+
+        boolean errorCodeUpdated =
+                (info.isProvisioningResultUpdated()
+                        ? sharedPref.getInt(KEY_PROVISIONING_RESULT, -1) != result.ordinal()
+                        : sharedPref.getInt(KEY_DOWNLOAD_RESULT, -1)
+                                != info.getDownloadResult().ordinal());
+        boolean carrierIdUpdated =
+                sharedPref.getInt(KEY_PRIMARY_CARRIER_ID, -1)
+                        != getSimCarrierId(info.getPrimarySimSubId());
+        boolean retryCountUpdated = sharedPref.getInt(KEY_RETRY_COUNT, -1) != info.getRetryCount();
+        boolean oppCarrierIdChanged =
+                sharedPref.getInt(KEY_OPP_CARRIER_ID, -1) != info.getOppSimCarrierId();
+        boolean detailedErrorChanged =
+                sharedPref.getInt(KEY_DETAILED_ERROR_CODE, -1) != info.getDetailedErrCode();
+        if (!(errorCodeUpdated
+                || carrierIdUpdated
+                || retryCountUpdated
+                || oppCarrierIdChanged
+                || detailedErrorChanged)) {
+            // Result codes are meant to log on every occurrence. These should not be ignored.
+            if (result == Result.SUCCESS
+                    || result == Result.ERR_DOWNLOADED_ESIM_NOT_FOUND
+                    || info.getDownloadResult()
+                            == DownloadRetryResultCode.ERR_INSTALL_ESIM_PROFILE_FAILED) {
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    private int getSimCarrierId(int subId) {
+        if (subId == -1) return -1;
+        SubscriptionInfo subInfo = mSubscriptionManager.getActiveSubscriptionInfo(subId);
+        return (subInfo != null) ? subInfo.getCarrierId() : -1;
+    }
+}
diff --git a/src/com/android/ons/ONSStatsInfo.java b/src/com/android/ons/ONSStatsInfo.java
new file mode 100644
index 0000000..080ed4a
--- /dev/null
+++ b/src/com/android/ons/ONSStatsInfo.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2022 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.ons;
+
+import com.android.ons.ONSProfileActivator.Result;
+import com.android.ons.ONSProfileDownloader.DownloadRetryResultCode;
+
+public final class ONSStatsInfo {
+    public static final int INVALID_VALUE = -1;
+    private Result mProvisioningResult = null;
+    private DownloadRetryResultCode mDownloadResult = null;
+    private int mPrimarySimSubId = INVALID_VALUE;
+    private int mOppSimCarrierId = INVALID_VALUE;
+    private int mRetryCount = INVALID_VALUE;
+    private int mDetailedErrCode = INVALID_VALUE;
+    private boolean mIsWifiConnected = false;
+    private boolean mIsProvisioningResultUpdated = false;
+
+    public Result getProvisioningResult() {
+        return mProvisioningResult;
+    }
+
+    public DownloadRetryResultCode getDownloadResult() {
+        return mDownloadResult;
+    }
+
+    public int getPrimarySimSubId() {
+        return mPrimarySimSubId;
+    }
+
+    public int getOppSimCarrierId() {
+        return mOppSimCarrierId;
+    }
+
+    public int getRetryCount() {
+        return mRetryCount;
+    }
+
+    public int getDetailedErrCode() {
+        return mDetailedErrCode;
+    }
+
+    public boolean isWifiConnected() {
+        return mIsWifiConnected;
+    }
+
+    public boolean isProvisioningResultUpdated() {
+        return mIsProvisioningResultUpdated;
+    }
+
+    public ONSStatsInfo setProvisioningResult(Result result) {
+        mProvisioningResult = result;
+        mDownloadResult = null;
+        mIsProvisioningResultUpdated = true;
+        return this;
+    }
+
+    public ONSStatsInfo setDownloadResult(DownloadRetryResultCode retryResultCode) {
+        mProvisioningResult = null;
+        mDownloadResult = retryResultCode;
+        mIsProvisioningResultUpdated = false;
+        return this;
+    }
+
+    public ONSStatsInfo setPrimarySimSubId(int primarySimSubId) {
+        mPrimarySimSubId = primarySimSubId;
+        return this;
+    }
+
+    public ONSStatsInfo setOppSimCarrierId(int oppSimCarrierId) {
+        mOppSimCarrierId = oppSimCarrierId;
+        return this;
+    }
+
+    public ONSStatsInfo setRetryCount(int retryCount) {
+        mRetryCount = retryCount;
+        return this;
+    }
+
+    public ONSStatsInfo setDetailedErrCode(int detailedErrCode) {
+        mDetailedErrCode = detailedErrCode;
+        return this;
+    }
+
+    public ONSStatsInfo setWifiConnected(boolean wifiConnected) {
+        mIsWifiConnected = wifiConnected;
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return "ONSStatsInfo{"
+                + "mProvisioningResult="
+                + mProvisioningResult
+                + ", mDownloadResult="
+                + mDownloadResult
+                + ", mPrimarySimSubId="
+                + mPrimarySimSubId
+                + ", mOppSimCarrierId="
+                + mOppSimCarrierId
+                + ", mRetryCount="
+                + mRetryCount
+                + ", mDetailedErrCode="
+                + mDetailedErrCode
+                + ", mIsWifiConnected="
+                + mIsWifiConnected
+                + ", mIsProvisioningResultUpdated="
+                + mIsProvisioningResultUpdated
+                + '}';
+    }
+}
diff --git a/src/com/android/ons/OpportunisticNetworkService.java b/src/com/android/ons/OpportunisticNetworkService.java
index 13e7034..485722f 100644
--- a/src/com/android/ons/OpportunisticNetworkService.java
+++ b/src/com/android/ons/OpportunisticNetworkService.java
@@ -35,6 +35,7 @@
 import android.os.RemoteException;
 import android.os.TelephonyServiceManager.ServiceRegisterer;
 import android.telephony.AvailableNetworkInfo;
+import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyFrameworkInitializer;
@@ -62,6 +63,7 @@
     private TelephonyManager mTelephonyManager;
     @VisibleForTesting protected SubscriptionManager mSubscriptionManager;
     private ONSProfileActivator mONSProfileActivator;
+    private ONSStats mONSStats;
     private Handler mHandler = null;
 
     private final Object mLock = new Object();
@@ -421,7 +423,7 @@
                     }
                     break;
 
-                    case ONSProfileActivator.ACTION_CARRIER_CONFIG_CHANGED:
+                    case CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED:
                         mONSProfileActivator.handleCarrierConfigChange();
                         break;
                 }
@@ -455,10 +457,11 @@
         mSubscriptionManager = (SubscriptionManager) mContext.getSystemService(
                 Context.TELEPHONY_SUBSCRIPTION_SERVICE);
         mONSConfigInputHashMap = new HashMap<String, ONSConfigInput>();
+        mONSStats = new ONSStats(mContext, mSubscriptionManager);
         mContext.registerReceiver(mBroadcastReceiver,
             new IntentFilter(TelephonyIntents.ACTION_SIM_STATE_CHANGED));
         enableOpportunisticNetwork(getPersistentEnableState());
-        mONSProfileActivator = new ONSProfileActivator(mContext);
+        mONSProfileActivator = new ONSProfileActivator(mContext, mONSStats);
     }
 
     private void handleCarrierAppAvailableNetworks(
diff --git a/tests/src/com/android/ons/ONSNetworkScanCtlrTest.java b/tests/src/com/android/ons/ONSNetworkScanCtlrTest.java
index e4470c5..0053adc 100644
--- a/tests/src/com/android/ons/ONSNetworkScanCtlrTest.java
+++ b/tests/src/com/android/ons/ONSNetworkScanCtlrTest.java
@@ -20,31 +20,25 @@
 import static org.mockito.Mockito.*;
 
 import android.os.Looper;
-import android.os.PersistableBundle;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.AvailableNetworkInfo;
-import android.telephony.CarrierConfigManager;
 import android.telephony.CellIdentityLte;
+import android.telephony.CellIdentityNr;
 import android.telephony.CellInfo;
 import android.telephony.CellInfoLte;
+import android.telephony.CellInfoNr;
 import android.telephony.NetworkScan;
 import android.telephony.NetworkScanRequest;
 import android.telephony.RadioAccessSpecifier;
 import android.telephony.SubscriptionInfo;
-import android.telephony.TelephonyManager;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 
-import java.sql.Array;
-import java.util.Arrays;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
 public class ONSNetworkScanCtlrTest extends ONSBaseTest {
@@ -341,4 +335,16 @@
         waitUntilReady();
         mReady = false;
     }
+
+    @Test
+    public void testGetMncMccFromCellInfoNr() {
+        mONSNetworkScanCtlr = new ONSNetworkScanCtlr(mContext, mMockTelephonyManager, null);
+
+        CellIdentityNr cellIdentityNr = new CellIdentityNr(0, 0, 0, new int[]{0}, "111", "222", 0,
+                "", "",  Collections.emptyList());
+
+        CellInfoNr cellinfoNr = new CellInfoNr(0, true, 0, cellIdentityNr, null);
+
+        assertEquals(mONSNetworkScanCtlr.getMccMnc(cellinfoNr), "111222");
+    }
 }
diff --git a/tests/src/com/android/ons/ONSProfileActivatorTest.java b/tests/src/com/android/ons/ONSProfileActivatorTest.java
index c678d24..3e94bd5 100644
--- a/tests/src/com/android/ons/ONSProfileActivatorTest.java
+++ b/tests/src/com/android/ons/ONSProfileActivatorTest.java
@@ -18,6 +18,8 @@
 
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 import android.content.Context;
 import android.content.res.Resources;
@@ -25,6 +27,7 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.os.Looper;
+import android.os.ParcelUuid;
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionInfo;
@@ -41,9 +44,12 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.UUID;
 
 public class ONSProfileActivatorTest extends ONSBaseTest {
     private static final String TAG = ONSProfileActivatorTest.class.getName();
+    private static final int TEST_SUBID_0 = 0;
+    private static final int TEST_SUBID_1 = 1;
 
     @Mock
     Context mMockContext;
@@ -66,18 +72,20 @@
     @Mock
     SubscriptionInfo mMockSubInfo;
     @Mock
-    SubscriptionInfo mMockSubInfo2;
+    SubscriptionInfo mMockSubInfo1;
     @Mock
     List<SubscriptionInfo> mMocksubsInPSIMGroup;
     @Mock
     Resources mMockResources;
+    @Mock
+    ONSStats mMockONSStats;
 
     @Before
     public void setUp() throws Exception {
         super.setUp("ONSTest");
         MockitoAnnotations.initMocks(this);
         Looper.prepare();
-        doReturn(TelephonyManager.SIM_STATE_READY).when(mMockTeleManager).getSimState();
+
         doReturn(mMockResources).when(mMockContext).getResources();
 
         doReturn(mMockConnectivityManager).when(mMockContext).getSystemService(
@@ -86,6 +94,36 @@
                 NetworkCapabilities.NET_CAPABILITY_VALIDATED).build();
         doNothing().when(mMockConnectivityManager).registerNetworkCallback(request,
                 new ConnectivityManager.NetworkCallback());
+
+        PersistableBundle persistableBundle = new PersistableBundle();
+        persistableBundle.putBoolean(CarrierConfigManager
+                .KEY_CARRIER_SUPPORTS_OPP_DATA_AUTO_PROVISIONING_BOOL, true);
+        doReturn(persistableBundle).when(mMockCarrierConfigManager).getConfigForSubId(TEST_SUBID_1);
+    }
+
+    // Worker thread is used for testing asynchronous APIs and Message Handlers.
+    // ASync APIs are called and Handler messages are processed by Worker thread. Test results are
+    // verified by Main Thread.
+    static class WorkerThread extends Thread {
+        Looper mWorkerLooper;
+        private final Runnable mRunnable;
+
+        WorkerThread(Runnable runnable) {
+            mRunnable = runnable;
+        }
+
+        @Override
+        public void run() {
+            super.run();
+            Looper.prepare();
+            mWorkerLooper = Looper.myLooper();
+            mRunnable.run();
+            mWorkerLooper.loop();
+        }
+
+        public void exit() {
+            mWorkerLooper.quitSafely();
+        }
     }
 
     /*@Test
@@ -102,12 +140,13 @@
 
     @Test
     public void testONSAutoProvisioningDisabled() {
-        doReturn(TelephonyManager.SIM_STATE_READY).when(mMockTeleManager).getSimState();
+
         doReturn(false).when(mMockResources).getBoolean(R.bool.enable_ons_auto_provisioning);
 
         ONSProfileActivator onsProfileActivator = new ONSProfileActivator(mMockContext,
                 mMockSubManager, mMockTeleManager, mMockCarrierConfigManager, mMockEuiccManager,
-                mMockConnectivityManager, mMockONSProfileConfigurator, mMockONSProfileDownloader);
+                mMockConnectivityManager, mMockONSProfileConfigurator, mMockONSProfileDownloader,
+                mMockONSStats);
 
         assertEquals(ONSProfileActivator.Result.ERR_AUTO_PROVISIONING_DISABLED,
                 onsProfileActivator.handleCarrierConfigChange());
@@ -115,13 +154,14 @@
 
     @Test
     public void testESIMNotSupported() {
-        doReturn(TelephonyManager.SIM_STATE_READY).when(mMockTeleManager).getSimState();
+
         doReturn(true).when(mMockResources).getBoolean(R.bool.enable_ons_auto_provisioning);
         doReturn(false).when(mMockEuiccManager).isEnabled();
 
         ONSProfileActivator onsProfileActivator = new ONSProfileActivator(mMockContext,
                 mMockSubManager, mMockTeleManager, mMockCarrierConfigManager, mMockEuiccManager,
-                mMockConnectivityManager, mMockONSProfileConfigurator, mMockONSProfileDownloader);
+                mMockConnectivityManager, mMockONSProfileConfigurator, mMockONSProfileDownloader,
+                mMockONSStats);
 
         assertEquals(ONSProfileActivator.Result.ERR_ESIM_NOT_SUPPORTED,
                 onsProfileActivator.handleCarrierConfigChange());
@@ -130,7 +170,7 @@
     @Test
     //@DisplayName("Single SIM Device with eSIM support")
     public void testMultiSIMNotSupported() {
-        doReturn(TelephonyManager.SIM_STATE_READY).when(mMockTeleManager).getSimState();
+
         doReturn(true).when(mMockResources).getBoolean(R.bool.enable_ons_auto_provisioning);
         doReturn(true).when(mMockEuiccManager).isEnabled();
         doReturn(1).when(mMockTeleManager).getSupportedModemCount();
@@ -138,7 +178,8 @@
 
         ONSProfileActivator onsProfileActivator = new ONSProfileActivator(mMockContext,
                 mMockSubManager, mMockTeleManager, mMockCarrierConfigManager, mMockEuiccManager,
-                mMockConnectivityManager, mMockONSProfileConfigurator, mMockONSProfileDownloader);
+                mMockConnectivityManager, mMockONSProfileConfigurator, mMockONSProfileDownloader,
+                mMockONSStats);
 
         assertEquals(ONSProfileActivator.Result.ERR_MULTISIM_NOT_SUPPORTED,
                 onsProfileActivator.handleCarrierConfigChange());
@@ -146,7 +187,7 @@
 
     @Test
     public void testDeviceSwitchToDualSIMModeFailed() {
-        doReturn(TelephonyManager.SIM_STATE_READY).when(mMockTeleManager).getSimState();
+
         doReturn(true).when(mMockResources).getBoolean(R.bool.enable_ons_auto_provisioning);
         doReturn(true).when(mMockEuiccManager).isEnabled();
         doReturn(2).when(mMockTeleManager).getSupportedModemCount();
@@ -154,18 +195,14 @@
         doReturn(true).when(mMockTeleManager).doesSwitchMultiSimConfigTriggerReboot();
         doReturn(mMockactiveSubInfos).when(mMockSubManager).getActiveSubscriptionInfoList();
         doReturn(1).when(mMockactiveSubInfos).size();
-        doReturn(mMockSubInfo).when(mMockactiveSubInfos).get(0);
+        doReturn(mMockSubInfo).when(mMockactiveSubInfos).get(TEST_SUBID_0);
         doReturn(1).when(mMockSubInfo).getSubscriptionId();
         doReturn(false).when(mMockSubInfo).isOpportunistic();
 
-        PersistableBundle persistableBundle = new PersistableBundle();
-        persistableBundle.putBoolean(CarrierConfigManager
-                .KEY_CARRIER_SUPPORTS_OPP_DATA_AUTO_PROVISIONING_BOOL, true);
-        doReturn(persistableBundle).when(mMockCarrierConfigManager).getConfigForSubId(1);
-
         ONSProfileActivator onsProfileActivator = new ONSProfileActivator(mMockContext,
                 mMockSubManager, mMockTeleManager, mMockCarrierConfigManager, mMockEuiccManager,
-                mMockConnectivityManager, mMockONSProfileConfigurator, mMockONSProfileDownloader);
+                mMockConnectivityManager, mMockONSProfileConfigurator, mMockONSProfileDownloader,
+                mMockONSStats);
 
         assertEquals(ONSProfileActivator.Result.ERR_CANNOT_SWITCH_TO_DUAL_SIM_MODE,
                 onsProfileActivator.handleCarrierConfigChange());
@@ -173,26 +210,23 @@
 
     @Test
     public void testDeviceSwitchToDualSIMModeSuccess() {
-        doReturn(TelephonyManager.SIM_STATE_READY).when(mMockTeleManager).getSimState();
+
         doReturn(true).when(mMockResources).getBoolean(R.bool.enable_ons_auto_provisioning);
         doReturn(true).when(mMockEuiccManager).isEnabled();
         doReturn(2).when(mMockTeleManager).getSupportedModemCount();
         doReturn(1).when(mMockTeleManager).getActiveModemCount();
         doReturn(mMockactiveSubInfos).when(mMockSubManager).getActiveSubscriptionInfoList();
         doReturn(1).when(mMockactiveSubInfos).size();
-        doReturn(mMockSubInfo).when(mMockactiveSubInfos).get(0);
+        doReturn(mMockSubInfo).when(mMockactiveSubInfos).get(TEST_SUBID_0);
         doReturn(1).when(mMockSubInfo).getSubscriptionId();
         doReturn(false).when(mMockSubInfo).isOpportunistic();
 
-        PersistableBundle persistableBundle = new PersistableBundle();
-        persistableBundle.putBoolean(CarrierConfigManager
-                .KEY_CARRIER_SUPPORTS_OPP_DATA_AUTO_PROVISIONING_BOOL, true);
-        doReturn(persistableBundle).when(mMockCarrierConfigManager).getConfigForSubId(1);
         doReturn(false).when(mMockTeleManager).doesSwitchMultiSimConfigTriggerReboot();
 
         ONSProfileActivator onsProfileActivator = new ONSProfileActivator(mMockContext,
                 mMockSubManager, mMockTeleManager, mMockCarrierConfigManager, mMockEuiccManager,
-                mMockConnectivityManager, mMockONSProfileConfigurator, mMockONSProfileDownloader);
+                mMockConnectivityManager, mMockONSProfileConfigurator, mMockONSProfileDownloader,
+                mMockONSStats);
 
         assertEquals(ONSProfileActivator.Result.ERR_SWITCHING_TO_DUAL_SIM_MODE,
                 onsProfileActivator.handleCarrierConfigChange());
@@ -200,7 +234,7 @@
 
     //@DisplayName("Dual SIM device with no SIM inserted")
     public void testNoActiveSubscriptions() {
-        doReturn(TelephonyManager.SIM_STATE_READY).when(mMockTeleManager).getSimState();
+
         doReturn(true).when(mMockResources).getBoolean(R.bool.enable_ons_auto_provisioning);
         doReturn(true).when(mMockEuiccManager).isEnabled();
         doReturn(2).when(mMockTeleManager).getSupportedModemCount();
@@ -210,7 +244,8 @@
 
         ONSProfileActivator onsProfileActivator = new ONSProfileActivator(mMockContext,
                 mMockSubManager, mMockTeleManager, mMockCarrierConfigManager, mMockEuiccManager,
-                mMockConnectivityManager, mMockONSProfileConfigurator, mMockONSProfileDownloader);
+                mMockConnectivityManager, mMockONSProfileConfigurator, mMockONSProfileDownloader,
+                mMockONSStats);
 
         assertEquals(ONSProfileActivator.Result.ERR_NO_SIM_INSERTED,
                 onsProfileActivator.handleCarrierConfigChange());
@@ -219,7 +254,7 @@
     @Test
     //@DisplayName("Dual SIM device and non CBRS carrier pSIM inserted")
     public void testNonCBRSCarrierPSIMInserted() {
-        doReturn(TelephonyManager.SIM_STATE_READY).when(mMockTeleManager).getSimState();
+
         doReturn(true).when(mMockResources).getBoolean(R.bool.enable_ons_auto_provisioning);
         doReturn(true).when(mMockEuiccManager).isEnabled();
         doReturn(2).when(mMockTeleManager).getSupportedModemCount();
@@ -228,17 +263,18 @@
         PersistableBundle persistableBundle = new PersistableBundle();
         persistableBundle.putBoolean(CarrierConfigManager
                 .KEY_CARRIER_SUPPORTS_OPP_DATA_AUTO_PROVISIONING_BOOL, false);
-        doReturn(persistableBundle).when(mMockCarrierConfigManager).getConfigForSubId(1);
+        doReturn(persistableBundle).when(mMockCarrierConfigManager).getConfigForSubId(TEST_SUBID_0);
 
         doReturn(mMockactiveSubInfos).when(mMockSubManager).getActiveSubscriptionInfoList();
         doReturn(1).when(mMockactiveSubInfos).size();
-        doReturn(mMockSubInfo).when(mMockactiveSubInfos).get(0);
-        doReturn(1).when(mMockSubInfo).getSubscriptionId();
+        doReturn(mMockSubInfo).when(mMockactiveSubInfos).get(TEST_SUBID_0);
+        doReturn(TEST_SUBID_0).when(mMockSubInfo).getSubscriptionId();
         doReturn(false).when(mMockSubInfo).isOpportunistic();
 
         ONSProfileActivator onsProfileActivator = new ONSProfileActivator(mMockContext,
                 mMockSubManager, mMockTeleManager, mMockCarrierConfigManager, mMockEuiccManager,
-                mMockConnectivityManager, mMockONSProfileConfigurator, mMockONSProfileDownloader);
+                mMockConnectivityManager, mMockONSProfileConfigurator, mMockONSProfileDownloader,
+                mMockONSStats);
 
         assertEquals(ONSProfileActivator.Result.ERR_CARRIER_DOESNT_SUPPORT_CBRS,
                 onsProfileActivator.handleCarrierConfigChange());
@@ -247,7 +283,7 @@
     @Test
     //@DisplayName("Dual SIM device with Two PSIM active subscriptions")
     public void testTwoActivePSIMSubscriptions() {
-        doReturn(TelephonyManager.SIM_STATE_READY).when(mMockTeleManager).getSimState();
+
         doReturn(true).when(mMockResources).getBoolean(R.bool.enable_ons_auto_provisioning);
         doReturn(true).when(mMockEuiccManager).isEnabled();
         doReturn(2).when(mMockTeleManager).getSupportedModemCount();
@@ -255,69 +291,101 @@
 
         ArrayList<SubscriptionInfo> mActiveSubInfos = new ArrayList<>();
         mActiveSubInfos.add(mMockSubInfo);
-        mActiveSubInfos.add(mMockSubInfo2);
+        mActiveSubInfos.add(mMockSubInfo1);
         doReturn(mActiveSubInfos).when(mMockSubManager).getActiveSubscriptionInfoList();
         doReturn(false).when(mMockSubInfo).isEmbedded();
-        doReturn(false).when(mMockSubInfo2).isEmbedded();
+        doReturn(false).when(mMockSubInfo1).isEmbedded();
 
         ONSProfileActivator onsProfileActivator = new ONSProfileActivator(mMockContext,
                 mMockSubManager, mMockTeleManager, mMockCarrierConfigManager, mMockEuiccManager,
-                mMockConnectivityManager, mMockONSProfileConfigurator, mMockONSProfileDownloader);
+                mMockConnectivityManager, mMockONSProfileConfigurator, mMockONSProfileDownloader,
+                mMockONSStats);
 
         assertEquals(ONSProfileActivator.Result.ERR_DUAL_ACTIVE_SUBSCRIPTIONS,
                 onsProfileActivator.handleCarrierConfigChange());
     }
 
-    /*@Test
-    //Cannot mock/spy class android.os.PersistableBundle
-    public void testOneActivePSIMAndOneNonOpportunisticESIM() {
-        doReturn(true).when(mMockONSUtil).isESIMSupported();
-        doReturn(true).when(mMockONSUtil).isMultiSIMPhone();
+    @Test
+    public void testOneCBRSPSIMAndOneNonCBRSESIM() {
+        doReturn(true).when(mMockResources).getBoolean(R.bool.enable_ons_auto_provisioning);
+        doReturn(true).when(mMockEuiccManager).isEnabled();
+        doReturn(2).when(mMockTeleManager).getSupportedModemCount();
+        doReturn(2).when(mMockTeleManager).getActiveModemCount();
+
         ArrayList<SubscriptionInfo> mActiveSubInfos = new ArrayList<>();
+        mActiveSubInfos.add(mMockSubInfo);
         mActiveSubInfos.add(mMockSubInfo1);
-        mActiveSubInfos.add(mMockSubInfo2);
         doReturn(mActiveSubInfos).when(mMockSubManager).getActiveSubscriptionInfoList();
-        doReturn(false).when(mMockSubInfo1).isEmbedded();
-        doReturn(true).when(mMockSubInfo2).isEmbedded();
-        //0 - using carrier-id=0 to make sure it doesn't map to any opportunistic carrier-id
-        doReturn(0).when(mMockSubInfo2).getCarrierId();
+        doReturn(false).when(mMockSubInfo).isEmbedded();
+        doReturn(true).when(mMockSubInfo1).isEmbedded();
+        doReturn(TEST_SUBID_0).when(mMockSubInfo).getSubscriptionId();
+        doReturn(TEST_SUBID_1).when(mMockSubInfo1).getSubscriptionId();
+
+        PersistableBundle persistableBundle = new PersistableBundle();
+        persistableBundle.putBoolean(CarrierConfigManager
+                .KEY_CARRIER_SUPPORTS_OPP_DATA_AUTO_PROVISIONING_BOOL, false);
+        doReturn(persistableBundle).when(mMockCarrierConfigManager).getConfigForSubId(TEST_SUBID_0);
 
         ONSProfileActivator onsProfileActivator = new ONSProfileActivator(mMockContext,
-                mMockONSProfileConfigurator, mMockONSProfileDownloader);
+                mMockSubManager, mMockTeleManager, mMockCarrierConfigManager, mMockEuiccManager,
+                mMockConnectivityManager, mMockONSProfileConfigurator, mMockONSProfileDownloader,
+                mMockONSStats);
 
         assertEquals(ONSProfileActivator.Result.ERR_DUAL_ACTIVE_SUBSCRIPTIONS,
-                onsProfileActivator.handleSimStateChange());
-    }*/
+                onsProfileActivator.handleCarrierConfigChange());
+    }
 
-    /*@Test
-    //Cannot mock/spy class android.os.PersistableBundle
-    public void testOneActivePSIMAndOneOpportunisticESIM() {
-        doReturn(true).when(mMockONSUtil).isESIMSupported();
-        doReturn(true).when(mMockONSUtil).isMultiSIMPhone();
+    @Test
+    public void testOneCBRSPSIMAndOneOpportunisticESIM() {
+        doReturn(true).when(mMockResources).getBoolean(R.bool.enable_ons_auto_provisioning);
+        doReturn(true).when(mMockEuiccManager).isEnabled();
+        doReturn(2).when(mMockTeleManager).getSupportedModemCount();
+        doReturn(2).when(mMockTeleManager).getActiveModemCount();
+
         ArrayList<SubscriptionInfo> mActiveSubInfos = new ArrayList<>();
-        mActiveSubInfos.add(mMockSubInfo1);
-        mActiveSubInfos.add(mMockSubInfo2);
+        mActiveSubInfos.add(mMockSubInfo); //Primary CBRS SIM
+        mActiveSubInfos.add(mMockSubInfo1); //Opportunistic eSIM
         doReturn(mActiveSubInfos).when(mMockSubManager).getActiveSubscriptionInfoList();
-        doReturn(false).when(mMockSubInfo1).isEmbedded();
-        doReturn(true).when(mMockSubInfo2).isEmbedded();
-        doReturn(1).when(mMockSubInfo2).getSubscriptionId();
-        doReturn(mMockCarrierConfig).when(mMockCarrierConfigManager).getConfigForSubId(1);
-        doReturn(new int[]{1}).when(mMockCarrierConfig).get(
-                CarrierConfigManager.KEY_OPPORTUNISTIC_CARRIER_IDS_INT_ARRAY);
-        //1 - using carrier-id=1 to match with opportunistic carrier-id
-        doReturn(1).when(mMockSubInfo2).getCarrierId();
+        doReturn(mActiveSubInfos).when(mMockSubManager).getAvailableSubscriptionInfoList();
+
+        doReturn(mMockSubInfo).when(mMockSubManager).getActiveSubscriptionInfo(TEST_SUBID_0);
+        doReturn(TEST_SUBID_0).when(mMockSubInfo).getSubscriptionId();
+        doReturn(true).when(mMockSubManager).isActiveSubscriptionId(TEST_SUBID_0);
+        doReturn(false).when(mMockSubInfo).isOpportunistic();
+        doReturn(false).when(mMockSubInfo).isEmbedded();
+        ParcelUuid pSIMSubGroupId = new ParcelUuid(new UUID(0, 1));
+        doReturn(pSIMSubGroupId).when(mMockSubInfo).getGroupUuid();
+        PersistableBundle persistableBundle = new PersistableBundle();
+        persistableBundle.putBoolean(CarrierConfigManager
+                .KEY_CARRIER_SUPPORTS_OPP_DATA_AUTO_PROVISIONING_BOOL, true);
+        persistableBundle.putIntArray(CarrierConfigManager
+                .KEY_OPPORTUNISTIC_CARRIER_IDS_INT_ARRAY, new int[]{1, 2});
+        doReturn(persistableBundle).when(mMockCarrierConfigManager).getConfigForSubId(TEST_SUBID_0);
+
+        doReturn(mMockSubInfo1).when(mMockSubManager).getActiveSubscriptionInfo(TEST_SUBID_1);
+        doReturn(TEST_SUBID_1).when(mMockSubInfo1).getSubscriptionId();
+        doReturn(true).when(mMockSubManager).isActiveSubscriptionId(TEST_SUBID_1);
+        doReturn(true).when(mMockSubInfo1).isOpportunistic();
+        doReturn(true).when(mMockSubInfo1).isEmbedded();
+        doReturn(pSIMSubGroupId).when(mMockSubInfo1).getGroupUuid();
+        doReturn(1).when(mMockSubInfo1).getCarrierId();
+
+        doReturn(mMockSubInfo1).when(mMockONSProfileConfigurator)
+                .findOpportunisticSubscription(TEST_SUBID_0);
 
         ONSProfileActivator onsProfileActivator = new ONSProfileActivator(mMockContext,
-                mMockONSProfileConfigurator, mMockONSProfileDownloader);
+                mMockSubManager, mMockTeleManager, mMockCarrierConfigManager, mMockEuiccManager,
+                mMockConnectivityManager, mMockONSProfileConfigurator, mMockONSProfileDownloader,
+                mMockONSStats);
 
         assertEquals(ONSProfileActivator.Result.SUCCESS,
-                onsProfileActivator.handleSimStateChange());
-    }*/
+                onsProfileActivator.handleCarrierConfigChange());
+    }
 
     @Test
     //@DisplayName("Dual SIM device with only opportunistic eSIM active")
     public void testOnlyOpportunisticESIMActive() {
-        doReturn(TelephonyManager.SIM_STATE_READY).when(mMockTeleManager).getSimState();
+
         doReturn(true).when(mMockResources).getBoolean(R.bool.enable_ons_auto_provisioning);
         doReturn(true).when(mMockEuiccManager).isEnabled();
         doReturn(2).when(mMockTeleManager).getSupportedModemCount();
@@ -329,7 +397,8 @@
 
         ONSProfileActivator onsProfileActivator = new ONSProfileActivator(mMockContext,
                 mMockSubManager, mMockTeleManager, mMockCarrierConfigManager, mMockEuiccManager,
-                mMockConnectivityManager, mMockONSProfileConfigurator, mMockONSProfileDownloader);
+                mMockConnectivityManager, mMockONSProfileConfigurator, mMockONSProfileDownloader,
+                mMockONSStats);
 
         assertEquals(ONSProfileActivator.Result.ERR_SINGLE_ACTIVE_OPPORTUNISTIC_SIM,
                 onsProfileActivator.handleCarrierConfigChange());
@@ -338,30 +407,28 @@
     @Test
     //@DisplayName("Dual SIM device, only CBRS carrier pSIM inserted and pSIM not Grouped")
     public void testCBRSpSIMAndNotGrouped() {
-        doReturn(TelephonyManager.SIM_STATE_READY).when(mMockTeleManager).getSimState();
+
         doReturn(true).when(mMockResources).getBoolean(R.bool.enable_ons_auto_provisioning);
         doReturn(true).when(mMockEuiccManager).isEnabled();
         doReturn(2).when(mMockTeleManager).getSupportedModemCount();
         doReturn(2).when(mMockTeleManager).getActiveModemCount();
 
-        PersistableBundle persistableBundle = new PersistableBundle();
-        persistableBundle.putBoolean(CarrierConfigManager
-                .KEY_CARRIER_SUPPORTS_OPP_DATA_AUTO_PROVISIONING_BOOL, true);
-        doReturn(persistableBundle).when(mMockCarrierConfigManager).getConfigForSubId(1);
-
         doReturn(mMockactiveSubInfos).when(mMockSubManager).getActiveSubscriptionInfoList();
         doReturn(1).when(mMockactiveSubInfos).size();
         doReturn(mMockSubInfo).when(mMockactiveSubInfos).get(0);
         doReturn(false).when(mMockSubInfo).isOpportunistic();
-        doReturn(1).when(mMockSubInfo).getSubscriptionId();
+        doReturn(TEST_SUBID_1).when(mMockSubInfo).getSubscriptionId();
         doReturn(null).when(mMockSubInfo).getGroupUuid();
+        doReturn(ONSProfileDownloader.DownloadProfileResult.SUCCESS).when(mMockONSProfileDownloader)
+                .downloadProfile(TEST_SUBID_1);
 
         ONSProfileActivator onsProfileActivator = new ONSProfileActivator(mMockContext,
                 mMockSubManager, mMockTeleManager, mMockCarrierConfigManager, mMockEuiccManager,
-                mMockConnectivityManager, mMockONSProfileConfigurator, mMockONSProfileDownloader);
+                mMockConnectivityManager, mMockONSProfileConfigurator, mMockONSProfileDownloader,
+                mMockONSStats);
 
         onsProfileActivator.mIsInternetConnAvailable = true;
-        assertEquals(ONSProfileActivator.Result.SUCCESS,
+        assertEquals(ONSProfileActivator.Result.DOWNLOAD_REQUESTED,
                 onsProfileActivator.handleCarrierConfigChange());
     }
 
@@ -400,8 +467,7 @@
             }
         };
 
-        ONSProfileDownloaderTest.WorkerThread workerThread = new ONSProfileDownloaderTest
-                .WorkerThread(runnable);
+        WorkerThread workerThread = new WorkerThread(runnable);
         workerThread.start();
 
         synchronized (lock) {
@@ -441,7 +507,7 @@
         ONSProfileActivator onsProfileActivator =
                 new ONSProfileActivator(mMockContext, mMockSubManager, mMockEuiCCManager,
                 mMockTeleManager,
-                        mMockONSProfileConfigurator, mMockONSProfileDownloader);
+                        mMockONSProfileConfigurator, mMockONSProfileDownloader, mMockONSStats);
 
         assertEquals(ONSProfileActivator.Result.SUCCESS,
                 onsProfileActivator.handleSimStateChange());
@@ -516,7 +582,7 @@
         //TODO: mock ParcelUuid - pSIM group
 
         ONSProfileActivator onsProfileActivator = new ONSProfileActivator(mMockContext,
-                mMockONSProfileConfigurator, mMockONSProfileDownloader);
+                mMockONSProfileConfigurator, mMockONSProfileDownloader, mMockONSStats);
 
         assertEquals(ONSProfileActivator.Result.ERR_INVALID_PSIM_SUBID,
                 onsProfileActivator.retryDownloadAfterReboot());
@@ -537,8 +603,88 @@
         verify(mMockEUICCManager, never()).downloadSubscription(null, true, null);
     }*/
 
+    @Test
+    public void testESIMDownloadFailureAndRetry() {
+        doReturn(true).when(mMockResources).getBoolean(R.bool.enable_ons_auto_provisioning);
+        doReturn(true).when(mMockEuiccManager).isEnabled();
+        doReturn(2).when(mMockTeleManager).getSupportedModemCount();
+        doReturn(2).when(mMockTeleManager).getActiveModemCount();
+        doReturn(ONSProfileDownloader.DownloadProfileResult.SUCCESS).when(mMockONSProfileDownloader)
+            .downloadProfile(TEST_SUBID_0);
+
+        doReturn(mMockactiveSubInfos).when(mMockSubManager).getActiveSubscriptionInfoList();
+        doReturn(1).when(mMockactiveSubInfos).size();
+        doReturn(mMockSubInfo).when(mMockactiveSubInfos).get(0);
+        doReturn(false).when(mMockSubInfo).isOpportunistic();
+        doReturn(TEST_SUBID_0).when(mMockSubInfo).getSubscriptionId();
+        doReturn(null).when(mMockSubInfo).getGroupUuid();
+
+        final int maxRetryCount = 5;
+        final int retryBackoffTime = 1; //1 second
+
+        PersistableBundle persistableBundle = new PersistableBundle();
+        persistableBundle.putBoolean(CarrierConfigManager
+                .KEY_CARRIER_SUPPORTS_OPP_DATA_AUTO_PROVISIONING_BOOL, true);
+        persistableBundle.putInt(CarrierConfigManager
+                .KEY_ESIM_MAX_DOWNLOAD_RETRY_ATTEMPTS_INT, maxRetryCount);
+        persistableBundle.putInt(CarrierConfigManager
+                .KEY_ESIM_DOWNLOAD_RETRY_BACKOFF_TIMER_SEC_INT, retryBackoffTime);
+        doReturn(persistableBundle).when(mMockCarrierConfigManager).getConfigForSubId(TEST_SUBID_0);
+
+        final Object lock = new Object();
+        class TestRunnable implements Runnable {
+            public ONSProfileActivator mOnsProfileActivator;
+
+            @Override
+            public void run() {
+                mOnsProfileActivator = new ONSProfileActivator(mMockContext,
+                        mMockSubManager, mMockTeleManager, mMockCarrierConfigManager,
+                        mMockEuiccManager, mMockConnectivityManager, mMockONSProfileConfigurator,
+                        mMockONSProfileDownloader, mMockONSStats);
+
+                synchronized (lock) {
+                    lock.notify();
+                }
+            }
+        }
+
+        TestRunnable runnable = new TestRunnable();
+        WorkerThread workerThread = new WorkerThread(runnable);
+        workerThread.start();
+
+        synchronized (lock) {
+            try {
+                lock.wait();
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+
+        ONSProfileActivator onsProfileActivator = runnable.mOnsProfileActivator;
+        onsProfileActivator.mIsInternetConnAvailable = true;
+
+        for (int idx = 0; idx <= maxRetryCount; idx++) {
+            onsProfileActivator.onDownloadError(
+                    TEST_SUBID_0,
+                    ONSProfileDownloader.DownloadRetryResultCode.ERR_RETRY_DOWNLOAD, 0);
+
+            //Wait for Handler to process download message. Backoff delay + 500 milli secs.
+            try {
+                Thread.sleep(onsProfileActivator.calculateBackoffDelay(
+                        onsProfileActivator.mDownloadRetryCount, retryBackoffTime) + 1000);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+
+        workerThread.exit();
+
+        verify(mMockONSProfileDownloader, times(maxRetryCount)).downloadProfile(TEST_SUBID_0);
+    }
+
     @After
     public void tearDown() throws Exception {
         super.tearDown();
     }
-}
\ No newline at end of file
+}
+
diff --git a/tests/src/com/android/ons/ONSProfileConfiguratorTest.java b/tests/src/com/android/ons/ONSProfileConfiguratorTest.java
index 394a3ae..b583f73 100644
--- a/tests/src/com/android/ons/ONSProfileConfiguratorTest.java
+++ b/tests/src/com/android/ons/ONSProfileConfiguratorTest.java
@@ -97,6 +97,22 @@
     }
 
     @Test
+    public void testAlreadyGroupedSubscriptions() {
+        doReturn(TEST_SUB_ID).when(mMockSubscriptionInfo1).getSubscriptionId();
+        doReturn(true).when(mMockSubscriptionInfo1).isOpportunistic();
+
+        ONSProfileConfigurator mOnsProfileConfigurator = new ONSProfileConfigurator(mContext,
+                mMockSubManager, mMockCarrierConfigManager, mMockEuiccMngr, mMockConfigListener);
+
+        ParcelUuid uuid = new ParcelUuid(new UUID(1, 2));
+        doReturn(uuid).when(mMockSubscriptionInfo1).getGroupUuid();
+
+        mOnsProfileConfigurator.groupWithPSIMAndSetOpportunistic(mMockSubscriptionInfo1, uuid);
+
+        verifyNoMoreInteractions(mMockSubManager);
+    }
+
+    @Test
     public void testActivateSubscription() {
         ONSProfileConfigurator mOnsProfileConfigurator = new ONSProfileConfigurator(mContext,
                 mMockSubManager, mMockCarrierConfigManager, mMockEuiccMngr, mMockConfigListener);
diff --git a/tests/src/com/android/ons/ONSProfileDownloaderTest.java b/tests/src/com/android/ons/ONSProfileDownloaderTest.java
index f37ef1e..e82df6a 100644
--- a/tests/src/com/android/ons/ONSProfileDownloaderTest.java
+++ b/tests/src/com/android/ons/ONSProfileDownloaderTest.java
@@ -16,9 +16,12 @@
 
 package com.android.ons;
 
+import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 
@@ -33,6 +36,8 @@
 import android.util.Log;
 import android.util.Pair;
 
+import com.android.ons.ONSProfileDownloader.DownloadProfileResult;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -42,7 +47,7 @@
 public class ONSProfileDownloaderTest extends ONSBaseTest {
     private static final String TAG = ONSProfileDownloaderTest.class.getName();
     private static final int TEST_SUB_ID = 1;
-    private static final String TEST_SMDP_ADDRESS = "LPA:1$TEST-ESIM.COM$";
+    private static final String TEST_SMDP_ADDRESS = "TEST-ESIM.COM";
 
     @Mock
     Context mMockContext;
@@ -117,8 +122,9 @@
 
                     @Override
                     public void onDownloadError(
-                            ONSProfileDownloader.DownloadRetryOperationCode operationCode,
-                            int pSIMSubId) {
+                            int pSIMSubId,
+                            ONSProfileDownloader.DownloadRetryResultCode operationCode,
+                            int detailedErrorCode) {
 
                     }
                 };
@@ -233,8 +239,7 @@
         }
 
         verify(mMockDownloadListener).onDownloadError(
-                ONSProfileDownloader.DownloadRetryOperationCode.ERR_MEMORY_FULL,
-                TEST_SUB_ID);
+                TEST_SUB_ID, ONSProfileDownloader.DownloadRetryResultCode.ERR_MEMORY_FULL, 0);
         workerThread.exit();
     }
 
@@ -246,8 +251,7 @@
         config.putString(CarrierConfigManager.KEY_SMDP_SERVER_ADDRESS_STRING, TEST_SMDP_ADDRESS);
         doReturn(config).when(mMockCarrierConfigManager).getConfigForSubId(TEST_SUB_ID);
         doNothing().when(mMockDownloadListener).onDownloadError(
-                ONSProfileDownloader.DownloadRetryOperationCode.ERR_RETRY_DOWNLOAD,
-                TEST_SUB_ID);
+                TEST_SUB_ID, ONSProfileDownloader.DownloadRetryResultCode.ERR_RETRY_DOWNLOAD, 0);
 
         Runnable runnable = new Runnable() {
             @Override
@@ -288,8 +292,7 @@
                 .getEncodedActivationCode();
 
         verify(mMockDownloadListener).onDownloadError(
-                ONSProfileDownloader.DownloadRetryOperationCode.ERR_RETRY_DOWNLOAD,
-                TEST_SUB_ID);
+                TEST_SUB_ID, ONSProfileDownloader.DownloadRetryResultCode.ERR_RETRY_DOWNLOAD, 0);
 
         workerThread.exit();
     }
@@ -302,8 +305,7 @@
         config.putString(CarrierConfigManager.KEY_SMDP_SERVER_ADDRESS_STRING, TEST_SMDP_ADDRESS);
         doReturn(config).when(mMockCarrierConfigManager).getConfigForSubId(TEST_SUB_ID);
         doNothing().when(mMockDownloadListener).onDownloadError(
-                ONSProfileDownloader.DownloadRetryOperationCode.ERR_RETRY_DOWNLOAD,
-                TEST_SUB_ID);
+                TEST_SUB_ID, ONSProfileDownloader.DownloadRetryResultCode.ERR_RETRY_DOWNLOAD, 0);
 
         Runnable runnable = new Runnable() {
             @Override
@@ -344,8 +346,7 @@
                 .getEncodedActivationCode();
 
         verify(mMockDownloadListener).onDownloadError(
-                ONSProfileDownloader.DownloadRetryOperationCode.ERR_RETRY_DOWNLOAD,
-                TEST_SUB_ID);
+                TEST_SUB_ID, ONSProfileDownloader.DownloadRetryResultCode.ERR_RETRY_DOWNLOAD, 0);
 
         workerThread.exit();
     }
@@ -358,8 +359,7 @@
         config.putString(CarrierConfigManager.KEY_SMDP_SERVER_ADDRESS_STRING, TEST_SMDP_ADDRESS);
         doReturn(config).when(mMockCarrierConfigManager).getConfigForSubId(TEST_SUB_ID);
         doNothing().when(mMockDownloadListener).onDownloadError(
-                ONSProfileDownloader.DownloadRetryOperationCode.ERR_RETRY_DOWNLOAD,
-                TEST_SUB_ID);
+                TEST_SUB_ID, ONSProfileDownloader.DownloadRetryResultCode.ERR_RETRY_DOWNLOAD, 0);
 
         Runnable runnable = new Runnable() {
             @Override
@@ -400,8 +400,7 @@
                 .getEncodedActivationCode();
 
         verify(mMockDownloadListener).onDownloadError(
-                ONSProfileDownloader.DownloadRetryOperationCode.ERR_RETRY_DOWNLOAD,
-                TEST_SUB_ID);
+                TEST_SUB_ID, ONSProfileDownloader.DownloadRetryResultCode.ERR_RETRY_DOWNLOAD, 0);
 
         workerThread.exit();
     }
@@ -414,8 +413,7 @@
         config.putString(CarrierConfigManager.KEY_SMDP_SERVER_ADDRESS_STRING, TEST_SMDP_ADDRESS);
         doReturn(config).when(mMockCarrierConfigManager).getConfigForSubId(TEST_SUB_ID);
         doNothing().when(mMockDownloadListener).onDownloadError(
-                ONSProfileDownloader.DownloadRetryOperationCode.ERR_RETRY_DOWNLOAD,
-                TEST_SUB_ID);
+                TEST_SUB_ID, ONSProfileDownloader.DownloadRetryResultCode.ERR_RETRY_DOWNLOAD, 0);
 
         Runnable runnable = new Runnable() {
             @Override
@@ -450,8 +448,7 @@
         }
 
         verify(mMockDownloadListener).onDownloadError(
-                ONSProfileDownloader.DownloadRetryOperationCode.ERR_UNRESOLVABLE,
-                TEST_SUB_ID);
+                TEST_SUB_ID, ONSProfileDownloader.DownloadRetryResultCode.ERR_UNRESOLVABLE, 0);
         workerThread.exit();
     }
 
@@ -469,51 +466,51 @@
                 ONSProfileDownloader.DownloadHandler downloadHandler =
                         onsProfileDownloader.new DownloadHandler();
 
-                ONSProfileDownloader.DownloadRetryOperationCode res =
+                ONSProfileDownloader.DownloadRetryResultCode res =
                         downloadHandler.mapDownloaderErrorCode(
                         EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK, 0, 0, 0);
                 assertEquals(
-                        ONSProfileDownloader.DownloadRetryOperationCode.DOWNLOAD_SUCCESSFUL, res);
+                        ONSProfileDownloader.DownloadRetryResultCode.DOWNLOAD_SUCCESSFUL, res);
 
                 res = downloadHandler.mapDownloaderErrorCode(
                         EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR, 0,
                         EuiccManager.OPERATION_EUICC_GSMA,
                         EuiccManager.ERROR_EUICC_INSUFFICIENT_MEMORY);
-                assertEquals(ONSProfileDownloader.DownloadRetryOperationCode
+                assertEquals(ONSProfileDownloader.DownloadRetryResultCode
                         .ERR_MEMORY_FULL, res);
 
                 res = downloadHandler.mapDownloaderErrorCode(
                         EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR, 0,
                         EuiccManager.OPERATION_SIM_SLOT,
                         EuiccManager.ERROR_TIME_OUT);
-                assertEquals(ONSProfileDownloader.DownloadRetryOperationCode
+                assertEquals(ONSProfileDownloader.DownloadRetryResultCode
                         .ERR_RETRY_DOWNLOAD, res);
 
                 res = downloadHandler.mapDownloaderErrorCode(
                         EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR, 0,
                         EuiccManager.OPERATION_SMDX,
                         EuiccManager.ERROR_CONNECTION_ERROR);
-                assertEquals(ONSProfileDownloader.DownloadRetryOperationCode
+                assertEquals(ONSProfileDownloader.DownloadRetryResultCode
                         .ERR_RETRY_DOWNLOAD, res);
 
                 res = downloadHandler.mapDownloaderErrorCode(
                         EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR, 0,
                         EuiccManager.OPERATION_SMDX,
                         EuiccManager.ERROR_INVALID_RESPONSE);
-                assertEquals(ONSProfileDownloader.DownloadRetryOperationCode
+                assertEquals(ONSProfileDownloader.DownloadRetryResultCode
                         .ERR_UNRESOLVABLE, res);
 
                 res = downloadHandler.mapDownloaderErrorCode(
                         EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_ERROR, 0,
                         EuiccManager.OPERATION_SMDX,
                         EuiccManager.ERROR_INVALID_RESPONSE);
-                assertEquals(ONSProfileDownloader.DownloadRetryOperationCode
+                assertEquals(ONSProfileDownloader.DownloadRetryResultCode
                         .ERR_UNRESOLVABLE, res);
 
                 res = downloadHandler.mapDownloaderErrorCode(
                         EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_ERROR, 0xA810048,
                         EuiccManager.OPERATION_SMDX_SUBJECT_REASON_CODE, 0);
-                assertEquals(ONSProfileDownloader.DownloadRetryOperationCode
+                assertEquals(ONSProfileDownloader.DownloadRetryResultCode
                         .ERR_MEMORY_FULL, res);
 
                 synchronized (lock) {
@@ -595,6 +592,99 @@
         workerThread.exit();
     }
 
+    @Test
+    public void testMultipleDownloadRequests() {
+        doReturn(TEST_SUB_ID).when(mMockSubInfo).getSubscriptionId();
+        PersistableBundle config = new PersistableBundle();
+        config.putString(CarrierConfigManager.KEY_SMDP_SERVER_ADDRESS_STRING, TEST_SMDP_ADDRESS);
+        doReturn(config).when(mMockCarrierConfigManager).getConfigForSubId(TEST_SUB_ID);
+
+        ONSProfileDownloader onsProfileDownloader = new ONSProfileDownloader(mContext,
+                mMockCarrierConfigManager, mMockEUICCManager, mMockONSProfileConfig,
+                mMockDownloadListener);
+
+        //When multiple download requests are received, download should be triggered only once.
+        onsProfileDownloader.downloadProfile(mMockSubInfo.getSubscriptionId());
+        onsProfileDownloader.downloadProfile(mMockSubInfo.getSubscriptionId());
+        onsProfileDownloader.downloadProfile(mMockSubInfo.getSubscriptionId());
+        onsProfileDownloader.downloadProfile(mMockSubInfo.getSubscriptionId());
+        verify(mMockEUICCManager, times(1)).downloadSubscription(any(), eq(true), any());
+
+        //Simulate response for download request from LPA.
+        Intent intent = new Intent(mContext, ONSProfileResultReceiver.class);
+        intent.setAction(ONSProfileDownloader.ACTION_ONS_ESIM_DOWNLOAD);
+        intent.putExtra(Intent.EXTRA_COMPONENT_NAME, ONSProfileDownloader.class.getName());
+        intent.putExtra(ONSProfileDownloader.PARAM_PRIMARY_SUBID, TEST_SUB_ID);
+        intent.putExtra(ONSProfileDownloader.PARAM_REQUEST_TYPE,
+                ONSProfileDownloader.REQUEST_CODE_DOWNLOAD_SUB);
+        intent.putExtra(EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_OPERATION_CODE,
+                EuiccManager.OPERATION_DOWNLOAD);
+
+        onsProfileDownloader.onCallbackIntentReceived(intent,
+                EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK);
+
+        //Trigger new download after a sec
+        try {
+            Thread.sleep(1000);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        //After download response is received, new download requests should be processed.
+        onsProfileDownloader.downloadProfile(mMockSubInfo.getSubscriptionId());
+        onsProfileDownloader.downloadProfile(mMockSubInfo.getSubscriptionId());
+        onsProfileDownloader.downloadProfile(mMockSubInfo.getSubscriptionId());
+        onsProfileDownloader.downloadProfile(mMockSubInfo.getSubscriptionId());
+        verify(mMockEUICCManager, times(1)).downloadSubscription(any(), eq(true), any());
+    }
+
+    @Test
+    public void testDownloadRequestFailures() {
+        doReturn(TEST_SUB_ID).when(mMockSubInfo).getSubscriptionId();
+        PersistableBundle invalidConfig = new PersistableBundle();
+        invalidConfig.putString(CarrierConfigManager.KEY_SMDP_SERVER_ADDRESS_STRING, "");
+
+        PersistableBundle validConfig = new PersistableBundle();
+        validConfig.putString(
+                CarrierConfigManager.KEY_SMDP_SERVER_ADDRESS_STRING, TEST_SMDP_ADDRESS);
+
+        // Only the first download request, will receive invalid SMDP server address.
+        doReturn(invalidConfig, validConfig)
+                .when(mMockCarrierConfigManager)
+                .getConfigForSubId(TEST_SUB_ID);
+
+        ONSProfileDownloader onsProfileDownloader =
+                new ONSProfileDownloader(
+                        mContext,
+                        mMockCarrierConfigManager,
+                        mMockEUICCManager,
+                        mMockONSProfileConfig,
+                        mMockDownloadListener);
+
+        // First download request to be failed with INVALID_SMDP_ADDRESS error because of empty SMDP
+        // server address in configuration.
+        DownloadProfileResult retryResultCode =
+                onsProfileDownloader.downloadProfile(mMockSubInfo.getSubscriptionId());
+        assertEquals(DownloadProfileResult.INVALID_SMDP_ADDRESS, retryResultCode);
+
+        verify(mMockEUICCManager, never()).downloadSubscription(any(), eq(true), any());
+
+        // Second Download request should be success and processed to EuiccManager.
+        retryResultCode = onsProfileDownloader.downloadProfile(mMockSubInfo.getSubscriptionId());
+        assertEquals(DownloadProfileResult.SUCCESS, retryResultCode);
+        verify(mMockEUICCManager).downloadSubscription(any(), eq(true), any());
+
+        // Since download request is in progress, no further request to be sent to EuiccManager.
+        // They should return with DUPLICATE_REQUEST error.
+        retryResultCode = onsProfileDownloader.downloadProfile(mMockSubInfo.getSubscriptionId());
+        assertEquals(DownloadProfileResult.DUPLICATE_REQUEST, retryResultCode);
+
+        retryResultCode = onsProfileDownloader.downloadProfile(mMockSubInfo.getSubscriptionId());
+        assertEquals(DownloadProfileResult.DUPLICATE_REQUEST, retryResultCode);
+
+        verify(mMockEUICCManager).downloadSubscription(any(), eq(true), any());
+    }
+
     @After
     public void tearDown() throws Exception {
         super.tearDown();
diff --git a/tests/src/com/android/ons/ONSProfileSelectorTest.java b/tests/src/com/android/ons/ONSProfileSelectorTest.java
index e86726a..68925f7 100644
--- a/tests/src/com/android/ons/ONSProfileSelectorTest.java
+++ b/tests/src/com/android/ons/ONSProfileSelectorTest.java
@@ -25,11 +25,16 @@
 import android.os.ServiceManager;
 import android.telephony.AvailableNetworkInfo;
 import android.telephony.CellIdentityLte;
+import android.telephony.CellIdentityNr;
 import android.telephony.CellInfo;
 import android.telephony.CellInfoLte;
+import android.telephony.CellInfoNr;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.telephony.UiccCardInfo;
+import android.telephony.UiccPortInfo;
+import android.telephony.euicc.EuiccManager;
 import android.util.Log;
 
 import com.android.internal.telephony.ISub;
@@ -43,6 +48,7 @@
 
 import java.lang.reflect.Field;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
@@ -54,6 +60,8 @@
     private int mDataSubId;
     private int mResult;
     @Mock
+    EuiccManager mMockEuiccManager;
+    @Mock
     ONSNetworkScanCtlr mONSNetworkScanCtlr;
     @Mock
     TelephonyManager mSubscriptionBoundTelephonyManager;
@@ -160,6 +168,18 @@
         ArrayList<AvailableNetworkInfo> availableNetworkInfos = new ArrayList<AvailableNetworkInfo>();
         availableNetworkInfos.add(availableNetworkInfo);
 
+        UiccPortInfo uiccPortInfo = new UiccPortInfo("", 1, 1, false);
+        ArrayList<UiccPortInfo> uiccPortInfoList = new ArrayList<>();
+        uiccPortInfoList.add(uiccPortInfo);
+
+        UiccCardInfo uiccCardInfo = new UiccCardInfo(true, 1, "", 0, false, true, uiccPortInfoList);
+        ArrayList<UiccCardInfo> uiccCardInfoList = new ArrayList<>();
+        uiccCardInfoList.add(uiccCardInfo);
+
+        doReturn(uiccCardInfoList).when(mMockTelephonyManager).getUiccCardsInfo();
+        doReturn(mMockEuiccManager).when(mMockEuiccManager).createForCardId(1);
+        doReturn(true).when(mMockEuiccManager).isSimPortAvailable(1);
+
         IUpdateAvailableNetworksCallback mCallback = new IUpdateAvailableNetworksCallback.Stub() {
             @Override
             public void onComplete(int result) {
@@ -180,6 +200,8 @@
                     .getOpportunisticSubscriptions();
                 mONSProfileSelector = new MyONSProfileSelector(mContext,
                     mONSProfileSelectionCallback);
+                mONSProfileSelector.mTelephonyManager = mMockTelephonyManager;
+                mONSProfileSelector.mEuiccManager = mMockEuiccManager;
                 mONSProfileSelector.updateOppSubs();
                 mONSProfileSelector.startProfileSelection(availableNetworkInfos, mCallback);
                 mLooper = Looper.myLooper();
@@ -203,13 +225,17 @@
     @Test
     public void testStartProfileSelectionSuccess() {
         int subId = 5;
-        List<SubscriptionInfo> subscriptionInfoList = new ArrayList<SubscriptionInfo>();
-        SubscriptionInfo subscriptionInfo = new SubscriptionInfo(subId, "", 1, "TMO", "TMO", 1, 1,
-            "123", 1, null, "310", "210", "", false, null, "1");
-        SubscriptionInfo subscriptionInfo2 = new SubscriptionInfo(5, "", 1, "TMO", "TMO", 1, 1,
-            "123", 1, null, "310", "211", "", false, null, "1");
-        subscriptionInfoList.add(subscriptionInfo);
-        doReturn(subscriptionInfo).when(mSubscriptionManager).getActiveSubscriptionInfo(subId);
+        List<SubscriptionInfo> activeSubscriptionInfoList = new ArrayList<SubscriptionInfo>();
+        List<SubscriptionInfo> oppSubscriptionInfoList = new ArrayList<SubscriptionInfo>();
+        SubscriptionInfo subscriptionInfo1 = new SubscriptionInfo(subId, "", 1, "TMO", "TMO", 1, 1,
+                "123", 1, null, "310", "210", "", true, null, "1", 1, true, null, false, 1, 1, 1,
+                null, null, false, 0);
+        SubscriptionInfo subscriptionInfo2 = new SubscriptionInfo(6, "", 1, "TMO", "TMO", 1, 1,
+                "123", 1, null, "310", "211", "", true, null, "1", 1, false, null, false, 1, 1, 1,
+                null, null, false, 0);
+        oppSubscriptionInfoList.add(subscriptionInfo1);
+        activeSubscriptionInfoList.add(subscriptionInfo1);
+        activeSubscriptionInfoList.add(subscriptionInfo2);
 
         List<CellInfo> results2 = new ArrayList<CellInfo>();
         CellIdentityLte cellIdentityLte = new CellIdentityLte(310, 210, 1, 1, 1);
@@ -236,8 +262,14 @@
             @Override
             public void run() {
                 Looper.prepare();
-                doReturn(subscriptionInfoList).when(mSubscriptionManager)
+                doReturn(subscriptionInfo1).when(mSubscriptionManager)
+                        .getActiveSubscriptionInfo(subId);
+                doReturn(oppSubscriptionInfoList).when(mSubscriptionManager)
                     .getOpportunisticSubscriptions();
+                doReturn(activeSubscriptionInfoList).when(mSubscriptionManager)
+                        .getActiveSubscriptionInfoList();
+                doReturn(activeSubscriptionInfoList).when(mSubscriptionManager)
+                        .getCompleteActiveSubscriptionInfoList();
                 doReturn(true).when(mSubscriptionManager).isActiveSubId(subId);
                 doReturn(true).when(mSubscriptionBoundTelephonyManager).enableModemForSlot(
                     anyInt(), anyBoolean());
@@ -280,13 +312,18 @@
 
         List<SubscriptionInfo> subscriptionInfoList = new ArrayList<SubscriptionInfo>();
         SubscriptionInfo subscriptionInfo = new SubscriptionInfo(5, "", 1, "TMO", "TMO", 1, 1,
-                "123", 1, null, "310", "210", "", false, null, "1");
+                "123", 1, null, "310", "210", "", true, null, "1", 1, true, null, false, 1, 1, 1,
+                null, null, false, 0);
         subscriptionInfoList.add(subscriptionInfo);
         SubscriptionInfo subscriptionInfo_2 = new SubscriptionInfo(8, "", 1, "Vzw", "Vzw", 1, 1,
-                "123", 1, null, "311", "480", "", false, null, "1");
+                "456", 1, null, "311", "480", "", true, null, "1", 1, true, null, false, 1, 1, 1,
+                null, null, false, 1);
         subscriptionInfoList.add(subscriptionInfo_2);
         doReturn(subscriptionInfo).when(mSubscriptionManager).getActiveSubscriptionInfo(5);
         doReturn(subscriptionInfo_2).when(mSubscriptionManager).getActiveSubscriptionInfo(8);
+        doReturn(subscriptionInfoList).when(mSubscriptionManager)
+                .getCompleteActiveSubscriptionInfoList();
+        doReturn(subscriptionInfoList).when(mSubscriptionManager).getOpportunisticSubscriptions();
 
         List<CellInfo> results2 = new ArrayList<CellInfo>();
         CellIdentityLte cellIdentityLte = new CellIdentityLte(310, 210, 1, 1, 1);
@@ -323,8 +360,6 @@
             @Override
             public void run() {
                 Looper.prepare();
-                doReturn(subscriptionInfoList).when(mSubscriptionManager)
-                        .getOpportunisticSubscriptions();
                 doReturn(true).when(mSubscriptionManager).isActiveSubId(anyInt());
                 doReturn(true).when(mSubscriptionBoundTelephonyManager).enableModemForSlot(
                         anyInt(), anyBoolean());
@@ -354,13 +389,16 @@
     public void testStartProfileSelectionWithActivePrimarySimOnESim() {
         List<SubscriptionInfo> opportunisticSubscriptionInfoList = new ArrayList<SubscriptionInfo>();
         List<SubscriptionInfo> activeSubscriptionInfoList = new ArrayList<SubscriptionInfo>();
-        SubscriptionInfo subscriptionInfo = new SubscriptionInfo(5, "", 1, "TMO", "TMO", 1, 1,
-            "123", 1, null, "310", "210", "", true, null, "1", true, null, 1839, 1);
+        SubscriptionInfo subscriptionInfo1 = new SubscriptionInfo(5, "", 1, "TMO", "TMO", 1, 1,
+                "123", 1, null, "310", "210", "", true, null, "1", 1, true, null, false, 1839, 1,
+                1, null, null, false, 1);
         SubscriptionInfo subscriptionInfo2 = new SubscriptionInfo(6, "", 1, "TMO", "TMO", 1, 1,
-            "123", 1, null, "310", "211", "", true, null, "1", false, null, 1839, 1);
-        opportunisticSubscriptionInfoList.add(subscriptionInfo);
+                "456", 1, null, "310", "211", "", true, null, "1", 1, false, null, false, 1839, 1,
+                1, null, null, false, 2);
+
+        activeSubscriptionInfoList.add(subscriptionInfo1);
         activeSubscriptionInfoList.add(subscriptionInfo2);
-        doReturn(subscriptionInfo).when(mSubscriptionManager).getActiveSubscriptionInfo(5);
+        doReturn(subscriptionInfo1).when(mSubscriptionManager).getActiveSubscriptionInfo(5);
         doReturn(subscriptionInfo2).when(mSubscriptionManager).getActiveSubscriptionInfo(6);
 
         ArrayList<String> mccMncs = new ArrayList<>();
@@ -370,6 +408,19 @@
         ArrayList<AvailableNetworkInfo> availableNetworkInfos = new ArrayList<AvailableNetworkInfo>();
         availableNetworkInfos.add(availableNetworkInfo);
 
+        ArrayList<UiccPortInfo> uiccPortInfoList = new ArrayList<>();
+        uiccPortInfoList.add(new UiccPortInfo("1", 0, 0, false));
+        uiccPortInfoList.add(new UiccPortInfo("2", 1, 1, true));
+
+        UiccCardInfo uiccCardInfo = new UiccCardInfo(
+                true, 1, "1", 0, false, true, uiccPortInfoList);
+        ArrayList<UiccCardInfo> uiccCardInfoList = new ArrayList<>();
+        uiccCardInfoList.add(uiccCardInfo);
+
+        doReturn(uiccCardInfoList).when(mMockTelephonyManager).getUiccCardsInfo();
+        doReturn(mMockEuiccManager).when(mMockEuiccManager).createForCardId(1);
+        doReturn(true).when(mMockEuiccManager).isSimPortAvailable(1);
+
         IUpdateAvailableNetworksCallback mCallback = new IUpdateAvailableNetworksCallback.Stub() {
             @Override
             public void onComplete(int result) {
@@ -388,12 +439,18 @@
                 doReturn(false).when(mSubscriptionManager).isActiveSubId(anyInt());
                 doReturn(activeSubscriptionInfoList).when(mSubscriptionManager)
                     .getActiveSubscriptionInfoList(anyBoolean());
+                doReturn(activeSubscriptionInfoList).when(mSubscriptionManager)
+                        .getActiveSubscriptionInfoList();
+                doReturn(true).when(mSubscriptionBoundTelephonyManager).enableModemForSlot(
+                        anyInt(), anyBoolean());
                 mONSProfileSelector = new MyONSProfileSelector(mContext,
                     new MyONSProfileSelector.ONSProfileSelectionCallback() {
                         public void onProfileSelectionDone() {
                             setReady(true);
                         }
                     });
+                mONSProfileSelector.mTelephonyManager = mMockTelephonyManager;
+                mONSProfileSelector.mEuiccManager = mMockEuiccManager;
                 mONSProfileSelector.updateOppSubs();
                 mONSProfileSelector.startProfileSelection(availableNetworkInfos, mCallback);
                 mLooper = Looper.myLooper();
@@ -415,7 +472,8 @@
         callbackIntent.putExtra("sequenceId", 1);
         callbackIntent.putExtra("subId", 5);
         waitUntilReady();
-        assertEquals(TelephonyManager.UPDATE_AVAILABLE_NETWORKS_INVALID_ARGUMENTS, mResult);
+        assertEquals(TelephonyManager.UPDATE_AVAILABLE_NETWORKS_NO_OPPORTUNISTIC_SUB_AVAILABLE,
+                mResult);
     }
 
     public static void waitForMs(long ms) {
@@ -559,15 +617,24 @@
 
     @Test
     public void testStartProfileSelectionSuccessWithSameArgumentsAgain() {
-        List<SubscriptionInfo> subscriptionInfoList = new ArrayList<SubscriptionInfo>();
-        SubscriptionInfo subscriptionInfo = new SubscriptionInfo(5, "", 1, "TMO", "TMO", 1, 1,
-            "123", 1, null, "310", "210", "", false, null, "1");
+        List<SubscriptionInfo> activeSubscriptionInfoList = new ArrayList<SubscriptionInfo>();
+        List<SubscriptionInfo> oppSubscriptionInfoList = new ArrayList<SubscriptionInfo>();
+        SubscriptionInfo subscriptionInfo1 = new SubscriptionInfo(5, "", 1, "TMO", "TMO", 1, 1,
+                "123", 1, null, "310", "210", "", true, null, "1", 1, true, null, false, 1, 1, 1,
+                null, null, false, 0);
         SubscriptionInfo subscriptionInfo2 = new SubscriptionInfo(6, "", 1, "TMO", "TMO", 1, 1,
-            "123", 1, null, "310", "211", "", false, null, "1");
-        subscriptionInfoList.add(subscriptionInfo);
-        doReturn(subscriptionInfo).when(mSubscriptionManager).getActiveSubscriptionInfo(5);
+                "123", 1, null, "310", "211", "", true, null, "1", 1, false, null, false, 1, 1, 1,
+                null, null, false, 0);
+
+        oppSubscriptionInfoList.add(subscriptionInfo1);
+        doReturn(subscriptionInfo1).when(mSubscriptionManager).getActiveSubscriptionInfo(5);
         doReturn(subscriptionInfo2).when(mSubscriptionManager).getActiveSubscriptionInfo(6);
 
+        activeSubscriptionInfoList.add(subscriptionInfo1);
+        activeSubscriptionInfoList.add(subscriptionInfo2);
+        doReturn(activeSubscriptionInfoList).when(mSubscriptionManager)
+                .getCompleteActiveSubscriptionInfoList();
+
         List<CellInfo> results2 = new ArrayList<CellInfo>();
         CellIdentityLte cellIdentityLte = new CellIdentityLte(310, 210, 1, 1, 1);
         CellInfoLte cellInfoLte = new CellInfoLte();
@@ -599,7 +666,7 @@
             @Override
             public void run() {
                 Looper.prepare();
-                doReturn(subscriptionInfoList).when(mSubscriptionManager)
+                doReturn(oppSubscriptionInfoList).when(mSubscriptionManager)
                     .getOpportunisticSubscriptions();
                 doReturn(true).when(mSubscriptionManager).isActiveSubId(anyInt());
                 doReturn(true).when(mSubscriptionBoundTelephonyManager).enableModemForSlot(
@@ -636,7 +703,7 @@
             @Override
             public void run() {
                 Looper.prepare();
-                doReturn(subscriptionInfoList).when(mSubscriptionManager)
+                doReturn(oppSubscriptionInfoList).when(mSubscriptionManager)
                     .getOpportunisticSubscriptions();
                 doReturn(true).when(mSubscriptionManager).isActiveSubId(anyInt());
                 doReturn(true).when(mSubscriptionBoundTelephonyManager).enableModemForSlot(
@@ -759,6 +826,233 @@
         }).start();
         waitUntilReady();
         waitForMs(500);
-        assertEquals(mONSProfileSelector.getCurrentPreferredData(), 5);
+        assertEquals(5, mONSProfileSelector.getCurrentPreferredData());
+    }
+
+    @Test
+    public void testAvailablePortWhenTwoPrimarySIMsAreActive() {
+        /**
+         * 2 - Primary active subscriptions and
+         * 1 - Inactive opportunistic subscription
+         */
+
+        List<SubscriptionInfo> activeSubscriptionInfoList = new ArrayList<SubscriptionInfo>();
+        List<SubscriptionInfo> opportunisticInfoList = new ArrayList<SubscriptionInfo>();
+
+        SubscriptionInfo oppSubInfo = new SubscriptionInfo(4, "", -1, "TMO", "TMO", 1, 1,
+                "001", 1, null, "110", "210", "", true, null, "1", 1, true, null, false, 2839, 1,
+                1, null, null, false, TelephonyManager.INVALID_PORT_INDEX);
+
+        SubscriptionInfo primarySubInfo1 = new SubscriptionInfo(5, "", 0, "TMO", "TMO", 1, 1,
+                "123", 1, null, "310", "210", "", true, null, "1", 1, false, null, false, 1839, 1,
+                1, null, null, false, 0);
+        SubscriptionInfo primarySubInfo2 = new SubscriptionInfo(6, "", 0, "TMO", "TMO", 1, 1,
+                "456", 1, null, "310", "211", "", true, null, "1", 1, false, null, false, 1839, 1,
+                1, null, null, false, 1);
+
+        activeSubscriptionInfoList.add(primarySubInfo1);
+        activeSubscriptionInfoList.add(primarySubInfo2);
+        opportunisticInfoList.add(oppSubInfo);
+
+        doReturn(opportunisticInfoList).when(mSubscriptionManager).getOpportunisticSubscriptions();
+        doReturn(activeSubscriptionInfoList).when(mSubscriptionManager)
+                .getCompleteActiveSubscriptionInfoList();
+
+        UiccPortInfo uiccPortInfo1 = new UiccPortInfo("", 0, 0, true);
+        UiccPortInfo uiccPortInfo2 = new UiccPortInfo("", 1, 0, true);
+        ArrayList<UiccPortInfo> uiccPortInfoList = new ArrayList<>();
+        uiccPortInfoList.add(uiccPortInfo1);
+        uiccPortInfoList.add(uiccPortInfo2);
+
+        UiccCardInfo uiccCardInfo = new UiccCardInfo(true, 1, "", 0, false, true, uiccPortInfoList);
+        ArrayList<UiccCardInfo> uiccCardInfoList = new ArrayList<>();
+        uiccCardInfoList.add(uiccCardInfo);
+
+        doReturn(uiccCardInfoList).when(mMockTelephonyManager).getUiccCardsInfo();
+        doReturn(mMockEuiccManager).when(mMockEuiccManager).createForCardId(1);
+        doReturn(false).when(mMockEuiccManager).isSimPortAvailable(0);
+        doReturn(false).when(mMockEuiccManager).isSimPortAvailable(1);
+
+        mONSProfileSelector = new MyONSProfileSelector(mContext, null);
+        mONSProfileSelector.mTelephonyManager = mMockTelephonyManager;
+        mONSProfileSelector.mEuiccManager = mMockEuiccManager;
+
+        int portIdx = mONSProfileSelector.getAvailableESIMPortIndex();
+        assertEquals(TelephonyManager.INVALID_PORT_INDEX, portIdx);
+    }
+
+    @Test
+    public void testAvailablePortWhenOpportunisticEsimIsActive() {
+        /**
+         * 1 - Primary active subscriptions and
+         * 1 - Active opportunistic subscription
+         */
+
+        List<SubscriptionInfo> activeSubscriptionInfoList = new ArrayList<SubscriptionInfo>();
+        List<SubscriptionInfo> opportunisticInfoList = new ArrayList<SubscriptionInfo>();
+
+        SubscriptionInfo oppSubInfo = new SubscriptionInfo(5, "", 1, "TMO", "TMO", 1, 1,
+                "123", 1, null, "310", "210", "", true, null, "1", 1, true, null, false, 1839, 1,
+                1, null, null, false, 0);
+
+        SubscriptionInfo primarySubInfo = new SubscriptionInfo(6, "", 1, "TMO", "TMO", 1, 1,
+                "456", 1, null, "310", "211", "", true, null, "1", 1, false, null, false, 1839, 1,
+                1, null, null, false, 1);
+
+        opportunisticInfoList.add(oppSubInfo);
+        activeSubscriptionInfoList.add(oppSubInfo);
+        activeSubscriptionInfoList.add(primarySubInfo);
+
+        doReturn(opportunisticInfoList).when(mSubscriptionManager)
+                .getOpportunisticSubscriptions();
+        doReturn(activeSubscriptionInfoList).when(mSubscriptionManager)
+                .getCompleteActiveSubscriptionInfoList();
+
+        mONSProfileSelector = new MyONSProfileSelector(mContext, null);
+        int portIdx = mONSProfileSelector.getAvailableESIMPortIndex();
+        assertEquals(0, portIdx);
+    }
+
+    @Test
+    public void testAvailablePortWhenTwoOpportunisticEsimsAreActive() {
+        /**
+         * 2 - Active opportunistic subscriptions.
+         */
+
+        List<SubscriptionInfo> activeSubscriptionInfoList = new ArrayList<SubscriptionInfo>();
+        List<SubscriptionInfo> opportunisticInfoList = new ArrayList<SubscriptionInfo>();
+
+        SubscriptionInfo opportunisticSubInfo1 = new SubscriptionInfo(5, "", 1, "TMO", "TMO", 1, 1,
+                "123", 1, null, "310", "210", "", true, null, "1", 1, true, null, false, 1839, 1,
+                1, null, null, false, 0);
+
+        SubscriptionInfo opportunisticSubInfo2 = new SubscriptionInfo(6, "", 1, "TMO", "TMO", 1, 1,
+                "456", 1, null, "310", "211", "", true, null, "1", 1, true, null, false, 1839, 1,
+                1, null, null, false, 1);
+
+        opportunisticInfoList.add(opportunisticSubInfo1);
+        opportunisticInfoList.add(opportunisticSubInfo2);
+        activeSubscriptionInfoList.add(opportunisticSubInfo1);
+        activeSubscriptionInfoList.add(opportunisticSubInfo2);
+
+        doReturn(opportunisticInfoList).when(mSubscriptionManager)
+                .getOpportunisticSubscriptions();
+        doReturn(activeSubscriptionInfoList).when(mSubscriptionManager)
+                .getCompleteActiveSubscriptionInfoList();
+
+        mONSProfileSelector = new MyONSProfileSelector(mContext, null);
+        int portIdx = mONSProfileSelector.getAvailableESIMPortIndex();
+
+        /* one of the opportunistic eSIM port should be selected */
+        assertTrue(portIdx == 0 || portIdx == 1);
+    }
+
+    @Test
+    public void testAvailablePortWhenOpportunisticEsimIsActiveAndInactiveSubscriptions() {
+        /**
+         * 1 - Primary active subscription and
+         * 1 - Active opportunistic subscription and
+         * 2 - Inactive opportunistic subscriptions
+         */
+
+        List<SubscriptionInfo> activeSubscriptionInfoList = new ArrayList<SubscriptionInfo>();
+        List<SubscriptionInfo> opportunisticInfoList = new ArrayList<SubscriptionInfo>();
+
+        SubscriptionInfo opportunisticSubInfo1 = new SubscriptionInfo(5, "", 1, "TMO", "TMO", 1, 1,
+                "123", 1, null, "310", "210", "", true, null, "1", 1, true, null, false, 1839, 1,
+                1, null, null, false, 1);
+        SubscriptionInfo primarySubInfo = new SubscriptionInfo(6, "", 1, "TMO", "TMO", 1, 1,
+                "456", 1, null, "310", "211", "", true, null, "1", 1, false, null, false, 1839, 1,
+                1, null, null, false, 0);
+
+        SubscriptionInfo opportunisticSubInfo2 = new SubscriptionInfo(7, "", 1, "TMO", "TMO", 1, 1,
+                "789", 1, null, "310", "210", "", true, null, "1", 1, true, null, false, 1839, 1,
+                1, null, null, false, TelephonyManager.INVALID_PORT_INDEX);
+
+        SubscriptionInfo oppSubInfo3 = new SubscriptionInfo(8, "", 1, "TMO", "TMO", 1, 1,
+                "012", 1, null, "310", "210", "", true, null, "1", 1, true, null, false, 1839, 1,
+                1, null, null, false, TelephonyManager.INVALID_PORT_INDEX);
+
+        opportunisticInfoList.add(opportunisticSubInfo1);
+        opportunisticInfoList.add(opportunisticSubInfo2);
+        opportunisticInfoList.add(oppSubInfo3);
+        activeSubscriptionInfoList.add(opportunisticSubInfo1);
+        activeSubscriptionInfoList.add(primarySubInfo);
+
+        doReturn(opportunisticInfoList).when(mSubscriptionManager)
+                .getOpportunisticSubscriptions();
+        doReturn(activeSubscriptionInfoList).when(mSubscriptionManager)
+                .getCompleteActiveSubscriptionInfoList();
+
+        mONSProfileSelector = new MyONSProfileSelector(mContext, null);
+        int portIdx = mONSProfileSelector.getAvailableESIMPortIndex();
+        assertEquals(1, portIdx);
+    }
+
+    @Test
+    public void testAvailablePortWhenOnlyInactiveSubscriptions() {
+        /**
+         * 1 - Primary inactive subscription and
+         * 2 - Inactive opportunistic subscriptions
+         */
+
+        List<SubscriptionInfo> activeSubscriptionInfoList = new ArrayList<SubscriptionInfo>();
+        List<SubscriptionInfo> opportunisticInfoList = new ArrayList<SubscriptionInfo>();
+
+        SubscriptionInfo oppSubInfo1 = new SubscriptionInfo(5, "", 1, "TMO", "TMO", 1, 1,
+                "123", 1, null, "310", "210", "", true, null, "1", 1, true, null, false, 1839, 1,
+                1, null, null, false, TelephonyManager.INVALID_PORT_INDEX);
+
+        // Not used in activeSubscriptionInfoList or opportunisticInfoList
+        /*SubscriptionInfo primarySubInfo = new SubscriptionInfo(6, "", 1, "TMO", "TMO", 1, 1,
+                "456", 1, null, "310", "211", "", true, null, "1", 1, false, null, false, 1839, 1,
+                1, null, null, false, 2);*/
+
+        SubscriptionInfo oppSubInfo2 = new SubscriptionInfo(7, "", 1, "TMO", "TMO", 1, 1,
+                "789", 1, null, "310", "210", "", true, null, "1", 1, true, null, false, 1839, 1,
+                1, null, null, false, TelephonyManager.INVALID_PORT_INDEX);
+
+        opportunisticInfoList.add(oppSubInfo1);
+        opportunisticInfoList.add(oppSubInfo2);
+
+        doReturn(opportunisticInfoList).when(mSubscriptionManager)
+                .getOpportunisticSubscriptions();
+        doReturn(activeSubscriptionInfoList).when(mSubscriptionManager)
+                .getCompleteActiveSubscriptionInfoList();
+
+        UiccPortInfo uiccPortInfo1 = new UiccPortInfo("", 0, 0, false);
+        UiccPortInfo uiccPortInfo2 = new UiccPortInfo("", 1, 0, false);
+        ArrayList<UiccPortInfo> uiccPortInfoList = new ArrayList<>();
+        uiccPortInfoList.add(uiccPortInfo1);
+        uiccPortInfoList.add(uiccPortInfo2);
+
+        UiccCardInfo uiccCardInfo = new UiccCardInfo(true, 1, "", 0, false, true, uiccPortInfoList);
+        ArrayList<UiccCardInfo> uiccCardInfoList = new ArrayList<>();
+        uiccCardInfoList.add(uiccCardInfo);
+
+        doReturn(uiccCardInfoList).when(mMockTelephonyManager).getUiccCardsInfo();
+        doReturn(mMockEuiccManager).when(mMockEuiccManager).createForCardId(1);
+        doReturn(true).when(mMockEuiccManager).isSimPortAvailable(0);
+        doReturn(true).when(mMockEuiccManager).isSimPortAvailable(1);
+
+        mONSProfileSelector = new MyONSProfileSelector(mContext, null);
+        mONSProfileSelector.mTelephonyManager = mMockTelephonyManager;
+        mONSProfileSelector.mEuiccManager = mMockEuiccManager;
+
+        int portIdx = mONSProfileSelector.getAvailableESIMPortIndex();
+        assertTrue(portIdx == 0 || portIdx == 1);
+    }
+
+    @Test
+    public void testGetMncMccFromCellInfoNr() {
+        mONSProfileSelector = new MyONSProfileSelector(mContext, null);
+
+        CellIdentityNr cellIdentityNr = new CellIdentityNr(0, 0, 0, new int[]{0}, "111", "222", 0,
+                "", "",  Collections.emptyList());
+
+        CellInfoNr cellinfoNr = new CellInfoNr(0, true, 0, cellIdentityNr, null);
+
+        assertEquals(mONSProfileSelector.getMcc(cellinfoNr), "111");
+        assertEquals(mONSProfileSelector.getMnc(cellinfoNr), "222");
     }
 }
diff --git a/tests/src/com/android/ons/ONSStatsInfoTest.java b/tests/src/com/android/ons/ONSStatsInfoTest.java
new file mode 100644
index 0000000..3cd0456
--- /dev/null
+++ b/tests/src/com/android/ons/ONSStatsInfoTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2022 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.ons;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.ons.ONSProfileActivator.Result;
+import com.android.ons.ONSProfileDownloader.DownloadRetryResultCode;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ONSStatsInfoTest {
+
+    @Test
+    public void testProvisioningResult() {
+        ONSStatsInfo info;
+        info = new ONSStatsInfo().setProvisioningResult(Result.ERR_AUTO_PROVISIONING_DISABLED);
+        assertEquals(Result.ERR_AUTO_PROVISIONING_DISABLED, info.getProvisioningResult());
+        assertNull(info.getDownloadResult());
+        assertTrue(info.isProvisioningResultUpdated());
+    }
+
+    @Test
+    public void testDownloadResult() {
+        ONSStatsInfo info;
+        info = new ONSStatsInfo().setDownloadResult(DownloadRetryResultCode.ERR_MEMORY_FULL);
+        assertEquals(DownloadRetryResultCode.ERR_MEMORY_FULL, info.getDownloadResult());
+        assertNull(info.getProvisioningResult());
+        assertFalse(info.isProvisioningResultUpdated());
+    }
+
+    @Test
+    public void testPrimarySimSubId() {
+        ONSStatsInfo info;
+        info = new ONSStatsInfo().setPrimarySimSubId(1);
+        assertEquals(1, info.getPrimarySimSubId());
+    }
+
+    @Test
+    public void testOppSimCarrierId() {
+        ONSStatsInfo info;
+        info = new ONSStatsInfo().setOppSimCarrierId(1221);
+        assertEquals(1221, info.getOppSimCarrierId());
+    }
+
+    @Test
+    public void testRetryCount() {
+        ONSStatsInfo info;
+        info = new ONSStatsInfo().setRetryCount(3);
+        assertEquals(3, info.getRetryCount());
+    }
+
+    @Test
+    public void testDetailedErrCode() {
+        ONSStatsInfo info;
+        info = new ONSStatsInfo().setDetailedErrCode(1000);
+        assertEquals(1000, info.getDetailedErrCode());
+    }
+
+    @Test
+    public void testIsWifiConnected() {
+        ONSStatsInfo info;
+        info = new ONSStatsInfo().setWifiConnected(true);
+        assertTrue(info.isWifiConnected());
+    }
+
+    @Test
+    public void testIsProvisioningResultUpdated() {
+        ONSStatsInfo info;
+        info = new ONSStatsInfo().setProvisioningResult(Result.ERR_ESIM_NOT_SUPPORTED);
+        assertTrue(info.isProvisioningResultUpdated());
+
+        info.setDownloadResult(DownloadRetryResultCode.ERR_MEMORY_FULL);
+        assertFalse(info.isProvisioningResultUpdated());
+    }
+}
diff --git a/tests/src/com/android/ons/ONSStatsTest.java b/tests/src/com/android/ons/ONSStatsTest.java
new file mode 100644
index 0000000..12c49af
--- /dev/null
+++ b/tests/src/com/android/ons/ONSStatsTest.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2022 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.ons;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+
+import com.android.ons.ONSProfileActivator.Result;
+import com.android.ons.ONSProfileDownloader.DownloadRetryResultCode;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+@RunWith(JUnit4.class)
+public class ONSStatsTest {
+
+    private static final String ONS_ATOM_LOG_FILE = "ons_atom_log_info";
+    private static final String KEY_DETAILED_ERROR_CODE = "_detailed_error_code";
+
+    @Spy private Context mContext;
+    @Mock private SubscriptionManager mSubscriptionManager;
+    private SharedPreferences mSharedPreferences;
+    @Mock private SubscriptionInfo mSubInfoId1;
+    @Mock private SubscriptionInfo mSubInfoId2;
+    private ONSStats mONSStats;
+
+    private class FakeSharedPreferences implements SharedPreferences {
+        HashMap<String, Object> mMap = new HashMap<>();
+
+        @Override
+        public Map<String, ?> getAll() {
+            return mMap;
+        }
+
+        @Override
+        public String getString(String key, String defValue) {
+            return (String) mMap.getOrDefault(key, defValue);
+        }
+
+        @Override
+        public Set<String> getStringSet(String key, Set<String> defValues) {
+            if (mMap.containsKey(key)) {
+                return (Set<String>) mMap.get(key);
+            }
+            return defValues;
+        }
+
+        @Override
+        public int getInt(String key, int defValue) {
+            return (int) mMap.getOrDefault(key, defValue);
+        }
+
+        @Override
+        public long getLong(String key, long defValue) {
+            return 0; // not used
+        }
+
+        @Override
+        public float getFloat(String key, float defValue) {
+            return (float) mMap.getOrDefault(key, defValue);
+        }
+
+        @Override
+        public boolean getBoolean(String key, boolean defValue) {
+            return (boolean) mMap.getOrDefault(key, defValue);
+        }
+
+        @Override
+        public boolean contains(String key) {
+            return mMap.containsKey(key);
+        }
+
+        @Override
+        public Editor edit() {
+            TestEditor editor = new TestEditor();
+            editor.map = mMap;
+            return editor;
+        }
+
+        @Override
+        public void registerOnSharedPreferenceChangeListener(
+                OnSharedPreferenceChangeListener listener) {}
+
+        @Override
+        public void unregisterOnSharedPreferenceChangeListener(
+                OnSharedPreferenceChangeListener listener) {}
+
+        private class TestEditor implements SharedPreferences.Editor {
+            HashMap<String, Object> map = new HashMap<>();
+
+            @Override
+            public SharedPreferences.Editor putString(String key, String value) {
+                map.put(key, value);
+                return this;
+            }
+
+            @Override
+            public SharedPreferences.Editor putStringSet(String key, Set<String> values) {
+                map.put(key, values);
+                return this;
+            }
+
+            @Override
+            public SharedPreferences.Editor putInt(String key, int value) {
+                map.put(key, value);
+                return this;
+            }
+
+            @Override
+            public SharedPreferences.Editor putLong(String key, long value) {
+                map.put(key, value);
+                return this;
+            }
+
+            @Override
+            public SharedPreferences.Editor putFloat(String key, float value) {
+                map.put(key, value);
+                return this;
+            }
+
+            @Override
+            public SharedPreferences.Editor putBoolean(String key, boolean value) {
+                map.put(key, value);
+                return this;
+            }
+
+            @Override
+            public SharedPreferences.Editor remove(String key) {
+                map.remove(key);
+                return this;
+            }
+
+            @Override
+            public SharedPreferences.Editor clear() {
+                map.clear();
+                return this;
+            }
+
+            @Override
+            public boolean commit() {
+                mMap = map;
+                return true;
+            }
+
+            @Override
+            public void apply() {
+                mMap = map;
+            }
+        }
+        ;
+    }
+    ;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mSharedPreferences = new FakeSharedPreferences();
+        doReturn(mSharedPreferences)
+                .when(mContext)
+                .getSharedPreferences(eq(ONS_ATOM_LOG_FILE), eq(Context.MODE_PRIVATE));
+        doReturn(123).when(mSubInfoId1).getCarrierId();
+        doReturn(456).when(mSubInfoId2).getCarrierId();
+        doReturn(mSubInfoId1).when(mSubscriptionManager).getActiveSubscriptionInfo(1);
+        doReturn(mSubInfoId2).when(mSubscriptionManager).getActiveSubscriptionInfo(2);
+        mONSStats = new ONSStats(mContext, mSubscriptionManager);
+    }
+
+    @After
+    public void tearDown() {
+        mSharedPreferences.edit().clear().apply();
+    }
+
+    @Test
+    public void testLogEvent() {
+        ONSStatsInfo info =
+                new ONSStatsInfo()
+                        .setPrimarySimSubId(1)
+                        .setProvisioningResult(Result.ERR_CANNOT_SWITCH_TO_DUAL_SIM_MODE);
+        assertTrue(mONSStats.logEvent(info));
+    }
+
+    @Test
+    public void testIgnoredLogEvents() {
+        // ignored error codes should not log.
+        ONSStatsInfo info = new ONSStatsInfo().setProvisioningResult(Result.DOWNLOAD_REQUESTED);
+        assertFalse(mONSStats.logEvent(info));
+
+        info = new ONSStatsInfo().setProvisioningResult(Result.ERR_NO_SIM_INSERTED);
+        assertFalse(mONSStats.logEvent(info));
+
+        info = new ONSStatsInfo().setProvisioningResult(Result.ERR_DUPLICATE_DOWNLOAD_REQUEST);
+        assertFalse(mONSStats.logEvent(info));
+
+        info = new ONSStatsInfo().setProvisioningResult(Result.ERR_SWITCHING_TO_DUAL_SIM_MODE);
+        assertFalse(mONSStats.logEvent(info));
+    }
+
+    @Test
+    public void testRepeatedLogEvents() {
+        ONSStatsInfo info;
+        info =
+                new ONSStatsInfo()
+                        .setDownloadResult(DownloadRetryResultCode.ERR_MEMORY_FULL)
+                        .setDetailedErrCode(10011);
+        assertTrue(mONSStats.logEvent(info));
+
+        // same result should not log consecutively
+        assertFalse(mONSStats.logEvent(info));
+        assertFalse(mONSStats.logEvent(info));
+    }
+
+    @Test
+    public void testRepeatedAllowedLogEvents() {
+        ONSStatsInfo info;
+        info = new ONSStatsInfo().setProvisioningResult(Result.ERR_DOWNLOADED_ESIM_NOT_FOUND);
+        assertTrue(mONSStats.logEvent(info));
+
+        // ERR_DOWNLOADED_ESIM_NOT_FOUND is allowed to log consecutively
+        assertTrue(mONSStats.logEvent(info));
+        assertTrue(mONSStats.logEvent(info));
+
+        info =
+                new ONSStatsInfo()
+                        .setDownloadResult(DownloadRetryResultCode.ERR_INSTALL_ESIM_PROFILE_FAILED);
+        assertTrue(mONSStats.logEvent(info));
+
+        // ERR_INSTALL_ESIM_PROFILE_FAILED is allowed to log consecutively
+        assertTrue(mONSStats.logEvent(info));
+        assertTrue(mONSStats.logEvent(info));
+    }
+
+    @Test
+    public void testRepeatedSuccessLogEvents() {
+        ONSStatsInfo info;
+        info = new ONSStatsInfo().setProvisioningResult(Result.SUCCESS).setRetryCount(2);
+
+        // should log every time if eSIM is newly downloaded.
+        assertTrue(mONSStats.logEvent(info));
+        assertTrue(mONSStats.logEvent(info));
+
+        info = new ONSStatsInfo().setProvisioningResult(Result.SUCCESS);
+        // should log even if eSIM is already downloaded and event triggered just to group it.
+        assertTrue(mONSStats.logEvent(info));
+        assertTrue(mONSStats.logEvent(info));
+    }
+
+    @Test
+    public void testRepeatedErrorWithInfoChangeLogEvents() {
+        ONSStatsInfo info =
+                new ONSStatsInfo()
+                        .setPrimarySimSubId(1)
+                        .setProvisioningResult(Result.ERR_AUTO_PROVISIONING_DISABLED);
+        assertTrue(mONSStats.logEvent(info));
+
+        // Same error should log if the info is changed.
+        info.setPrimarySimSubId(2);
+        assertTrue(mONSStats.logEvent(info));
+
+        // no change in info
+        assertFalse(mONSStats.logEvent(info));
+    }
+
+    @Test
+    public void testDetailedErrorCodeLogEvents() {
+        ONSStatsInfo info;
+        info = new ONSStatsInfo().setProvisioningResult(Result.ERR_WAITING_FOR_INTERNET_CONNECTION);
+        assertTrue(mONSStats.logEvent(info));
+
+        // For provisioning errors; Result enum ordinal is set as detailed error code.
+        assertEquals(
+                Result.ERR_WAITING_FOR_INTERNET_CONNECTION.ordinal(),
+                mSharedPreferences.getInt(KEY_DETAILED_ERROR_CODE, -1));
+        assertEquals(
+                Result.ERR_WAITING_FOR_INTERNET_CONNECTION.ordinal(), info.getDetailedErrCode());
+
+        // For Download errors; detailed error code is updated from EuiccManager.
+        info =
+                new ONSStatsInfo()
+                        .setDownloadResult(DownloadRetryResultCode.ERR_MEMORY_FULL)
+                        .setDetailedErrCode(10223);
+        assertTrue(mONSStats.logEvent(info));
+        assertEquals(10223, mSharedPreferences.getInt(KEY_DETAILED_ERROR_CODE, -1));
+        assertEquals(10223, info.getDetailedErrCode());
+    }
+}
diff --git a/tests/src/com/android/ons/OpportunisticNetworkServiceTest.java b/tests/src/com/android/ons/OpportunisticNetworkServiceTest.java
index 0a75371..5b2d67c 100644
--- a/tests/src/com/android/ons/OpportunisticNetworkServiceTest.java
+++ b/tests/src/com/android/ons/OpportunisticNetworkServiceTest.java
@@ -76,13 +76,26 @@
                 mOpportunisticNetworkService = new OpportunisticNetworkService();
                 mOpportunisticNetworkService.initialize(mContext);
                 mOpportunisticNetworkService.mSubscriptionManager = mSubscriptionManager;
-                iOpportunisticNetworkService = (IOns) mOpportunisticNetworkService.onBind(null);
+                for (int retry = 2; retry > 0; retry--) {
+
+                    iOpportunisticNetworkService = (IOns) mOpportunisticNetworkService.onBind(null);
+
+                    if (iOpportunisticNetworkService != null) {
+                        break;
+                    }
+
+                    try {
+                        Thread.sleep(100);
+                    } catch (Exception e) {
+                        Log.e(TAG, e.toString());
+                    }
+                }
                 mLooper = Looper.myLooper();
                 setReady(true);
                 Looper.loop();
             }
         }).start();
-        waitUntilReady(200);
+        waitUntilReady(300);
     }
 
     @After
@@ -208,6 +221,7 @@
         };
 
         try {
+            assertNotNull(iOpportunisticNetworkService);
             iOpportunisticNetworkService.setPreferredDataSubscriptionId(5, false, callbackStub,
                     pkgForDebug);
         } catch (RemoteException ex) {