[Iwlan Metrics] Atom Logging code

Send Iwlan Metrics atom when
- IWLAN PDN setup request processed
- IWLAN PDN disconnected

Bug: 157575303
Test: atest IwlanTests
Change-Id: I03ec05a812c603f426e368f4445aaf443befe74c
diff --git a/Android.bp b/Android.bp
index 5994d3f..4d912dd 100644
--- a/Android.bp
+++ b/Android.bp
@@ -2,12 +2,21 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
+genrule {
+    name: "statslog-Iwlan-java-gen",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --java $(out) --module iwlan --javaPackage com.google.android.iwlan"
+        + " --javaClass IwlanStatsLog",
+    out: ["com/google/android/iwlan/IwlanStatsLog.java"],
+}
+
 android_app {
     name: "Iwlan",
     manifest: "AndroidManifest.xml",
     srcs: [
         "src/**/*.java",
         "src/**/I*.aidl",
+        ":statslog-Iwlan-java-gen",
     ],
     resource_dirs: [
         "res",
@@ -53,6 +62,7 @@
     srcs: [
         "src/**/*.java",
         "test/**/*.java",
+        ":statslog-Iwlan-java-gen",
     ],
 
     platform_apis: true,
diff --git a/src/com/google/android/iwlan/IwlanDataService.java b/src/com/google/android/iwlan/IwlanDataService.java
index 0131ed0..a33b4a9 100644
--- a/src/com/google/android/iwlan/IwlanDataService.java
+++ b/src/com/google/android/iwlan/IwlanDataService.java
@@ -30,6 +30,8 @@
 import android.net.TelephonyNetworkSpecifier;
 import android.net.TransportInfo;
 import android.net.vcn.VcnTransportInfo;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -41,6 +43,10 @@
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.CarrierConfigManager;
 import android.telephony.CellInfo;
+import android.telephony.CellInfoGsm;
+import android.telephony.CellInfoLte;
+import android.telephony.CellInfoNr;
+import android.telephony.CellInfoWcdma;
 import android.telephony.DataFailCause;
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
@@ -58,6 +64,7 @@
 import com.google.android.iwlan.epdg.EpdgTunnelManager;
 import com.google.android.iwlan.epdg.TunnelLinkProperties;
 import com.google.android.iwlan.epdg.TunnelSetupRequest;
+import com.google.android.iwlan.proto.MetricsAtom;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -100,6 +107,8 @@
     private static final int EVENT_FORCE_CLOSE_TUNNEL = EVENT_BASE + 5;
     private static final int EVENT_ADD_DATA_SERVICE_PROVIDER = EVENT_BASE + 6;
     private static final int EVENT_REMOVE_DATA_SERVICE_PROVIDER = EVENT_BASE + 7;
+    private static final int EVENT_TUNNEL_OPENED_METRICS = EVENT_BASE + 8;
+    private static final int EVENT_TUNNEL_CLOSED_METRICS = EVENT_BASE + 9;
 
     @VisibleForTesting
     enum Transport {
@@ -202,15 +211,18 @@
         private final String SUB_TAG;
         private final IwlanDataService mIwlanDataService;
         private final IwlanTunnelCallback mIwlanTunnelCallback;
+        private final IwlanTunnelCallbackMetrics mIwlanTunnelCallbackMetrics;
         private boolean mWfcEnabled = false;
         private boolean mCarrierConfigReady = false;
         private EpdgSelector mEpdgSelector;
         private IwlanDataTunnelStats mTunnelStats;
         private CellInfo mCellInfo = null;
+        private long mProcessingStartTime = 0;
 
         // apn to TunnelState
         // Access should be serialized inside IwlanDataServiceHandler
         private Map<String, TunnelState> mTunnelStateForApn = new ConcurrentHashMap<>();
+        private Map<String, MetricsAtom> mMetricsAtomForApn = new ConcurrentHashMap<>();
 
         // Holds the state of a tunnel (for an APN)
         @VisibleForTesting
@@ -388,6 +400,52 @@
             }
         }
 
+        @VisibleForTesting
+        class IwlanTunnelCallbackMetrics implements EpdgTunnelManager.TunnelCallbackMetrics {
+
+            DataServiceProvider mDataServiceProvider;
+
+            public IwlanTunnelCallbackMetrics(DataServiceProvider dsp) {
+                mDataServiceProvider = dsp;
+            }
+
+            public void onOpened(
+                    String apnName,
+                    String epdgServerAddress,
+                    int epdgServerSelectionDuration,
+                    int ikeTunnelEstablishmentDuration) {
+                mIwlanDataServiceHandler.sendMessage(
+                        mIwlanDataServiceHandler.obtainMessage(
+                                EVENT_TUNNEL_OPENED_METRICS,
+                                new TunnelOpenedMetricsData(
+                                        apnName,
+                                        epdgServerAddress,
+                                        System.currentTimeMillis() - mProcessingStartTime,
+                                        epdgServerSelectionDuration,
+                                        ikeTunnelEstablishmentDuration,
+                                        IwlanDataServiceProvider.this)));
+            }
+
+            public void onClosed(
+                    String apnName,
+                    String epdgServerAddress,
+                    int epdgServerSelectionDuration,
+                    int ikeTunnelEstablishmentDuration) {
+                mIwlanDataServiceHandler.sendMessage(
+                        mIwlanDataServiceHandler.obtainMessage(
+                                EVENT_TUNNEL_CLOSED_METRICS,
+                                new TunnelClosedMetricsData(
+                                        apnName,
+                                        epdgServerAddress,
+                                        mProcessingStartTime > 0
+                                                ? System.currentTimeMillis() - mProcessingStartTime
+                                                : 0,
+                                        epdgServerSelectionDuration,
+                                        ikeTunnelEstablishmentDuration,
+                                        IwlanDataServiceProvider.this)));
+            }
+        }
+
         /** Holds all tunnel related time and count statistics for this IwlanDataServiceProvider */
         @VisibleForTesting
         class IwlanDataTunnelStats {
@@ -543,6 +601,7 @@
             // get reference to resolver
             mIwlanDataService = iwlanDataService;
             mIwlanTunnelCallback = new IwlanTunnelCallback(this);
+            mIwlanTunnelCallbackMetrics = new IwlanTunnelCallbackMetrics(this);
             mEpdgSelector = EpdgSelector.getSelectorInstance(mContext, slotIndex);
             mTunnelStats = new IwlanDataTunnelStats();
 
@@ -714,6 +773,7 @@
                 boolean matchAllRuleAllowed,
                 @NonNull DataServiceCallback callback) {
 
+            mProcessingStartTime = System.currentTimeMillis();
             Log.d(
                     SUB_TAG,
                     "Setup data call with network: "
@@ -746,6 +806,31 @@
                             callback,
                             this);
 
+            int networkTransport = -1;
+            if (sDefaultDataTransport == Transport.MOBILE) {
+                networkTransport = TRANSPORT_CELLULAR;
+            } else if (sDefaultDataTransport == Transport.MOBILE) {
+                networkTransport = TRANSPORT_WIFI;
+            }
+
+            if (dataProfile != null) {
+                this.setMetricsAtom(
+                        // ApnName
+                        dataProfile.getApn(),
+                        // ApnType
+                        dataProfile.getSupportedApnTypesBitmask(),
+                        // IsHandover
+                        (reason == DataService.REQUEST_REASON_HANDOVER),
+                        // Source Rat
+                        getCurrentCellularRat(),
+                        // IsRoaming
+                        isRoaming,
+                        // Is Network Connected
+                        sNetworkConnected,
+                        // Transport Type
+                        networkTransport);
+            }
+
             mIwlanDataServiceHandler.sendMessage(
                     mIwlanDataServiceHandler.obtainMessage(
                             EVENT_SETUP_DATA_CALL, setupDataCallData));
@@ -829,11 +914,35 @@
         }
 
         @VisibleForTesting
+        void setMetricsAtom(
+                String apnName,
+                int apntype,
+                boolean isHandover,
+                int sourceRat,
+                boolean isRoaming,
+                boolean isNetworkConnected,
+                int transportType) {
+            MetricsAtom metricsAtom = new MetricsAtom();
+            metricsAtom.setApnType(apntype);
+            metricsAtom.setIsHandover(isHandover);
+            metricsAtom.setSourceRat(sourceRat);
+            metricsAtom.setIsCellularRoaming(isRoaming);
+            metricsAtom.setIsNetworkConnected(isNetworkConnected);
+            metricsAtom.setTransportType(transportType);
+            mMetricsAtomForApn.put(apnName, metricsAtom);
+        }
+
+        @VisibleForTesting
         public IwlanTunnelCallback getIwlanTunnelCallback() {
             return mIwlanTunnelCallback;
         }
 
         @VisibleForTesting
+        public IwlanTunnelCallbackMetrics getIwlanTunnelCallbackMetrics() {
+            return mIwlanTunnelCallbackMetrics;
+        }
+
+        @VisibleForTesting
         IwlanDataTunnelStats getTunnelStats() {
             return mTunnelStats;
         }
@@ -906,6 +1015,34 @@
                     0, EpdgSelector.PROTO_FILTER_IPV4V6, isRoaming, true, network, null);
         }
 
+        private int getCurrentCellularRat() {
+            TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
+            telephonyManager =
+                    telephonyManager.createForSubscriptionId(
+                            IwlanHelper.getSubId(mContext, getSlotIndex()));
+            List<CellInfo> cellInfoList = telephonyManager.getAllCellInfo();
+            if (cellInfoList == null) {
+                Log.e(TAG, "cellInfoList is NULL");
+                return 0;
+            }
+
+            for (CellInfo cellInfo : cellInfoList) {
+                if (!cellInfo.isRegistered()) {
+                    continue;
+                }
+                if (cellInfo instanceof CellInfoGsm) {
+                    return TelephonyManager.NETWORK_TYPE_GSM;
+                } else if (cellInfo instanceof CellInfoWcdma) {
+                    return TelephonyManager.NETWORK_TYPE_UMTS;
+                } else if (cellInfo instanceof CellInfoLte) {
+                    return TelephonyManager.NETWORK_TYPE_LTE;
+                } else if (cellInfo instanceof CellInfoNr) {
+                    return TelephonyManager.NETWORK_TYPE_NR;
+                }
+            }
+            return TelephonyManager.NETWORK_TYPE_UNKNOWN;
+        }
+
         /**
          * Called when the instance of data service is destroyed (e.g. got unbind or binder died) or
          * when the data service provider is removed.
@@ -961,6 +1098,9 @@
             DataServiceCallback callback;
             int reason;
             int slotId;
+            int retryTimeMillis;
+            int errorCause;
+            MetricsAtom metricsAtom;
 
             switch (msg.what) {
                 case EVENT_TUNNEL_OPENED:
@@ -978,6 +1118,16 @@
                     iwlanDataServiceProvider.mTunnelStats.reportTunnelSetupSuccess(
                             apnName, tunnelState);
 
+                    // Record setup result for the Metrics
+                    metricsAtom = iwlanDataServiceProvider.mMetricsAtomForApn.get(apnName);
+                    metricsAtom.setSetupRequestResult(DataServiceCallback.RESULT_SUCCESS);
+                    metricsAtom.setIwlanError(IwlanError.NO_ERROR);
+                    metricsAtom.setDataCallFailCause(DataFailCause.NONE);
+                    metricsAtom.setTunnelState(tunnelState.getState());
+                    metricsAtom.setHandoverFailureMode(-1);
+                    metricsAtom.setRetryDurationMillis(0);
+                    metricsAtom.setMessageId(IwlanStatsLog.IWLAN_SETUP_DATA_CALL_RESULT_REPORTED);
+
                     iwlanDataServiceProvider.deliverCallback(
                             IwlanDataServiceProvider.CALLBACK_TYPE_SETUP_DATACALL_COMPLETE,
                             DataServiceCallback.RESULT_SUCCESS,
@@ -994,6 +1144,7 @@
                     tunnelState = iwlanDataServiceProvider.mTunnelStateForApn.get(apnName);
                     iwlanDataServiceProvider.mTunnelStats.reportTunnelDown(apnName, tunnelState);
                     iwlanDataServiceProvider.mTunnelStateForApn.remove(apnName);
+                    metricsAtom = iwlanDataServiceProvider.mMetricsAtomForApn.get(apnName);
 
                     if (tunnelState.getState()
                                     == IwlanDataServiceProvider.TunnelState.TUNNEL_IN_BRINGUP
@@ -1009,31 +1160,53 @@
                             respBuilder.setHandoverFailureMode(
                                     DataCallResponse
                                             .HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER);
+                            metricsAtom.setHandoverFailureMode(
+                                    DataCallResponse
+                                            .HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER);
                         } else {
                             respBuilder.setHandoverFailureMode(
                                     DataCallResponse
                                             .HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_SETUP_NORMAL);
+                            metricsAtom.setHandoverFailureMode(
+                                    DataCallResponse
+                                            .HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_SETUP_NORMAL);
                         }
 
                         if (tunnelState.getState()
                                 == IwlanDataServiceProvider.TunnelState.TUNNEL_IN_BRINGUP) {
-                            respBuilder.setCause(
+                            errorCause =
                                     ErrorPolicyManager.getInstance(
                                                     mContext,
                                                     iwlanDataServiceProvider.getSlotIndex())
-                                            .getDataFailCause(apnName));
-                            respBuilder.setRetryDurationMillis(
-                                    ErrorPolicyManager.getInstance(
-                                                    mContext,
-                                                    iwlanDataServiceProvider.getSlotIndex())
-                                            .getCurrentRetryTimeMs(apnName));
+                                            .getDataFailCause(apnName);
+                            respBuilder.setCause(errorCause);
+                            metricsAtom.setDataCallFailCause(errorCause);
+
+                            retryTimeMillis =
+                                    (int)
+                                            ErrorPolicyManager.getInstance(
+                                                            mContext,
+                                                            iwlanDataServiceProvider.getSlotIndex())
+                                                    .getCurrentRetryTimeMs(apnName);
+                            respBuilder.setRetryDurationMillis(retryTimeMillis);
+                            metricsAtom.setRetryDurationMillis(retryTimeMillis);
+
                         } else if (tunnelState.getState()
                                 == IwlanDataServiceProvider.TunnelState
                                         .TUNNEL_IN_FORCE_CLEAN_WAS_IN_BRINGUP) {
                             respBuilder.setCause(DataFailCause.IWLAN_NETWORK_FAILURE);
+                            metricsAtom.setDataCallFailCause(DataFailCause.IWLAN_NETWORK_FAILURE);
                             respBuilder.setRetryDurationMillis(5000);
+                            metricsAtom.setRetryDurationMillis(5000);
                         }
 
+                        // Record setup result for the Metrics
+                        metricsAtom.setSetupRequestResult(DataServiceCallback.RESULT_SUCCESS);
+                        metricsAtom.setIwlanError(iwlanError.getErrorType());
+                        metricsAtom.setTunnelState(tunnelState.getState());
+                        metricsAtom.setMessageId(
+                                IwlanStatsLog.IWLAN_SETUP_DATA_CALL_RESULT_REPORTED);
+
                         iwlanDataServiceProvider.deliverCallback(
                                 IwlanDataServiceProvider.CALLBACK_TYPE_SETUP_DATACALL_COMPLETE,
                                 DataServiceCallback.RESULT_SUCCESS,
@@ -1070,6 +1243,28 @@
                     // just update list of data calls. No way to send error up
                     iwlanDataServiceProvider.notifyDataCallListChanged(
                             iwlanDataServiceProvider.getCallList());
+
+                    // Report IwlanPdnDisconnectedReason due to the disconnection is neither for
+                    // SETUP_DATA_CALL nor DEACTIVATE_DATA_CALL request.
+                    metricsAtom.setDataCallFailCause(
+                            ErrorPolicyManager.getInstance(
+                                            mContext, iwlanDataServiceProvider.getSlotIndex())
+                                    .getDataFailCause(apnName));
+
+                    WifiManager wifiManager = mContext.getSystemService(WifiManager.class);
+                    if (wifiManager == null) {
+                        Log.e(TAG, "Could not find wifiManager");
+                        return;
+                    }
+
+                    WifiInfo wifiInfo = wifiManager.getConnectionInfo();
+                    if (wifiInfo == null) {
+                        Log.e(TAG, "wifiInfo is null");
+                        return;
+                    }
+
+                    metricsAtom.setWifiSignalValue(wifiInfo.getRssi());
+                    metricsAtom.setMessageId(IwlanStatsLog.IWLAN_PDN_DISCONNECTED_REASON_REPORTED);
                     break;
 
                 case IwlanEventListener.CARRIER_CONFIG_CHANGED_EVENT:
@@ -1260,7 +1455,9 @@
                                     .getTunnelManager()
                                     .bringUpTunnel(
                                             tunnelReqBuilder.build(),
-                                            iwlanDataServiceProvider.getIwlanTunnelCallback());
+                                            iwlanDataServiceProvider.getIwlanTunnelCallback(),
+                                            iwlanDataServiceProvider
+                                                    .getIwlanTunnelCallbackMetrics());
                     Log.d(TAG + "[" + slotId + "]", "bringup Tunnel with result:" + result);
                     if (!result) {
                         iwlanDataServiceProvider.deliverCallback(
@@ -1359,6 +1556,43 @@
                     }
                     break;
 
+                case EVENT_TUNNEL_OPENED_METRICS:
+                    TunnelOpenedMetricsData tunnelOpenedMetricsData =
+                            (TunnelOpenedMetricsData) msg.obj;
+                    iwlanDataServiceProvider = tunnelOpenedMetricsData.mIwlanDataServiceProvider;
+                    apnName = tunnelOpenedMetricsData.mApnName;
+
+                    metricsAtom = iwlanDataServiceProvider.mMetricsAtomForApn.get(apnName);
+                    metricsAtom.setEpdgServerAddress(tunnelOpenedMetricsData.mEpdgServerAddress);
+                    metricsAtom.setProcessingDurationMillis(
+                            tunnelOpenedMetricsData.mProcessingDuration);
+                    metricsAtom.setEpdgServerSelectionDurationMillis(
+                            tunnelOpenedMetricsData.mEpdgServerSelectionDuration);
+                    metricsAtom.setIkeTunnelEstablishmentDurationMillis(
+                            tunnelOpenedMetricsData.mIkeTunnelEstablishmentDuration);
+
+                    metricsAtom.sendMetricsData();
+                    break;
+
+                case EVENT_TUNNEL_CLOSED_METRICS:
+                    TunnelClosedMetricsData tunnelClosedMetricsData =
+                            (TunnelClosedMetricsData) msg.obj;
+                    iwlanDataServiceProvider = tunnelClosedMetricsData.mIwlanDataServiceProvider;
+                    apnName = tunnelClosedMetricsData.mApnName;
+
+                    metricsAtom = iwlanDataServiceProvider.mMetricsAtomForApn.get(apnName);
+                    metricsAtom.setEpdgServerAddress(tunnelClosedMetricsData.mEpdgServerAddress);
+                    metricsAtom.setProcessingDurationMillis(
+                            tunnelClosedMetricsData.mProcessingDuration);
+                    metricsAtom.setEpdgServerSelectionDurationMillis(
+                            tunnelClosedMetricsData.mEpdgServerSelectionDuration);
+                    metricsAtom.setIkeTunnelEstablishmentDurationMillis(
+                            tunnelClosedMetricsData.mIkeTunnelEstablishmentDuration);
+
+                    metricsAtom.sendMetricsData();
+                    iwlanDataServiceProvider.mMetricsAtomForApn.remove(apnName);
+                    break;
+
                 default:
                     throw new IllegalStateException("Unexpected value: " + msg.what);
             }
@@ -1397,6 +1631,54 @@
         }
     }
 
+    private static final class TunnelOpenedMetricsData {
+        final String mApnName;
+        final IwlanDataServiceProvider mIwlanDataServiceProvider;
+        final String mEpdgServerAddress;
+        final int mProcessingDuration;
+        final int mEpdgServerSelectionDuration;
+        final int mIkeTunnelEstablishmentDuration;
+
+        private TunnelOpenedMetricsData(
+                String apnName,
+                String epdgServerAddress,
+                long processingDuration,
+                int epdgServerSelectionDuration,
+                int ikeTunnelEstablishmentDuration,
+                IwlanDataServiceProvider dsp) {
+            mApnName = apnName;
+            mIwlanDataServiceProvider = dsp;
+            mEpdgServerAddress = epdgServerAddress;
+            mProcessingDuration = (int) processingDuration;
+            mEpdgServerSelectionDuration = epdgServerSelectionDuration;
+            mIkeTunnelEstablishmentDuration = ikeTunnelEstablishmentDuration;
+        }
+    }
+
+    private static final class TunnelClosedMetricsData {
+        final String mApnName;
+        final IwlanDataServiceProvider mIwlanDataServiceProvider;
+        final String mEpdgServerAddress;
+        final int mProcessingDuration;
+        final int mEpdgServerSelectionDuration;
+        final int mIkeTunnelEstablishmentDuration;
+
+        private TunnelClosedMetricsData(
+                String apnName,
+                String epdgServerAddress,
+                long processingDuration,
+                int epdgServerSelectionDuration,
+                int ikeTunnelEstablishmentDuration,
+                IwlanDataServiceProvider dsp) {
+            mApnName = apnName;
+            mIwlanDataServiceProvider = dsp;
+            mEpdgServerAddress = epdgServerAddress;
+            mProcessingDuration = (int) processingDuration;
+            mEpdgServerSelectionDuration = epdgServerSelectionDuration;
+            mIkeTunnelEstablishmentDuration = ikeTunnelEstablishmentDuration;
+        }
+    }
+
     private static final class SetupDataCallData {
         final int mAccessNetworkType;
         @NonNull final DataProfile mDataProfile;
@@ -1746,6 +2028,10 @@
                 return "WIFI_CALLING_DISABLE_EVENT";
             case IwlanEventListener.CELLINFO_CHANGED_EVENT:
                 return "CELLINFO_CHANGED_EVENT";
+            case EVENT_TUNNEL_OPENED_METRICS:
+                return "EVENT_TUNNEL_OPENED_METRICS";
+            case EVENT_TUNNEL_CLOSED_METRICS:
+                return "EVENT_TUNNEL_CLOSED_METRICS";
             default:
                 return "Unknown(" + event + ")";
         }
diff --git a/src/com/google/android/iwlan/epdg/EpdgTunnelManager.java b/src/com/google/android/iwlan/epdg/EpdgTunnelManager.java
index 6ac0f3d..0cd759a 100644
--- a/src/com/google/android/iwlan/epdg/EpdgTunnelManager.java
+++ b/src/com/google/android/iwlan/epdg/EpdgTunnelManager.java
@@ -164,6 +164,10 @@
     private List<InetAddress> mLocalAddresses;
 
     @Nullable private byte[] mNextReauthId = null;
+    private long mEpdgServerSelectionDuration = 0;
+    private long mEpdgServerSelectionStartTime = 0;
+    private long mIkeTunnelEstablishmentDuration = 0;
+    private long mIkeTunnelEstablishmentStartTime = 0;
 
     private static final Set<Integer> VALID_DH_GROUPS;
     private static final Set<Integer> VALID_KEY_LENGTHS;
@@ -239,6 +243,7 @@
     @VisibleForTesting
     class TunnelConfig {
         @NonNull final TunnelCallback mTunnelCallback;
+        @NonNull final TunnelCallbackMetrics mTunnelCallbackMetrics;
         // TODO: Change this to TunnelLinkProperties after removing autovalue
         private List<InetAddress> mPcscfAddrList;
         private List<InetAddress> mDnsAddrList;
@@ -278,9 +283,11 @@
         public TunnelConfig(
                 IkeSession ikeSession,
                 TunnelCallback tunnelCallback,
+                TunnelCallbackMetrics tunnelCallbackMetrics,
                 InetAddress srcIpv6Addr,
                 int srcIpv6PrefixLength) {
             mTunnelCallback = tunnelCallback;
+            mTunnelCallbackMetrics = tunnelCallbackMetrics;
             mIkeSession = ikeSession;
             mError = new IwlanError(IwlanError.NO_ERROR);
             mSrcIpv6Address = srcIpv6Addr;
@@ -292,6 +299,11 @@
             return mTunnelCallback;
         }
 
+        @NonNull
+        TunnelCallbackMetrics getTunnelCallbackMetrics() {
+            return mTunnelCallbackMetrics;
+        }
+
         List<InetAddress> getPcscfAddrList() {
             return mPcscfAddrList;
         }
@@ -512,8 +524,8 @@
                     mHandler.obtainMessage(
                             EVENT_IKE_3GPP_DATA_RECEIVED,
                             new Ike3gppDataReceived(mApnName, payloads)));
-            }
         }
+    }
 
     @VisibleForTesting
     class TmChildSessionCallback implements ChildSessionCallback {
@@ -637,6 +649,37 @@
         void onClosed(@NonNull String apnName, @NonNull IwlanError error);
     }
 
+    public interface TunnelCallbackMetrics {
+        /**
+         * Called for logging the tunnel is opened.
+         *
+         * @param apnName apn for which the tunnel was opened
+         * @param epdgServerAddress epdg server IP address for bringup the tunnel
+         * @param epdgServerSelectionDuration time for EpdgSelector doing FQDN
+         * @param ikeTunnelEstablishmentDuration time for IKE module processing tunnel
+         *     establishement
+         */
+        void onOpened(
+                @NonNull String apnName,
+                @Nullable String epdgServerAddress,
+                @NonNull int epdgServerSelectionDuration,
+                @NonNull int ikeTunnelEstablishmentDuration);
+        /**
+         * Called for logging the tunnel is closed or bringup failed.
+         *
+         * @param apnName apn for which the tunnel was closed
+         * @param epdgServerAddress epdg server IP address for bringup the tunnel
+         * @param epdgServerSelectionDuration time for EpdgSelector doing FQDN
+         * @param ikeTunnelEstablishmentDuration time for IKE module processing tunnel
+         *     establishement
+         */
+        void onClosed(
+                @NonNull String apnName,
+                @Nullable String epdgServerAddress,
+                @NonNull int epdgServerSelectionDuration,
+                @NonNull int ikeTunnelEstablishmentDuration);
+    }
+
     /**
      * Close tunnel for an apn. Confirmation of closing will be delivered in TunnelCallback that was
      * provided in {@link #bringUpTunnel}
@@ -678,7 +721,9 @@
      * @return true if params are valid and no existing tunnel. False otherwise.
      */
     public boolean bringUpTunnel(
-            @NonNull TunnelSetupRequest setupRequest, @NonNull TunnelCallback tunnelCallback) {
+            @NonNull TunnelSetupRequest setupRequest,
+            @NonNull TunnelCallback tunnelCallback,
+            @NonNull TunnelCallbackMetrics tunnelCallbackMetrics) {
         String apnName = setupRequest.apnName();
 
         if (getTunnelSetupRequestApnName(setupRequest) == null) {
@@ -703,7 +748,7 @@
         }
 
         TunnelRequestWrapper tunnelRequestWrapper =
-                new TunnelRequestWrapper(setupRequest, tunnelCallback);
+                new TunnelRequestWrapper(setupRequest, tunnelCallback, tunnelCallbackMetrics);
 
         mHandler.sendMessage(
                 mHandler.obtainMessage(EVENT_TUNNEL_BRINGUP_REQUEST, tunnelRequestWrapper));
@@ -711,7 +756,10 @@
         return true;
     }
 
-    private void onBringUpTunnel(TunnelSetupRequest setupRequest, TunnelCallback tunnelCallback) {
+    private void onBringUpTunnel(
+            TunnelSetupRequest setupRequest,
+            TunnelCallback tunnelCallback,
+            TunnelCallbackMetrics tunnelCallbackMetrics) {
         String apnName = setupRequest.apnName();
         IkeSessionParams ikeSessionParams = null;
 
@@ -728,9 +776,11 @@
             IwlanError iwlanError = new IwlanError(IwlanError.SIM_NOT_READY_EXCEPTION);
             reportIwlanError(apnName, iwlanError);
             tunnelCallback.onClosed(apnName, iwlanError);
+            tunnelCallbackMetrics.onClosed(apnName, null, 0, 0);
             return;
         }
 
+        mIkeTunnelEstablishmentStartTime = System.currentTimeMillis();
         IkeSession ikeSession =
                 getIkeSessionCreator()
                         .createIkeSession(
@@ -746,6 +796,7 @@
                 apnName,
                 ikeSession,
                 tunnelCallback,
+                tunnelCallbackMetrics,
                 isSrcIpv6Present ? setupRequest.srcIpv6Address().get() : null,
                 setupRequest.srcIpv6AddressPrefixLength());
     }
@@ -1277,6 +1328,9 @@
                         tunnelRequestWrapper
                                 .getTunnelCallback()
                                 .onClosed(setupRequest.apnName(), iwlanError);
+                        tunnelRequestWrapper
+                                .getTunnelCallbackMetrics()
+                                .onClosed(setupRequest.apnName(), null, 0, 0);
                         return;
                     }
 
@@ -1287,12 +1341,18 @@
                                 .onClosed(
                                         setupRequest.apnName(),
                                         getLastError(setupRequest.apnName()));
+                        tunnelRequestWrapper
+                                .getTunnelCallbackMetrics()
+                                .onClosed(setupRequest.apnName(), null, 0, 0);
                         return;
                     }
 
                     if (mHasConnectedToEpdg) {
                         // Service the request immediately when epdg address is available
-                        onBringUpTunnel(setupRequest, tunnelRequestWrapper.getTunnelCallback());
+                        onBringUpTunnel(
+                                setupRequest,
+                                tunnelRequestWrapper.getTunnelCallback(),
+                                tunnelRequestWrapper.getTunnelCallbackMetrics());
                         break;
                     }
 
@@ -1326,7 +1386,8 @@
                         validateAndSetEpdgAddress(selectorResult.getValidIpList());
                         onBringUpTunnel(
                                 tunnelRequestWrapper.getSetupRequest(),
-                                tunnelRequestWrapper.getTunnelCallback());
+                                tunnelRequestWrapper.getTunnelCallback(),
+                                tunnelRequestWrapper.getTunnelCallbackMetrics());
                     } else {
                         IwlanError error =
                                 (selectorResult.getEpdgError().getErrorType()
@@ -1369,6 +1430,17 @@
 
                     reportIwlanError(apnName, new IwlanError(IwlanError.NO_ERROR));
 
+                    mIkeTunnelEstablishmentDuration =
+                            System.currentTimeMillis() - mIkeTunnelEstablishmentStartTime;
+                    mIkeTunnelEstablishmentStartTime = 0;
+                    tunnelConfig
+                            .getTunnelCallbackMetrics()
+                            .onOpened(
+                                    apnName,
+                                    mEpdgAddress.getHostAddress(),
+                                    (int) mEpdgServerSelectionDuration,
+                                    (int) mIkeTunnelEstablishmentDuration);
+
                     setHasConnectedToEpdg(true);
                     mValidEpdgInfo.resetIndex();
                     printRequestQueue("EVENT_CHILD_SESSION_OPENED");
@@ -1422,6 +1494,22 @@
 
                     if (!mHasConnectedToEpdg) {
                         failAllPendingRequests(iwlanError);
+                        tunnelConfig.getTunnelCallbackMetrics().onClosed(apnName, null, 0, 0);
+                    } else {
+                        mIkeTunnelEstablishmentDuration =
+                                mIkeTunnelEstablishmentStartTime > 0
+                                        ? System.currentTimeMillis()
+                                                - mIkeTunnelEstablishmentStartTime
+                                        : 0;
+                        mIkeTunnelEstablishmentStartTime = 0;
+
+                        tunnelConfig
+                                .getTunnelCallbackMetrics()
+                                .onClosed(
+                                        apnName,
+                                        mEpdgAddress.getHostAddress(),
+                                        (int) mEpdgServerSelectionDuration,
+                                        (int) mIkeTunnelEstablishmentDuration);
                     }
 
                     mApnNameToTunnelConfig.remove(apnName);
@@ -1666,6 +1754,7 @@
             mProtoFilter = EpdgSelector.PROTO_FILTER_IPV6;
         }
 
+        mEpdgServerSelectionStartTime = System.currentTimeMillis();
         EpdgSelector epdgSelector = getEpdgSelector();
         IwlanError epdgError =
                 epdgSelector.getValidatedServerList(
@@ -1701,6 +1790,10 @@
                 requestWrapper
                         .getTunnelCallback()
                         .onClosed(apnName, new IwlanError(IwlanError.NO_ERROR));
+
+                requestWrapper
+                        .getTunnelCallbackMetrics()
+                        .onClosed(apnName, mEpdgAddress.getHostAddress(), 0, 0);
                 numRequestsClosed++;
             } else {
                 mPendingBringUpRequests.add(requestWrapper);
@@ -1744,7 +1837,10 @@
         while (!mPendingBringUpRequests.isEmpty()) {
             Log.d(TAG, "serviceAllPendingRequests");
             TunnelRequestWrapper request = mPendingBringUpRequests.remove();
-            onBringUpTunnel(request.getSetupRequest(), request.getTunnelCallback());
+            onBringUpTunnel(
+                    request.getSetupRequest(),
+                    request.getTunnelCallback(),
+                    request.getTunnelCallbackMetrics());
         }
     }
 
@@ -1755,6 +1851,12 @@
             TunnelSetupRequest setupRequest = request.getSetupRequest();
             reportIwlanError(setupRequest.apnName(), error);
             request.getTunnelCallback().onClosed(setupRequest.apnName(), error);
+            request.getTunnelCallbackMetrics()
+                    .onClosed(
+                            setupRequest.apnName(),
+                            mEpdgAddress == null ? null : mEpdgAddress.getHostAddress(),
+                            0,
+                            0);
         }
     }
 
@@ -1789,11 +1891,15 @@
         private final TunnelSetupRequest mSetupRequest;
 
         private final TunnelCallback mTunnelCallback;
+        private final TunnelCallbackMetrics mTunnelCallbackMetrics;
 
         private TunnelRequestWrapper(
-                TunnelSetupRequest setupRequest, TunnelCallback tunnelCallback) {
+                TunnelSetupRequest setupRequest,
+                TunnelCallback tunnelCallback,
+                TunnelCallbackMetrics tunnelCallbackMetrics) {
             mTunnelCallback = tunnelCallback;
             mSetupRequest = setupRequest;
+            mTunnelCallbackMetrics = tunnelCallbackMetrics;
         }
 
         public TunnelSetupRequest getSetupRequest() {
@@ -1803,6 +1909,10 @@
         public TunnelCallback getTunnelCallback() {
             return mTunnelCallback;
         }
+
+        public TunnelCallbackMetrics getTunnelCallbackMetrics() {
+            return mTunnelCallbackMetrics;
+        }
     }
 
     private static final class EpdgSelectorResult {
@@ -2039,11 +2149,17 @@
             String apnName,
             IkeSession ikeSession,
             TunnelCallback tunnelCallback,
+            TunnelCallbackMetrics tunnelCallbackMetrics,
             InetAddress srcIpv6Addr,
             int srcIPv6AddrPrefixLen) {
         mApnNameToTunnelConfig.put(
                 apnName,
-                new TunnelConfig(ikeSession, tunnelCallback, srcIpv6Addr, srcIPv6AddrPrefixLen));
+                new TunnelConfig(
+                        ikeSession,
+                        tunnelCallback,
+                        tunnelCallbackMetrics,
+                        srcIpv6Addr,
+                        srcIPv6AddrPrefixLen));
         Log.d(TAG, "Added apn: " + apnName + " to TunnelConfig");
     }
 
@@ -2072,6 +2188,8 @@
     @VisibleForTesting
     void sendSelectionRequestComplete(
             ArrayList<InetAddress> validIPList, IwlanError result, int transactionId) {
+        mEpdgServerSelectionDuration = System.currentTimeMillis() - mEpdgServerSelectionStartTime;
+        mEpdgServerSelectionStartTime = 0;
         EpdgSelectorResult epdgSelectorResult =
                 new EpdgSelectorResult(validIPList, result, transactionId);
         mHandler.sendMessage(
@@ -2121,6 +2239,11 @@
         return ErrorPolicyManager.getInstance(mContext, mSlotId).canBringUpTunnel(apnName);
     }
 
+    @VisibleForTesting
+    void setEpdgAddress(InetAddress inetAddress) {
+        mEpdgAddress = inetAddress;
+    }
+
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("---- EpdgTunnelManager ----");
         pw.println("mHasConnectedToEpdg: " + mHasConnectedToEpdg);
diff --git a/src/com/google/android/iwlan/proto/MetricsAtom.java b/src/com/google/android/iwlan/proto/MetricsAtom.java
new file mode 100644
index 0000000..33b0fcd
--- /dev/null
+++ b/src/com/google/android/iwlan/proto/MetricsAtom.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2020 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.google.android.iwlan.proto;
+
+import com.google.android.iwlan.IwlanStatsLog;
+
+public class MetricsAtom {
+    private int mMessageId;
+    private int mApnType;
+    private boolean mIsHandover;
+    private String mEpdgServerAddress;
+    private int mSourceRat;
+    private boolean mIsCellularRoaming;
+    private boolean mIsNetworkConnected;
+    private int mTransportType;
+    private int mSetupRequestResult;
+    private int mIwlanError;
+    private int mDataCallFailCause;
+    private int mProcessingDurationMillis;
+    private int mEpdgServerSelectionDurationMillis;
+    private int mIkeTunnelEstablishmentDurationMillis;
+    private int mTunnelState;
+    private int mHandoverFailureMode;
+    private int mRetryDurationMillis;
+    private int mWifiSignalValue;
+
+    public void setMessageId(int messageId) {
+        this.mMessageId = messageId;
+    }
+
+    public void setApnType(int apnType) {
+        this.mApnType = apnType;
+    }
+
+    public void setIsHandover(boolean isHandover) {
+        this.mIsHandover = isHandover;
+    }
+
+    public void setEpdgServerAddress(String epdgServerAddress) {
+        this.mEpdgServerAddress = epdgServerAddress;
+    }
+
+    public void setSourceRat(int sourceRat) {
+        this.mSourceRat = sourceRat;
+    }
+
+    public void setIsCellularRoaming(boolean isCellularRoaming) {
+        this.mIsCellularRoaming = isCellularRoaming;
+    }
+
+    public void setIsNetworkConnected(boolean isNetworkConnected) {
+        this.mIsNetworkConnected = isNetworkConnected;
+    }
+
+    public void setTransportType(int transportType) {
+        this.mTransportType = transportType;
+    }
+
+    public void setSetupRequestResult(int setupRequestResult) {
+        this.mSetupRequestResult = setupRequestResult;
+    }
+
+    public void setIwlanError(int iwlanError) {
+        this.mIwlanError = iwlanError;
+    }
+
+    public void setDataCallFailCause(int dataCallFailCause) {
+        this.mDataCallFailCause = dataCallFailCause;
+    }
+
+    public void setProcessingDurationMillis(int processingDurationMillis) {
+        this.mProcessingDurationMillis = processingDurationMillis;
+    }
+
+    public void setEpdgServerSelectionDurationMillis(int epdgServerSelectionDurationMillis) {
+        this.mEpdgServerSelectionDurationMillis = epdgServerSelectionDurationMillis;
+    }
+
+    public void setIkeTunnelEstablishmentDurationMillis(int ikeTunnelEstablishmentDurationMillis) {
+        this.mIkeTunnelEstablishmentDurationMillis = ikeTunnelEstablishmentDurationMillis;
+    }
+
+    public void setTunnelState(int tunnelState) {
+        this.mTunnelState = tunnelState;
+    }
+
+    public void setHandoverFailureMode(int handoverFailureMode) {
+        this.mHandoverFailureMode = handoverFailureMode;
+    }
+
+    public void setRetryDurationMillis(int retryDurationMillis) {
+        this.mRetryDurationMillis = retryDurationMillis;
+    }
+
+    public void setWifiSignalValue(int wifiSignalValue) {
+        this.mWifiSignalValue = wifiSignalValue;
+    }
+
+    public void sendMetricsData() {
+        if (mMessageId == IwlanStatsLog.IWLAN_SETUP_DATA_CALL_RESULT_REPORTED) {
+            IwlanStatsLog.write(
+                    mMessageId,
+                    mApnType,
+                    mIsHandover,
+                    mEpdgServerAddress,
+                    mSourceRat,
+                    mIsCellularRoaming,
+                    mIsNetworkConnected,
+                    mTransportType,
+                    mSetupRequestResult,
+                    mIwlanError,
+                    mDataCallFailCause,
+                    mProcessingDurationMillis,
+                    mEpdgServerSelectionDurationMillis,
+                    mIkeTunnelEstablishmentDurationMillis,
+                    mTunnelState,
+                    mHandoverFailureMode,
+                    mRetryDurationMillis);
+            return;
+        } else if (mMessageId == IwlanStatsLog.IWLAN_PDN_DISCONNECTED_REASON_REPORTED) {
+            IwlanStatsLog.write(
+                    mMessageId,
+                    mDataCallFailCause,
+                    mIsNetworkConnected,
+                    mTransportType,
+                    mWifiSignalValue);
+            return;
+        }
+    }
+}
diff --git a/test/com/google/android/iwlan/IwlanDataServiceTest.java b/test/com/google/android/iwlan/IwlanDataServiceTest.java
index 5165d31..bd74d80 100644
--- a/test/com/google/android/iwlan/IwlanDataServiceTest.java
+++ b/test/com/google/android/iwlan/IwlanDataServiceTest.java
@@ -51,6 +51,7 @@
 
 import com.google.android.iwlan.IwlanDataService.IwlanDataServiceProvider;
 import com.google.android.iwlan.IwlanDataService.IwlanDataServiceProvider.IwlanTunnelCallback;
+import com.google.android.iwlan.IwlanDataService.IwlanDataServiceProvider.IwlanTunnelCallbackMetrics;
 import com.google.android.iwlan.IwlanDataService.IwlanDataServiceProvider.TunnelState;
 import com.google.android.iwlan.IwlanDataService.IwlanNetworkMonitorCallback;
 import com.google.android.iwlan.epdg.EpdgSelector;
@@ -503,7 +504,10 @@
 
         /* Check bringUpTunnel() is called. */
         verify(mMockEpdgTunnelManager, times(1))
-                .bringUpTunnel(any(TunnelSetupRequest.class), any(IwlanTunnelCallback.class));
+                .bringUpTunnel(
+                        any(TunnelSetupRequest.class),
+                        any(IwlanTunnelCallback.class),
+                        any(IwlanTunnelCallbackMetrics.class));
 
         /* Check callback result is RESULT_SUCCESS when onOpened() is called. */
         mSpyIwlanDataServiceProvider
@@ -540,7 +544,10 @@
 
         /* Check bringUpTunnel() is called. */
         verify(mMockEpdgTunnelManager, times(1))
-                .bringUpTunnel(any(TunnelSetupRequest.class), any(IwlanTunnelCallback.class));
+                .bringUpTunnel(
+                        any(TunnelSetupRequest.class),
+                        any(IwlanTunnelCallback.class),
+                        any(IwlanTunnelCallbackMetrics.class));
 
         /* Check callback result is RESULT_SUCCESS when onOpened() is called. */
         TunnelLinkProperties tp = TunnelLinkPropertiesTest.createTestTunnelLinkProperties();
@@ -635,6 +642,16 @@
                 (setupDataReason == DataService.REQUEST_REASON_HANDOVER),
                 1);
 
+        mSpyIwlanDataServiceProvider.setMetricsAtom(
+                TEST_APN_NAME,
+                64, // type IMS
+                true,
+                13, // LTE
+                false,
+                true,
+                1 // Transport Wi-Fi
+                );
+
         mSpyIwlanDataServiceProvider
                 .getIwlanTunnelCallback()
                 .onClosed(TEST_APN_NAME, new IwlanError(IwlanError.NO_ERROR));
@@ -674,6 +691,16 @@
                 (setupDataReason == DataService.REQUEST_REASON_HANDOVER),
                 1);
 
+        mSpyIwlanDataServiceProvider.setMetricsAtom(
+                TEST_APN_NAME,
+                64, // type IMS
+                true,
+                13, // LTE
+                false,
+                true,
+                1 // Transport Wi-Fi
+                );
+
         mSpyIwlanDataServiceProvider
                 .getIwlanTunnelCallback()
                 .onClosed(TEST_APN_NAME, new IwlanError(IwlanError.NO_ERROR));
@@ -885,7 +912,10 @@
 
         /* Check bringUpTunnel() is called. */
         verify(mMockEpdgTunnelManager, times(1))
-                .bringUpTunnel(any(TunnelSetupRequest.class), any(IwlanTunnelCallback.class));
+                .bringUpTunnel(
+                        any(TunnelSetupRequest.class),
+                        any(IwlanTunnelCallback.class),
+                        any(IwlanTunnelCallbackMetrics.class));
 
         /* Check callback result is RESULT_SUCCESS when onOpened() is called. */
         mSpyIwlanDataServiceProvider
@@ -922,6 +952,11 @@
     public void testIwlanTunnelStatsUnsolDownCounts() {
         DataProfile dp = buildDataProfile();
 
+        when(ErrorPolicyManager.getInstance(eq(mMockContext), eq(DEFAULT_SLOT_INDEX)))
+                .thenReturn(mMockErrorPolicyManager);
+        when(mMockErrorPolicyManager.getDataFailCause(eq(TEST_APN_NAME)))
+                .thenReturn(DataFailCause.ERROR_UNSPECIFIED);
+
         mIwlanDataService.setNetworkConnected(true, mMockNetwork, IwlanDataService.Transport.WIFI);
         doReturn(mMockEpdgTunnelManager).when(mSpyIwlanDataServiceProvider).getTunnelManager();
 
@@ -992,6 +1027,21 @@
         mSpyIwlanDataServiceProvider.setTunnelState(
                 dp, mMockDataServiceCallback, TunnelState.TUNNEL_UP, null, false, 1);
 
+        mSpyIwlanDataServiceProvider.setMetricsAtom(
+                TEST_APN_NAME,
+                64, // type IMS
+                true,
+                13, // LTE
+                false,
+                true,
+                1 // Transport Wi-Fi
+                );
+
+        when(ErrorPolicyManager.getInstance(eq(mMockContext), eq(DEFAULT_SLOT_INDEX)))
+                .thenReturn(mMockErrorPolicyManager);
+        when(mMockErrorPolicyManager.getDataFailCause(eq(TEST_APN_NAME)))
+                .thenReturn(DataFailCause.ERROR_UNSPECIFIED);
+
         // Simulate IwlanDataService.onUnbind() which force close all tunnels
         mSpyIwlanDataServiceProvider.forceCloseTunnels();
         // Simulate DataService.onUnbind() which remove all IwlanDataServiceProviders
@@ -1022,7 +1072,10 @@
                 mMockDataServiceCallback);
         doReturn(true)
                 .when(mMockEpdgTunnelManager)
-                .bringUpTunnel(any(TunnelSetupRequest.class), any(IwlanTunnelCallback.class));
+                .bringUpTunnel(
+                        any(TunnelSetupRequest.class),
+                        any(IwlanTunnelCallback.class),
+                        any(IwlanTunnelCallbackMetrics.class));
 
         mSpyIwlanDataServiceProvider
                 .getIwlanTunnelCallback()
@@ -1048,7 +1101,10 @@
                 mMockDataServiceCallback);
         doReturn(true)
                 .when(mMockEpdgTunnelManager)
-                .bringUpTunnel(any(TunnelSetupRequest.class), any(IwlanTunnelCallback.class));
+                .bringUpTunnel(
+                        any(TunnelSetupRequest.class),
+                        any(IwlanTunnelCallback.class),
+                        any(IwlanTunnelCallbackMetrics.class));
         mTestLooper.dispatchAll();
 
         advanceCalendarByTimeMs(setupTime);
diff --git a/test/com/google/android/iwlan/epdg/EpdgTunnelManagerTest.java b/test/com/google/android/iwlan/epdg/EpdgTunnelManagerTest.java
index 3c589d6..28669ff 100644
--- a/test/com/google/android/iwlan/epdg/EpdgTunnelManagerTest.java
+++ b/test/com/google/android/iwlan/epdg/EpdgTunnelManagerTest.java
@@ -127,11 +127,27 @@
         public void onClosed(String apnName, IwlanError error) {}
     }
 
+    private static class IwlanTunnelCallbackMetrics
+            implements EpdgTunnelManager.TunnelCallbackMetrics {
+        public void onOpened(
+                String apnName,
+                String epdgServerAddress,
+                int epdgServerSelectionDuration,
+                int ikeTunnelEstablishmentDuration) {}
+
+        public void onClosed(
+                String apnName,
+                String epdgServerAddress,
+                int epdgServerSelectionDuration,
+                int ikeTunnelEstablishmentDuration) {}
+    }
+
     @Rule public final MockitoRule mockito = MockitoJUnit.rule();
     private TestLooper mTestLooper = new TestLooper();
 
     @Mock private Context mMockContext;
     @Mock private IwlanTunnelCallback mMockIwlanTunnelCallback;
+    @Mock private IwlanTunnelCallbackMetrics mMockIwlanTunnelCallbackMetrics;
     @Mock private IkeSession mMockIkeSession;
     @Mock private EpdgSelector mMockEpdgSelector;
     @Mock private Network mMockNetwork;
@@ -151,7 +167,6 @@
     @Mock IpSecTransform mMockedIpSecTransformOut;
     @Mock LinkProperties mMockLinkProperties;
 
-
     class IkeSessionArgumentCaptors {
         ArgumentCaptor<IkeSessionParams> mIkeSessionParamsCaptor =
                 ArgumentCaptor.forClass(IkeSessionParams.class);
@@ -214,7 +229,8 @@
         boolean ret =
                 mEpdgTunnelManager.bringUpTunnel(
                         getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_PPP),
-                        mMockIwlanTunnelCallback);
+                        mMockIwlanTunnelCallback,
+                        mMockIwlanTunnelCallbackMetrics);
         assertFalse(ret);
     }
 
@@ -223,13 +239,15 @@
         boolean ret =
                 mEpdgTunnelManager.bringUpTunnel(
                         getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IPV6, 16),
-                        mMockIwlanTunnelCallback);
+                        mMockIwlanTunnelCallback,
+                        mMockIwlanTunnelCallbackMetrics);
         assertFalse(ret);
 
         ret =
                 mEpdgTunnelManager.bringUpTunnel(
                         getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IPV6, -1),
-                        mMockIwlanTunnelCallback);
+                        mMockIwlanTunnelCallback,
+                        mMockIwlanTunnelCallbackMetrics);
         assertFalse(ret);
     }
 
@@ -252,13 +270,19 @@
         doReturn(true).when(mEpdgTunnelManager).canBringUpTunnel(eq(testApnName2));
         doReturn(true).when(mEpdgTunnelManager).canBringUpTunnel(eq(testApnName3));
 
-        boolean ret = mEpdgTunnelManager.bringUpTunnel(TSR_v4, mMockIwlanTunnelCallback);
+        boolean ret =
+                mEpdgTunnelManager.bringUpTunnel(
+                        TSR_v4, mMockIwlanTunnelCallback, mMockIwlanTunnelCallbackMetrics);
         assertTrue(ret);
 
-        ret = mEpdgTunnelManager.bringUpTunnel(TSR_v6, mMockIwlanTunnelCallback);
+        ret =
+                mEpdgTunnelManager.bringUpTunnel(
+                        TSR_v6, mMockIwlanTunnelCallback, mMockIwlanTunnelCallbackMetrics);
         assertTrue(ret);
 
-        ret = mEpdgTunnelManager.bringUpTunnel(TSR_v4v6, mMockIwlanTunnelCallback);
+        ret =
+                mEpdgTunnelManager.bringUpTunnel(
+                        TSR_v4v6, mMockIwlanTunnelCallback, mMockIwlanTunnelCallbackMetrics);
         assertTrue(ret);
     }
 
@@ -269,7 +293,9 @@
 
         when(mEpdgTunnelManager.getTunnelSetupRequestApnName(TSR)).thenReturn(null);
 
-        boolean ret = mEpdgTunnelManager.bringUpTunnel(TSR, mMockIwlanTunnelCallback);
+        boolean ret =
+                mEpdgTunnelManager.bringUpTunnel(
+                        TSR, mMockIwlanTunnelCallback, mMockIwlanTunnelCallbackMetrics);
         assertFalse(ret);
         verify(mEpdgTunnelManager).getTunnelSetupRequestApnName(TSR);
     }
@@ -280,7 +306,9 @@
 
         when(mEpdgTunnelManager.isTunnelConfigContainExistApn(TEST_APN_NAME)).thenReturn(true);
 
-        boolean ret = mEpdgTunnelManager.bringUpTunnel(TSR, mMockIwlanTunnelCallback);
+        boolean ret =
+                mEpdgTunnelManager.bringUpTunnel(
+                        TSR, mMockIwlanTunnelCallback, mMockIwlanTunnelCallbackMetrics);
         assertFalse(ret);
         verify(mEpdgTunnelManager).isTunnelConfigContainExistApn(TEST_APN_NAME);
     }
@@ -292,10 +320,17 @@
         TunnelSetupRequest TSR = getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP);
 
         mEpdgTunnelManager.putApnNameToTunnelConfig(
-                testApnName2, mMockIkeSession, mMockIwlanTunnelCallback, null, 0);
+                testApnName2,
+                mMockIkeSession,
+                mMockIwlanTunnelCallback,
+                mMockIwlanTunnelCallbackMetrics,
+                null,
+                0);
         doReturn(true).when(mEpdgTunnelManager).canBringUpTunnel(eq(TEST_APN_NAME));
 
-        boolean ret = mEpdgTunnelManager.bringUpTunnel(TSR, mMockIwlanTunnelCallback);
+        boolean ret =
+                mEpdgTunnelManager.bringUpTunnel(
+                        TSR, mMockIwlanTunnelCallback, mMockIwlanTunnelCallbackMetrics);
         assertTrue(ret);
     }
 
@@ -308,7 +343,9 @@
         PersistableBundle bundle = new PersistableBundle();
         setupMockForGetConfig(bundle);
 
-        boolean ret = mEpdgTunnelManager.bringUpTunnel(TSR, mMockIwlanTunnelCallback);
+        boolean ret =
+                mEpdgTunnelManager.bringUpTunnel(
+                        TSR, mMockIwlanTunnelCallback, mMockIwlanTunnelCallbackMetrics);
         assertTrue(ret);
         mTestLooper.dispatchAll();
 
@@ -339,7 +376,8 @@
         boolean ret =
                 mEpdgTunnelManager.bringUpTunnel(
                         getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP),
-                        mMockIwlanTunnelCallback);
+                        mMockIwlanTunnelCallback,
+                        mMockIwlanTunnelCallbackMetrics);
         assertTrue(ret);
         mTestLooper.dispatchAll();
 
@@ -454,7 +492,8 @@
         boolean ret =
                 mEpdgTunnelManager.bringUpTunnel(
                         getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP),
-                        mMockIwlanTunnelCallback);
+                        mMockIwlanTunnelCallback,
+                        mMockIwlanTunnelCallbackMetrics);
         assertTrue(ret);
         mTestLooper.dispatchAll();
 
@@ -518,7 +557,12 @@
         String testApnName = "www.xyz.com";
 
         mEpdgTunnelManager.putApnNameToTunnelConfig(
-                testApnName, mMockIkeSession, mMockIwlanTunnelCallback, null, 0);
+                testApnName,
+                mMockIkeSession,
+                mMockIwlanTunnelCallback,
+                mMockIwlanTunnelCallbackMetrics,
+                null,
+                0);
 
         boolean ret = mEpdgTunnelManager.closeTunnel(testApnName, true /*forceClose*/);
         assertTrue(ret);
@@ -533,7 +577,12 @@
         String testApnName = "www.xyz.com";
 
         mEpdgTunnelManager.putApnNameToTunnelConfig(
-                testApnName, mMockIkeSession, mMockIwlanTunnelCallback, null, 0);
+                testApnName,
+                mMockIkeSession,
+                mMockIwlanTunnelCallback,
+                mMockIwlanTunnelCallbackMetrics,
+                null,
+                0);
 
         boolean ret = mEpdgTunnelManager.closeTunnel(testApnName, false /*forceClose*/);
         assertTrue(ret);
@@ -588,7 +637,8 @@
         boolean ret =
                 mEpdgTunnelManager.bringUpTunnel(
                         getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP),
-                        mMockIwlanTunnelCallback);
+                        mMockIwlanTunnelCallback,
+                        mMockIwlanTunnelCallbackMetrics);
         assertTrue(ret);
         mTestLooper.dispatchAll();
 
@@ -654,7 +704,8 @@
         boolean ret =
                 mEpdgTunnelManager.bringUpTunnel(
                         getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP),
-                        mMockIwlanTunnelCallback);
+                        mMockIwlanTunnelCallback,
+                        mMockIwlanTunnelCallbackMetrics);
         assertTrue(ret);
         mTestLooper.dispatchAll();
 
@@ -711,7 +762,8 @@
         boolean ret =
                 mEpdgTunnelManager.bringUpTunnel(
                         getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP),
-                        mMockIwlanTunnelCallback);
+                        mMockIwlanTunnelCallback,
+                        mMockIwlanTunnelCallbackMetrics);
         assertTrue(ret);
         mTestLooper.dispatchAll();
 
@@ -770,7 +822,8 @@
         boolean ret =
                 mEpdgTunnelManager.bringUpTunnel(
                         getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP),
-                        mMockIwlanTunnelCallback);
+                        mMockIwlanTunnelCallback,
+                        mMockIwlanTunnelCallbackMetrics);
         assertTrue(ret);
         mTestLooper.dispatchAll();
 
@@ -827,7 +880,8 @@
         boolean ret =
                 mEpdgTunnelManager.bringUpTunnel(
                         getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP),
-                        mMockIwlanTunnelCallback);
+                        mMockIwlanTunnelCallback,
+                        mMockIwlanTunnelCallbackMetrics);
         assertTrue(ret);
         mTestLooper.dispatchAll();
 
@@ -873,7 +927,7 @@
         LinkAddress l1 = new LinkAddress(a1, 64);
         InetAddress src = InetAddress.getByName("2600:381:4872:5d1e:0:10:3582:a501");
         EpdgTunnelManager.TunnelConfig tf =
-                mEpdgTunnelManager.new TunnelConfig(null, null, src, 64);
+                mEpdgTunnelManager.new TunnelConfig(null, null, null, src, 64);
         assertTrue(tf.isPrefixSameAsSrcIP(l1));
 
         // different prefix length
@@ -950,7 +1004,8 @@
         boolean ret =
                 mEpdgTunnelManager.bringUpTunnel(
                         getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP),
-                        mMockIwlanTunnelCallback);
+                        mMockIwlanTunnelCallback,
+                        mMockIwlanTunnelCallbackMetrics);
         assertTrue(ret);
         mTestLooper.dispatchAll();
 
@@ -1081,9 +1136,15 @@
 
         doReturn(0L).when(mEpdgTunnelManager).reportIwlanError(eq(testApnName), eq(error));
         mEpdgTunnelManager.putApnNameToTunnelConfig(
-                testApnName, mMockIkeSession, mMockIwlanTunnelCallback, null, 0);
+                testApnName,
+                mMockIkeSession,
+                mMockIwlanTunnelCallback,
+                mMockIwlanTunnelCallbackMetrics,
+                null,
+                0);
 
         mEpdgTunnelManager.setHasConnectedToEpdg(true);
+        mEpdgTunnelManager.setEpdgAddress(InetAddresses.parseNumericAddress(EPDG_ADDRESS));
 
         mEpdgTunnelManager.getTmIkeSessionCallback(testApnName).onClosed();
         mTestLooper.dispatchAll();
@@ -1107,7 +1168,8 @@
         boolean ret =
                 mEpdgTunnelManager.bringUpTunnel(
                         getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP),
-                        mMockIwlanTunnelCallback);
+                        mMockIwlanTunnelCallback,
+                        mMockIwlanTunnelCallbackMetrics);
         assertTrue(ret);
         mTestLooper.dispatchAll();
 
@@ -1127,7 +1189,12 @@
 
     private void setOneTunnelOpened(String apnName) throws Exception {
         mEpdgTunnelManager.putApnNameToTunnelConfig(
-                apnName, mMockIkeSession, mMockIwlanTunnelCallback, null, 0);
+                apnName,
+                mMockIkeSession,
+                mMockIwlanTunnelCallback,
+                mMockIwlanTunnelCallbackMetrics,
+                null,
+                0);
         setVariable(mEpdgTunnelManager, "mLocalAddresses", EXPECTED_LOCAL_ADDRESSES);
         mEpdgTunnelManager.validateAndSetEpdgAddress(EXPECTED_EPDG_ADDRESSES);
         mEpdgTunnelManager.setHasConnectedToEpdg(true);
@@ -1190,7 +1257,8 @@
         boolean ret =
                 mEpdgTunnelManager.bringUpTunnel(
                         getBasicTunnelSetupRequest(apnName, ApnSetting.PROTOCOL_IP),
-                        mMockIwlanTunnelCallback);
+                        mMockIwlanTunnelCallback,
+                        mMockIwlanTunnelCallbackMetrics);
         assertTrue(ret);
         mTestLooper.dispatchAll();
 
@@ -1268,9 +1336,15 @@
         doReturn(0L).when(mEpdgTunnelManager).reportIwlanError(eq(testApnName), eq(error));
 
         mEpdgTunnelManager.putApnNameToTunnelConfig(
-                testApnName, mMockIkeSession, mMockIwlanTunnelCallback, null, 0);
+                testApnName,
+                mMockIkeSession,
+                mMockIwlanTunnelCallback,
+                mMockIwlanTunnelCallbackMetrics,
+                null,
+                0);
 
         mEpdgTunnelManager.setHasConnectedToEpdg(true);
+        mEpdgTunnelManager.setEpdgAddress(InetAddresses.parseNumericAddress(EPDG_ADDRESS));
 
         mEpdgTunnelManager
                 .getTmIkeSessionCallback(testApnName)
@@ -1296,7 +1370,8 @@
         boolean ret =
                 mEpdgTunnelManager.bringUpTunnel(
                         getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP),
-                        mMockIwlanTunnelCallback);
+                        mMockIwlanTunnelCallback,
+                        mMockIwlanTunnelCallbackMetrics);
         assertTrue(ret);
         mTestLooper.dispatchAll();
 
@@ -1323,7 +1398,12 @@
 
         doReturn(0L).when(mEpdgTunnelManager).reportIwlanError(eq(testApnName), eq(error));
         mEpdgTunnelManager.putApnNameToTunnelConfig(
-                testApnName, mMockIkeSession, mMockIwlanTunnelCallback, null, 0);
+                testApnName,
+                mMockIkeSession,
+                mMockIwlanTunnelCallback,
+                mMockIwlanTunnelCallbackMetrics,
+                null,
+                0);
 
         when(mMockIkeSessionConfiguration.getPcscfServers()).thenReturn(EXPECTED_EPDG_ADDRESSES);
 
@@ -1458,12 +1538,14 @@
             ret =
                     mEpdgTunnelManager.bringUpTunnel(
                             getHandoverTunnelSetupRequest(TEST_APN_NAME, apnProtocol),
-                            mMockIwlanTunnelCallback);
+                            mMockIwlanTunnelCallback,
+                            mMockIwlanTunnelCallbackMetrics);
         } else {
             ret =
                     mEpdgTunnelManager.bringUpTunnel(
                             getBasicTunnelSetupRequest(TEST_APN_NAME, apnProtocol),
-                            mMockIwlanTunnelCallback);
+                            mMockIwlanTunnelCallback,
+                            mMockIwlanTunnelCallbackMetrics);
         }
 
         assertTrue(ret);
@@ -1563,9 +1645,15 @@
         doReturn(0L).when(mEpdgTunnelManager).reportIwlanError(eq(testApnName), eq(error));
 
         mEpdgTunnelManager.putApnNameToTunnelConfig(
-                testApnName, mMockIkeSession, mMockIwlanTunnelCallback, null, 0);
+                testApnName,
+                mMockIkeSession,
+                mMockIwlanTunnelCallback,
+                mMockIwlanTunnelCallbackMetrics,
+                null,
+                0);
 
         mEpdgTunnelManager.setHasConnectedToEpdg(true);
+        mEpdgTunnelManager.setEpdgAddress(InetAddresses.parseNumericAddress(EPDG_ADDRESS));
 
         mEpdgTunnelManager
                 .getTmIkeSessionCallback(testApnName)
@@ -1591,7 +1679,8 @@
         boolean ret =
                 mEpdgTunnelManager.bringUpTunnel(
                         getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP),
-                        mMockIwlanTunnelCallback);
+                        mMockIwlanTunnelCallback,
+                        mMockIwlanTunnelCallbackMetrics);
         assertTrue(ret);
         mTestLooper.dispatchAll();
 
@@ -1640,7 +1729,8 @@
         boolean ret =
                 mEpdgTunnelManager.bringUpTunnel(
                         getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP),
-                        mMockIwlanTunnelCallback);
+                        mMockIwlanTunnelCallback,
+                        mMockIwlanTunnelCallbackMetrics);
         assertTrue(ret);
         mTestLooper.dispatchAll();
         verify(mMockIwlanTunnelCallback, times(1)).onClosed(eq(testApnName), eq(error));
@@ -1680,7 +1770,8 @@
                 mEpdgTunnelManager.bringUpTunnel(
                         getBasicTunnelSetupRequest(
                                 TEST_APN_NAME, ApnSetting.PROTOCOL_IPV6, PDU_SESSION_ID),
-                        mMockIwlanTunnelCallback);
+                        mMockIwlanTunnelCallback,
+                        mMockIwlanTunnelCallbackMetrics);
 
         assertTrue(ret);
         mTestLooper.dispatchAll();
@@ -1747,7 +1838,8 @@
         boolean ret =
                 mEpdgTunnelManager.bringUpTunnel(
                         getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP),
-                        mMockIwlanTunnelCallback);
+                        mMockIwlanTunnelCallback,
+                        mMockIwlanTunnelCallbackMetrics);
         assertTrue(ret);
         mTestLooper.dispatchAll();
 
@@ -1817,7 +1909,9 @@
                         any(ChildSessionCallback.class));
         doReturn(true).when(mEpdgTunnelManager).canBringUpTunnel(eq(testApnName));
 
-        boolean ret = mEpdgTunnelManager.bringUpTunnel(tsr, mMockIwlanTunnelCallback);
+        boolean ret =
+                mEpdgTunnelManager.bringUpTunnel(
+                        tsr, mMockIwlanTunnelCallback, mMockIwlanTunnelCallbackMetrics);
         assertTrue(ret);
         mTestLooper.dispatchAll();
 
@@ -1922,7 +2016,8 @@
         boolean ret =
                 mEpdgTunnelManager.bringUpTunnel(
                         getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP),
-                        mMockIwlanTunnelCallback);
+                        mMockIwlanTunnelCallback,
+                        mMockIwlanTunnelCallbackMetrics);
         assertTrue(ret);
         mTestLooper.dispatchAll();
 
@@ -1951,7 +2046,8 @@
         boolean ret =
                 mEpdgTunnelManager.bringUpTunnel(
                         getBasicTunnelSetupRequest(TEST_APN_NAME, ApnSetting.PROTOCOL_IP),
-                        mMockIwlanTunnelCallback);
+                        mMockIwlanTunnelCallback,
+                        mMockIwlanTunnelCallbackMetrics);
         assertTrue(ret);
         mTestLooper.dispatchAll();