Merge "Add extra logging"
diff --git a/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
index 69e6313..2d1a3fe 100644
--- a/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
+++ b/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
@@ -57,9 +57,11 @@
     @NonNull private final Set<Integer> mAllowedSpecificCarrierIds;
 
     private static final String ROAMING_MATCH_KEY = "mRoamingMatchCriteria";
+    private static final int DEFAULT_ROAMING_MATCH_CRITERIA = MATCH_ANY;
     private final int mRoamingMatchCriteria;
 
     private static final String OPPORTUNISTIC_MATCH_KEY = "mOpportunisticMatchCriteria";
+    private static final int DEFAULT_OPPORTUNISTIC_MATCH_CRITERIA = MATCH_ANY;
     private final int mOpportunisticMatchCriteria;
 
     private VcnCellUnderlyingNetworkTemplate(
@@ -253,23 +255,31 @@
     /** @hide */
     @Override
     void dumpTransportSpecificFields(IndentingPrintWriter pw) {
-        pw.println("mAllowedNetworkPlmnIds: " + mAllowedNetworkPlmnIds.toString());
-        pw.println("mAllowedSpecificCarrierIds: " + mAllowedSpecificCarrierIds.toString());
-        pw.println("mRoamingMatchCriteria: " + getMatchCriteriaString(mRoamingMatchCriteria));
-        pw.println(
-                "mOpportunisticMatchCriteria: "
-                        + getMatchCriteriaString(mOpportunisticMatchCriteria));
+        if (!mAllowedNetworkPlmnIds.isEmpty()) {
+            pw.println("mAllowedNetworkPlmnIds: " + mAllowedNetworkPlmnIds);
+        }
+        if (!mAllowedNetworkPlmnIds.isEmpty()) {
+            pw.println("mAllowedSpecificCarrierIds: " + mAllowedSpecificCarrierIds);
+        }
+        if (mRoamingMatchCriteria != DEFAULT_ROAMING_MATCH_CRITERIA) {
+            pw.println("mRoamingMatchCriteria: " + getMatchCriteriaString(mRoamingMatchCriteria));
+        }
+        if (mOpportunisticMatchCriteria != DEFAULT_OPPORTUNISTIC_MATCH_CRITERIA) {
+            pw.println(
+                    "mOpportunisticMatchCriteria: "
+                            + getMatchCriteriaString(mOpportunisticMatchCriteria));
+        }
     }
 
     /** This class is used to incrementally build VcnCellUnderlyingNetworkTemplate objects. */
     public static final class Builder {
-        private int mMeteredMatchCriteria = MATCH_ANY;
+        private int mMeteredMatchCriteria = DEFAULT_METERED_MATCH_CRITERIA;
 
         @NonNull private final Set<String> mAllowedNetworkPlmnIds = new ArraySet<>();
         @NonNull private final Set<Integer> mAllowedSpecificCarrierIds = new ArraySet<>();
 
-        private int mRoamingMatchCriteria = MATCH_ANY;
-        private int mOpportunisticMatchCriteria = MATCH_ANY;
+        private int mRoamingMatchCriteria = DEFAULT_ROAMING_MATCH_CRITERIA;
+        private int mOpportunisticMatchCriteria = DEFAULT_OPPORTUNISTIC_MATCH_CRITERIA;
 
         private int mMinEntryUpstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS;
         private int mMinExitUpstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS;
diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java
index 3a9ca3e..9235d09 100644
--- a/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java
+++ b/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java
@@ -15,8 +15,6 @@
  */
 package android.net.vcn;
 
-import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY;
-
 import static com.android.internal.annotations.VisibleForTesting.Visibility;
 
 import android.annotation.IntDef;
@@ -88,6 +86,9 @@
     /** @hide */
     static final String METERED_MATCH_KEY = "mMeteredMatchCriteria";
 
+    /** @hide */
+    static final int DEFAULT_METERED_MATCH_CRITERIA = MATCH_ANY;
+
     private final int mMeteredMatchCriteria;
 
     /** @hide */
@@ -237,11 +238,21 @@
         pw.println(this.getClass().getSimpleName() + ":");
         pw.increaseIndent();
 
-        pw.println("mMeteredMatchCriteria: " + getMatchCriteriaString(mMeteredMatchCriteria));
-        pw.println("mMinEntryUpstreamBandwidthKbps: " + mMinEntryUpstreamBandwidthKbps);
-        pw.println("mMinExitUpstreamBandwidthKbps: " + mMinExitUpstreamBandwidthKbps);
-        pw.println("mMinEntryDownstreamBandwidthKbps: " + mMinEntryDownstreamBandwidthKbps);
-        pw.println("mMinExitDownstreamBandwidthKbps: " + mMinExitDownstreamBandwidthKbps);
+        if (mMeteredMatchCriteria != DEFAULT_METERED_MATCH_CRITERIA) {
+            pw.println("mMeteredMatchCriteria: " + getMatchCriteriaString(mMeteredMatchCriteria));
+        }
+        if (mMinEntryUpstreamBandwidthKbps != DEFAULT_MIN_BANDWIDTH_KBPS) {
+            pw.println("mMinEntryUpstreamBandwidthKbps: " + mMinEntryUpstreamBandwidthKbps);
+        }
+        if (mMinExitUpstreamBandwidthKbps != DEFAULT_MIN_BANDWIDTH_KBPS) {
+            pw.println("mMinExitUpstreamBandwidthKbps: " + mMinExitUpstreamBandwidthKbps);
+        }
+        if (mMinEntryDownstreamBandwidthKbps != DEFAULT_MIN_BANDWIDTH_KBPS) {
+            pw.println("mMinEntryDownstreamBandwidthKbps: " + mMinEntryDownstreamBandwidthKbps);
+        }
+        if (mMinExitDownstreamBandwidthKbps != DEFAULT_MIN_BANDWIDTH_KBPS) {
+            pw.println("mMinExitDownstreamBandwidthKbps: " + mMinExitDownstreamBandwidthKbps);
+        }
         dumpTransportSpecificFields(pw);
 
         pw.decreaseIndent();
diff --git a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
index 23a07ab..2544a6d 100644
--- a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
+++ b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
@@ -147,7 +147,9 @@
     /** @hide */
     @Override
     void dumpTransportSpecificFields(IndentingPrintWriter pw) {
-        pw.println("mSsids: " + mSsids);
+        if (!mSsids.isEmpty()) {
+            pw.println("mSsids: " + mSsids);
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 2d328d8..210532a 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -153,7 +153,7 @@
 public class VcnManagementService extends IVcnManagementService.Stub {
     @NonNull private static final String TAG = VcnManagementService.class.getSimpleName();
     private static final long DUMP_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(5);
-    private static final int LOCAL_LOG_LINE_COUNT = 128;
+    private static final int LOCAL_LOG_LINE_COUNT = 512;
 
     // Public for use in all other VCN classes
     @NonNull public static final LocalLog LOCAL_LOG = new LocalLog(LOCAL_LOG_LINE_COUNT);
@@ -456,7 +456,7 @@
             synchronized (mLock) {
                 final TelephonySubscriptionSnapshot oldSnapshot = mLastSnapshot;
                 mLastSnapshot = snapshot;
-                logDbg("new snapshot: " + mLastSnapshot);
+                logInfo("new snapshot: " + mLastSnapshot);
 
                 // Start any VCN instances as necessary
                 for (Entry<ParcelUuid, VcnConfig> entry : mConfigs.entrySet()) {
@@ -522,6 +522,8 @@
 
     @GuardedBy("mLock")
     private void stopVcnLocked(@NonNull ParcelUuid uuidToTeardown) {
+        logInfo("Stopping VCN config for subGrp: " + uuidToTeardown);
+
         // Remove in 2 steps. Make sure teardownAsync is triggered before removing from the map.
         final Vcn vcnToTeardown = mVcns.get(uuidToTeardown);
         if (vcnToTeardown == null) {
@@ -567,7 +569,7 @@
 
     @GuardedBy("mLock")
     private void startVcnLocked(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) {
-        logDbg("Starting VCN config for subGrp: " + subscriptionGroup);
+        logInfo("Starting VCN config for subGrp: " + subscriptionGroup);
 
         // TODO(b/193687515): Support multiple VCNs active at the same time
         if (!mVcns.isEmpty()) {
@@ -626,7 +628,7 @@
         if (!config.getProvisioningPackageName().equals(opPkgName)) {
             throw new IllegalArgumentException("Mismatched caller and VcnConfig creator");
         }
-        logDbg("VCN config updated for subGrp: " + subscriptionGroup);
+        logInfo("VCN config updated for subGrp: " + subscriptionGroup);
 
         mContext.getSystemService(AppOpsManager.class)
                 .checkPackage(mDeps.getBinderCallingUid(), config.getProvisioningPackageName());
@@ -652,7 +654,7 @@
     public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup, @NonNull String opPkgName) {
         requireNonNull(subscriptionGroup, "subscriptionGroup was null");
         requireNonNull(opPkgName, "opPkgName was null");
-        logDbg("VCN config cleared for subGrp: " + subscriptionGroup);
+        logInfo("VCN config cleared for subGrp: " + subscriptionGroup);
 
         mContext.getSystemService(AppOpsManager.class)
                 .checkPackage(mDeps.getBinderCallingUid(), opPkgName);
@@ -1050,24 +1052,34 @@
         Slog.d(TAG, msg, tr);
     }
 
+    private void logInfo(String msg) {
+        Slog.i(TAG, msg);
+        LOCAL_LOG.log("[INFO] [" + TAG + "] " + msg);
+    }
+
+    private void logInfo(String msg, Throwable tr) {
+        Slog.i(TAG, msg, tr);
+        LOCAL_LOG.log("[INFO] [" + TAG + "] " + msg + tr);
+    }
+
     private void logErr(String msg) {
         Slog.e(TAG, msg);
-        LOCAL_LOG.log(TAG + " ERR: " + msg);
+        LOCAL_LOG.log("[ERR] [" + TAG + "] " + msg);
     }
 
     private void logErr(String msg, Throwable tr) {
         Slog.e(TAG, msg, tr);
-        LOCAL_LOG.log(TAG + " ERR: " + msg + tr);
+        LOCAL_LOG.log("[ERR ] [" + TAG + "] " + msg + tr);
     }
 
     private void logWtf(String msg) {
         Slog.wtf(TAG, msg);
-        LOCAL_LOG.log(TAG + " WTF: " + msg);
+        LOCAL_LOG.log("[WTF] [" + TAG + "] " + msg);
     }
 
     private void logWtf(String msg, Throwable tr) {
         Slog.wtf(TAG, msg, tr);
-        LOCAL_LOG.log(TAG + " WTF: " + msg + tr);
+        LOCAL_LOG.log("[WTF ] [" + TAG + "] " + msg + tr);
     }
 
     /**
diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java
index f29c40f..37f0450 100644
--- a/services/core/java/com/android/server/vcn/Vcn.java
+++ b/services/core/java/com/android/server/vcn/Vcn.java
@@ -341,6 +341,9 @@
                 if (gatewayConnection == null) {
                     logWtf("Found gatewayConnectionConfig without GatewayConnection");
                 } else {
+                    logInfo(
+                            "Config updated, restarting gateway "
+                                    + gatewayConnection.getLogPrefix());
                     gatewayConnection.teardownAsynchronously();
                 }
             }
@@ -397,7 +400,7 @@
         // If preexisting VcnGatewayConnection(s) satisfy request, return
         for (VcnGatewayConnectionConfig gatewayConnectionConfig : mVcnGatewayConnections.keySet()) {
             if (isRequestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) {
-                logDbg("Request already satisfied by existing VcnGatewayConnection: " + request);
+                logVdbg("Request already satisfied by existing VcnGatewayConnection: " + request);
                 return;
             }
         }
@@ -407,8 +410,6 @@
         for (VcnGatewayConnectionConfig gatewayConnectionConfig :
                 mConfig.getGatewayConnectionConfigs()) {
             if (isRequestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) {
-                logDbg("Bringing up new VcnGatewayConnection for request " + request);
-
                 if (getExposedCapabilitiesForMobileDataState(gatewayConnectionConfig).isEmpty()) {
                     // Skip; this network does not provide any services if mobile data is disabled.
                     continue;
@@ -424,6 +425,7 @@
                     return;
                 }
 
+                logInfo("Bringing up new VcnGatewayConnection for request " + request);
                 final VcnGatewayConnection vcnGatewayConnection =
                         mDeps.newVcnGatewayConnection(
                                 mVcnContext,
@@ -455,7 +457,7 @@
     }
 
     private void handleGatewayConnectionQuit(VcnGatewayConnectionConfig config) {
-        logDbg("VcnGatewayConnection quit: " + config);
+        logInfo("VcnGatewayConnection quit: " + config);
         mVcnGatewayConnections.remove(config);
 
         // Trigger a re-evaluation of all NetworkRequests (to make sure any that can be satisfied
@@ -534,7 +536,7 @@
             // Trigger re-evaluation of all requests; mobile data state impacts supported caps.
             mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener);
 
-            logDbg("Mobile data " + (mIsMobileDataEnabled ? "enabled" : "disabled"));
+            logInfo("Mobile data " + (mIsMobileDataEnabled ? "enabled" : "disabled"));
         }
     }
 
@@ -569,11 +571,11 @@
     }
 
     private String getLogPrefix() {
-        return "["
+        return "("
                 + LogUtils.getHashedSubscriptionGroup(mSubscriptionGroup)
                 + "-"
                 + System.identityHashCode(this)
-                + "] ";
+                + ") ";
     }
 
     private void logVdbg(String msg) {
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index be38005..cefd8ef 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -732,14 +732,11 @@
         logDbg("Triggering async teardown");
         sendDisconnectRequestedAndAcquireWakelock(
                 DISCONNECT_REASON_TEARDOWN, true /* shouldQuit */);
-
-        // TODO: Notify VcnInstance (via callbacks) of permanent teardown of this tunnel, since this
-        // is also called asynchronously when a NetworkAgent becomes unwanted
     }
 
     @Override
     protected void onQuitting() {
-        logDbg("Quitting VcnGatewayConnection");
+        logInfo("Quitting VcnGatewayConnection");
 
         if (mNetworkAgent != null) {
             logWtf("NetworkAgent was non-null in onQuitting");
@@ -794,7 +791,7 @@
             // TODO(b/180132994): explore safely removing this Thread check
             mVcnContext.ensureRunningOnLooperThread();
 
-            logDbg(
+            logInfo(
                     "Selected underlying network changed: "
                             + (underlying == null ? null : underlying.network));
 
@@ -1335,7 +1332,7 @@
         protected void handleDisconnectRequested(EventDisconnectRequestedInfo info) {
             // TODO(b/180526152): notify VcnStatusCallback for Network loss
 
-            logDbg("Tearing down. Cause: " + info.reason);
+            logInfo("Tearing down. Cause: " + info.reason + "; quitting = " + info.shouldQuit);
             if (info.shouldQuit) {
                 mIsQuitting.setTrue();
             }
@@ -1353,7 +1350,7 @@
 
         protected void handleSafeModeTimeoutExceeded() {
             mSafeModeTimeoutAlarm = null;
-            logDbg("Entering safe mode after timeout exceeded");
+            logInfo("Entering safe mode after timeout exceeded");
 
             // Connectivity for this GatewayConnection is broken; tear down the Network.
             teardownNetwork();
@@ -1362,7 +1359,7 @@
         }
 
         protected void logUnexpectedEvent(int what) {
-            logDbg(
+            logVdbg(
                     "Unexpected event code "
                             + what
                             + " in state "
@@ -1672,7 +1669,7 @@
                                     return;
                                 }
 
-                                logDbg("NetworkAgent was unwanted");
+                                logInfo("NetworkAgent was unwanted");
                                 teardownAsynchronously();
                             } /* networkUnwantedCallback */,
                             (status) -> {
@@ -1748,7 +1745,7 @@
                             tunnelIface, IpSecManager.DIRECTION_FWD, transform);
                 }
             } catch (IOException e) {
-                logDbg("Transform application failed for network " + token, e);
+                logInfo("Transform application failed for network " + token, e);
                 sessionLost(token, e);
             }
         }
@@ -1782,7 +1779,7 @@
                     tunnelIface.removeAddress(address.getAddress(), address.getPrefixLength());
                 }
             } catch (IOException e) {
-                logDbg("Adding address to tunnel failed for token " + token, e);
+                logInfo("Adding address to tunnel failed for token " + token, e);
                 sessionLost(token, e);
             }
         }
@@ -1862,7 +1859,7 @@
         }
 
         private void handleMigrationCompleted(EventMigrationCompletedInfo migrationCompletedInfo) {
-            logDbg("Migration completed: " + mUnderlying.network);
+            logInfo("Migration completed: " + mUnderlying.network);
 
             applyTransform(
                     mCurrentToken,
@@ -1890,7 +1887,7 @@
             mUnderlying = ((EventUnderlyingNetworkChangedInfo) msg.obj).newUnderlying;
 
             if (mUnderlying == null) {
-                logDbg("Underlying network lost");
+                logInfo("Underlying network lost");
 
                 // Ignored for now; a new network may be coming up. If none does, the delayed
                 // NETWORK_LOST disconnect will be fired, and tear down the session + network.
@@ -1900,7 +1897,7 @@
             // mUnderlying assumed non-null, given check above.
             // If network changed, migrate. Otherwise, update any existing networkAgent.
             if (oldUnderlying == null || !oldUnderlying.network.equals(mUnderlying.network)) {
-                logDbg("Migrating to new network: " + mUnderlying.network);
+                logInfo("Migrating to new network: " + mUnderlying.network);
                 mIkeSession.setNetwork(mUnderlying.network);
             } else {
                 // oldUnderlying is non-null & underlying network itself has not changed
@@ -2168,13 +2165,13 @@
 
         @Override
         public void onClosedExceptionally(@NonNull IkeException exception) {
-            logDbg("IkeClosedExceptionally for token " + mToken, exception);
+            logInfo("IkeClosedExceptionally for token " + mToken, exception);
             sessionClosed(mToken, exception);
         }
 
         @Override
         public void onError(@NonNull IkeProtocolException exception) {
-            logDbg("IkeError for token " + mToken, exception);
+            logInfo("IkeError for token " + mToken, exception);
             // Non-fatal, log and continue.
         }
     }
@@ -2208,7 +2205,7 @@
 
         @Override
         public void onClosedExceptionally(@NonNull IkeException exception) {
-            logDbg("ChildClosedExceptionally for token " + mToken, exception);
+            logInfo("ChildClosedExceptionally for token " + mToken, exception);
             sessionLost(mToken, exception);
         }
 
@@ -2234,14 +2231,19 @@
         }
     }
 
-    private String getLogPrefix() {
-        return "["
+    // Used in Vcn.java, but must be public for mockito to mock this.
+    public String getLogPrefix() {
+        return "("
                 + LogUtils.getHashedSubscriptionGroup(mSubscriptionGroup)
                 + "-"
                 + mConnectionConfig.getGatewayConnectionName()
                 + "-"
                 + System.identityHashCode(this)
-                + "] ";
+                + ") ";
+    }
+
+    private String getTagLogPrefix() {
+        return "[ " + TAG + " " + getLogPrefix() + "]";
     }
 
     private void logVdbg(String msg) {
@@ -2258,34 +2260,44 @@
         Slog.d(TAG, getLogPrefix() + msg, tr);
     }
 
+    private void logInfo(String msg) {
+        Slog.i(TAG, getLogPrefix() + msg);
+        LOCAL_LOG.log("[INFO] " + getTagLogPrefix() + msg);
+    }
+
+    private void logInfo(String msg, Throwable tr) {
+        Slog.i(TAG, getLogPrefix() + msg, tr);
+        LOCAL_LOG.log("[INFO] " + getTagLogPrefix() + msg + tr);
+    }
+
     private void logWarn(String msg) {
         Slog.w(TAG, getLogPrefix() + msg);
-        LOCAL_LOG.log(getLogPrefix() + "WARN: " + msg);
+        LOCAL_LOG.log("[WARN] " + getTagLogPrefix() + msg);
     }
 
     private void logWarn(String msg, Throwable tr) {
         Slog.w(TAG, getLogPrefix() + msg, tr);
-        LOCAL_LOG.log(getLogPrefix() + "WARN: " + msg + tr);
+        LOCAL_LOG.log("[WARN] " + getTagLogPrefix() + msg + tr);
     }
 
     private void logErr(String msg) {
         Slog.e(TAG, getLogPrefix() + msg);
-        LOCAL_LOG.log(getLogPrefix() + "ERR: " + msg);
+        LOCAL_LOG.log("[ERR ] " + getTagLogPrefix() + msg);
     }
 
     private void logErr(String msg, Throwable tr) {
         Slog.e(TAG, getLogPrefix() + msg, tr);
-        LOCAL_LOG.log(getLogPrefix() + "ERR: " + msg + tr);
+        LOCAL_LOG.log("[ERR ] " + getTagLogPrefix() + msg + tr);
     }
 
     private void logWtf(String msg) {
         Slog.wtf(TAG, getLogPrefix() + msg);
-        LOCAL_LOG.log(getLogPrefix() + "WTF: " + msg);
+        LOCAL_LOG.log("[WTF ] " + msg);
     }
 
     private void logWtf(String msg, Throwable tr) {
         Slog.wtf(TAG, getLogPrefix() + msg, tr);
-        LOCAL_LOG.log(getLogPrefix() + "WTF: " + msg + tr);
+        LOCAL_LOG.log("[WTF ] " + msg + tr);
     }
 
     /**
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
index ca2e449..a3babf7 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
@@ -48,6 +48,7 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 import com.android.server.vcn.VcnContext;
+import com.android.server.vcn.util.LogUtils;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -368,6 +369,18 @@
             return;
         }
 
+        String allNetworkPriorities = "";
+        for (UnderlyingNetworkRecord record : sorted) {
+            if (!allNetworkPriorities.isEmpty()) {
+                allNetworkPriorities += ", ";
+            }
+            allNetworkPriorities += record.network + ": " + record.getPriorityClass();
+        }
+        logInfo(
+                "Selected network changed to "
+                        + (candidate == null ? null : candidate.network)
+                        + ", selected from list: "
+                        + allNetworkPriorities);
         mCurrentRecord = candidate;
         mCb.onSelectedUnderlyingNetworkChanged(mCurrentRecord);
     }
@@ -478,14 +491,38 @@
         }
     }
 
-    private static void logWtf(String msg) {
-        Slog.wtf(TAG, msg);
-        LOCAL_LOG.log(TAG + " WTF: " + msg);
+    private String getLogPrefix() {
+        return "("
+                + LogUtils.getHashedSubscriptionGroup(mSubscriptionGroup)
+                + "-"
+                + mConnectionConfig.getGatewayConnectionName()
+                + "-"
+                + System.identityHashCode(this)
+                + ") ";
     }
 
-    private static void logWtf(String msg, Throwable tr) {
+    private String getTagLogPrefix() {
+        return "[ " + TAG + " " + getLogPrefix() + "]";
+    }
+
+    private void logInfo(String msg) {
+        Slog.i(TAG, getLogPrefix() + msg);
+        LOCAL_LOG.log("[INFO] " + getTagLogPrefix() + msg);
+    }
+
+    private void logInfo(String msg, Throwable tr) {
+        Slog.i(TAG, getLogPrefix() + msg, tr);
+        LOCAL_LOG.log("[INFO] " + getTagLogPrefix() + msg + tr);
+    }
+
+    private void logWtf(String msg) {
+        Slog.wtf(TAG, msg);
+        LOCAL_LOG.log(TAG + "[WTF ] " + getTagLogPrefix() + msg);
+    }
+
+    private void logWtf(String msg, Throwable tr) {
         Slog.wtf(TAG, msg, tr);
-        LOCAL_LOG.log(TAG + " WTF: " + msg + tr);
+        LOCAL_LOG.log(TAG + "[WTF ] " + getTagLogPrefix() + msg + tr);
     }
 
     /** Dumps the state of this record for logging and debugging purposes. */
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
index c0488b1..06f9280 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
@@ -41,11 +41,15 @@
  * @hide
  */
 public class UnderlyingNetworkRecord {
+    private static final int PRIORITY_CLASS_INVALID = Integer.MAX_VALUE;
+
     @NonNull public final Network network;
     @NonNull public final NetworkCapabilities networkCapabilities;
     @NonNull public final LinkProperties linkProperties;
     public final boolean isBlocked;
 
+    private int mPriorityClass = PRIORITY_CLASS_INVALID;
+
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     public UnderlyingNetworkRecord(
             @NonNull Network network,
@@ -58,6 +62,34 @@
         this.isBlocked = isBlocked;
     }
 
+    private int getOrCalculatePriorityClass(
+            VcnContext vcnContext,
+            List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+            ParcelUuid subscriptionGroup,
+            TelephonySubscriptionSnapshot snapshot,
+            UnderlyingNetworkRecord currentlySelected,
+            PersistableBundle carrierConfig) {
+        // Never changes after the underlying network record is created.
+        if (mPriorityClass == PRIORITY_CLASS_INVALID) {
+            mPriorityClass =
+                    NetworkPriorityClassifier.calculatePriorityClass(
+                            vcnContext,
+                            this,
+                            underlyingNetworkTemplates,
+                            subscriptionGroup,
+                            snapshot,
+                            currentlySelected,
+                            carrierConfig);
+        }
+
+        return mPriorityClass;
+    }
+
+    // Used in UnderlyingNetworkController
+    int getPriorityClass() {
+        return mPriorityClass;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
@@ -84,18 +116,16 @@
             PersistableBundle carrierConfig) {
         return (left, right) -> {
             final int leftIndex =
-                    NetworkPriorityClassifier.calculatePriorityClass(
+                    left.getOrCalculatePriorityClass(
                             vcnContext,
-                            left,
                             underlyingNetworkTemplates,
                             subscriptionGroup,
                             snapshot,
                             currentlySelected,
                             carrierConfig);
             final int rightIndex =
-                    NetworkPriorityClassifier.calculatePriorityClass(
+                    right.getOrCalculatePriorityClass(
                             vcnContext,
-                            right,
                             underlyingNetworkTemplates,
                             subscriptionGroup,
                             snapshot,
@@ -142,16 +172,15 @@
         pw.increaseIndent();
 
         final int priorityIndex =
-                NetworkPriorityClassifier.calculatePriorityClass(
+                getOrCalculatePriorityClass(
                         vcnContext,
-                        this,
                         underlyingNetworkTemplates,
                         subscriptionGroup,
                         snapshot,
                         currentlySelected,
                         carrierConfig);
 
-        pw.println("Priority index:" + priorityIndex);
+        pw.println("Priority index: " + priorityIndex);
         pw.println("mNetwork: " + network);
         pw.println("mNetworkCapabilities: " + networkCapabilities);
         pw.println("mLinkProperties: " + linkProperties);