diff options
| author | 2024-01-11 19:40:47 +0000 | |
|---|---|---|
| committer | 2024-01-11 19:40:47 +0000 | |
| commit | eaf32e574e88dcb5c5c552bf7746f05659e98f5c (patch) | |
| tree | 67baf58ce87de1e25247a0c5f65a8bd4a71eb086 | |
| parent | f13f82700d08f3f1c870778d3295b0adf0b79a1c (diff) | |
| parent | f4853c52e27d90a6ede3f200223d5829c8c612b4 (diff) | |
Merge "Support IPsec packet loss detector" into main am: f4853c52e2
Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/2873077
Change-Id: Ia31bbf665aa7a9e1d8443e5e3c77c6fd60782499
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
9 files changed, 1141 insertions, 6 deletions
| diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java index c727a6034006..561db9c8a8ce 100644 --- a/core/java/android/net/vcn/VcnManager.java +++ b/core/java/android/net/vcn/VcnManager.java @@ -102,6 +102,24 @@ public class VcnManager {      public static final String VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY =              "vcn_network_selection_wifi_exit_rssi_threshold"; +    /** +     * Key for the interval to poll IpSecTransformState for packet loss monitoring +     * +     * @hide +     */ +    @NonNull +    public static final String VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY = +            "vcn_network_selection_poll_ipsec_state_interval_seconds"; + +    /** +     * Key for the threshold of IPSec packet loss rate +     * +     * @hide +     */ +    @NonNull +    public static final String VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY = +            "vcn_network_selection_ipsec_packet_loss_percent_threshold"; +      // TODO: Add separate signal strength thresholds for 2.4 GHz and 5GHz      /** @@ -148,6 +166,8 @@ public class VcnManager {              new String[] {                  VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY,                  VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY, +                VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY, +                VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY,                  VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY,                  VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY,                  VCN_TUNNEL_AGGREGATION_SA_COUNT_MAX_KEY, diff --git a/core/java/android/net/vcn/flags.aconfig b/core/java/android/net/vcn/flags.aconfig index 67a1906d48ed..7afd72195fcb 100644 --- a/core/java/android/net/vcn/flags.aconfig +++ b/core/java/android/net/vcn/flags.aconfig @@ -12,4 +12,11 @@ flag {      namespace: "vcn"      description: "Feature flag for adjustable safe mode timeout"      bug: "317406085" +} + +flag{ +    name: "network_metric_monitor" +    namespace: "vcn" +    description: "Feature flag for enabling network metric monitor" +    bug: "282996138"  }
\ No newline at end of file diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java index 6ce868540070..ed04e5fde024 100644 --- a/services/core/java/com/android/server/vcn/VcnContext.java +++ b/services/core/java/com/android/server/vcn/VcnContext.java @@ -34,6 +34,7 @@ public class VcnContext {      @NonNull private final Looper mLooper;      @NonNull private final VcnNetworkProvider mVcnNetworkProvider;      @NonNull private final FeatureFlags mFeatureFlags; +    @NonNull private final com.android.net.flags.FeatureFlags mCoreNetFeatureFlags;      private final boolean mIsInTestMode;      public VcnContext( @@ -48,6 +49,7 @@ public class VcnContext {          // Auto-generated class          mFeatureFlags = new FeatureFlagsImpl(); +        mCoreNetFeatureFlags = new com.android.net.flags.FeatureFlagsImpl();      }      @NonNull @@ -69,6 +71,14 @@ public class VcnContext {          return mIsInTestMode;      } +    public boolean isFlagNetworkMetricMonitorEnabled() { +        return mFeatureFlags.networkMetricMonitor(); +    } + +    public boolean isFlagIpSecTransformStateEnabled() { +        return mCoreNetFeatureFlags.ipsecTransformState(); +    } +      @NonNull      public FeatureFlags getFeatureFlags() {          return mFeatureFlags; diff --git a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java new file mode 100644 index 000000000000..5f4852f77727 --- /dev/null +++ b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java @@ -0,0 +1,387 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.vcn.routeselection; + +import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.IpSecTransformState; +import android.net.Network; +import android.net.vcn.VcnManager; +import android.os.Handler; +import android.os.HandlerExecutor; +import android.os.OutcomeReceiver; +import android.os.PowerManager; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.annotations.VisibleForTesting.Visibility; +import com.android.server.vcn.VcnContext; + +import java.util.BitSet; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +/** + * IpSecPacketLossDetector is responsible for continuously monitoring IPsec packet loss + * + * <p>When the packet loss rate surpass the threshold, IpSecPacketLossDetector will report it to the + * caller + * + * <p>IpSecPacketLossDetector will start monitoring when the network being monitored is selected AND + * an inbound IpSecTransform has been applied to this network. + * + * <p>This class is flag gated by "network_metric_monitor" and "ipsec_tramsform_state" + */ +public class IpSecPacketLossDetector extends NetworkMetricMonitor { +    private static final String TAG = IpSecPacketLossDetector.class.getSimpleName(); + +    @VisibleForTesting(visibility = Visibility.PRIVATE) +    static final int PACKET_LOSS_UNAVALAIBLE = -1; + +    // For VoIP, losses between 5% and 10% of the total packet stream will affect the quality +    // significantly (as per "Computer Networking for LANS to WANS: Hardware, Software and +    // Security"). For audio and video streaming, above 10-12% packet loss is unacceptable (as per +    // "ICTP-SDU: About PingER"). Thus choose 12% as a conservative default threshold to declare a +    // validation failure. +    private static final int IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DEFAULT = 12; + +    private static final int POLL_IPSEC_STATE_INTERVAL_SECONDS_DEFAULT = 20; + +    private long mPollIpSecStateIntervalMs; +    private final int mPacketLossRatePercentThreshold; + +    @NonNull private final Handler mHandler; +    @NonNull private final PowerManager mPowerManager; +    @NonNull private final Object mCancellationToken = new Object(); +    @NonNull private final PacketLossCalculator mPacketLossCalculator; + +    @Nullable private IpSecTransformWrapper mInboundTransform; +    @Nullable private IpSecTransformState mLastIpSecTransformState; + +    @VisibleForTesting(visibility = Visibility.PRIVATE) +    public IpSecPacketLossDetector( +            @NonNull VcnContext vcnContext, +            @NonNull Network network, +            @Nullable PersistableBundleWrapper carrierConfig, +            @NonNull NetworkMetricMonitorCallback callback, +            @NonNull Dependencies deps) +            throws IllegalAccessException { +        super(vcnContext, network, carrierConfig, callback); + +        Objects.requireNonNull(deps, "Missing deps"); + +        if (!vcnContext.isFlagIpSecTransformStateEnabled()) { +            // Caller error +            logWtf("ipsecTransformState flag disabled"); +            throw new IllegalAccessException("ipsecTransformState flag disabled"); +        } + +        mHandler = new Handler(getVcnContext().getLooper()); + +        mPowerManager = getVcnContext().getContext().getSystemService(PowerManager.class); + +        mPacketLossCalculator = deps.getPacketLossCalculator(); + +        mPollIpSecStateIntervalMs = getPollIpSecStateIntervalMs(carrierConfig); +        mPacketLossRatePercentThreshold = getPacketLossRatePercentThreshold(carrierConfig); + +        // Register for system broadcasts to monitor idle mode change +        final IntentFilter intentFilter = new IntentFilter(); +        intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); +        getVcnContext() +                .getContext() +                .registerReceiver( +                        new BroadcastReceiver() { +                            @Override +                            public void onReceive(Context context, Intent intent) { +                                if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals( +                                                intent.getAction()) +                                        && mPowerManager.isDeviceIdleMode()) { +                                    mLastIpSecTransformState = null; +                                } +                            } +                        }, +                        intentFilter, +                        null /* broadcastPermission not required */, +                        mHandler); +    } + +    public IpSecPacketLossDetector( +            @NonNull VcnContext vcnContext, +            @NonNull Network network, +            @Nullable PersistableBundleWrapper carrierConfig, +            @NonNull NetworkMetricMonitorCallback callback) +            throws IllegalAccessException { +        this(vcnContext, network, carrierConfig, callback, new Dependencies()); +    } + +    @VisibleForTesting(visibility = Visibility.PRIVATE) +    public static class Dependencies { +        public PacketLossCalculator getPacketLossCalculator() { +            return new PacketLossCalculator(); +        } +    } + +    private static long getPollIpSecStateIntervalMs( +            @Nullable PersistableBundleWrapper carrierConfig) { +        final int seconds; + +        if (carrierConfig != null) { +            seconds = +                    carrierConfig.getInt( +                            VcnManager.VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY, +                            POLL_IPSEC_STATE_INTERVAL_SECONDS_DEFAULT); +        } else { +            seconds = POLL_IPSEC_STATE_INTERVAL_SECONDS_DEFAULT; +        } + +        return TimeUnit.SECONDS.toMillis(seconds); +    } + +    private static int getPacketLossRatePercentThreshold( +            @Nullable PersistableBundleWrapper carrierConfig) { +        if (carrierConfig != null) { +            return carrierConfig.getInt( +                    VcnManager.VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY, +                    IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DEFAULT); +        } +        return IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DEFAULT; +    } + +    @Override +    protected void onSelectedUnderlyingNetworkChanged() { +        if (!isSelectedUnderlyingNetwork()) { +            mInboundTransform = null; +            stop(); +        } + +        // No action when the underlying network got selected. Wait for the inbound transform to +        // start the monitor +    } + +    @Override +    public void setInboundTransformInternal(@NonNull IpSecTransformWrapper inboundTransform) { +        Objects.requireNonNull(inboundTransform, "inboundTransform is null"); + +        if (Objects.equals(inboundTransform, mInboundTransform)) { +            return; +        } + +        if (!isSelectedUnderlyingNetwork()) { +            logWtf("setInboundTransform called but network not selected"); +            return; +        } + +        // When multiple parallel inbound transforms are created, NetworkMetricMonitor will be +        // enabled on the last one as a sample +        mInboundTransform = inboundTransform; +        start(); +    } + +    @Override +    public void setCarrierConfig(@Nullable PersistableBundleWrapper carrierConfig) { +        // The already scheduled event will not be affected. The followup events will be scheduled +        // with the new interval +        mPollIpSecStateIntervalMs = getPollIpSecStateIntervalMs(carrierConfig); +    } + +    @Override +    protected void start() { +        super.start(); +        clearTransformStateAndPollingEvents(); +        mHandler.postDelayed(new PollIpSecStateRunnable(), mCancellationToken, 0L); +    } + +    @Override +    public void stop() { +        super.stop(); +        clearTransformStateAndPollingEvents(); +    } + +    private void clearTransformStateAndPollingEvents() { +        mHandler.removeCallbacksAndEqualMessages(mCancellationToken); +        mLastIpSecTransformState = null; +    } + +    @Override +    public void close() { +        super.close(); + +        if (mInboundTransform != null) { +            mInboundTransform.close(); +        } +    } + +    @VisibleForTesting(visibility = Visibility.PRIVATE) +    @Nullable +    public IpSecTransformState getLastTransformState() { +        return mLastIpSecTransformState; +    } + +    @VisibleForTesting(visibility = Visibility.PROTECTED) +    @Nullable +    public IpSecTransformWrapper getInboundTransformInternal() { +        return mInboundTransform; +    } + +    private class PollIpSecStateRunnable implements Runnable { +        @Override +        public void run() { +            if (!isStarted()) { +                logWtf("Monitor stopped but PollIpSecStateRunnable not removed from Handler"); +                return; +            } + +            getInboundTransformInternal() +                    .getIpSecTransformState( +                            new HandlerExecutor(mHandler), new IpSecTransformStateReceiver()); + +            // Schedule for next poll +            mHandler.postDelayed( +                    new PollIpSecStateRunnable(), mCancellationToken, mPollIpSecStateIntervalMs); +        } +    } + +    private class IpSecTransformStateReceiver +            implements OutcomeReceiver<IpSecTransformState, RuntimeException> { +        @Override +        public void onResult(@NonNull IpSecTransformState state) { +            getVcnContext().ensureRunningOnLooperThread(); + +            if (!isStarted()) { +                return; +            } + +            onIpSecTransformStateReceived(state); +        } + +        @Override +        public void onError(@NonNull RuntimeException error) { +            getVcnContext().ensureRunningOnLooperThread(); + +            // Nothing we can do here +            logW("TransformStateReceiver#onError " + error.toString()); +        } +    } + +    private void onIpSecTransformStateReceived(@NonNull IpSecTransformState state) { +        if (mLastIpSecTransformState == null) { +            // This is first time to poll the state +            mLastIpSecTransformState = state; +            return; +        } + +        final int packetLossRate = +                mPacketLossCalculator.getPacketLossRatePercentage( +                        mLastIpSecTransformState, state, getLogPrefix()); + +        if (packetLossRate == PACKET_LOSS_UNAVALAIBLE) { +            return; +        } + +        final String logMsg = +                "packetLossRate: " +                        + packetLossRate +                        + "% in the past " +                        + (state.getTimestamp() - mLastIpSecTransformState.getTimestamp()) +                        + "ms"; + +        mLastIpSecTransformState = state; +        if (packetLossRate < mPacketLossRatePercentThreshold) { +            logV(logMsg); +            onValidationResultReceivedInternal(false /* isFailed */); +        } else { +            logInfo(logMsg); +            onValidationResultReceivedInternal(true /* isFailed */); +        } +    } + +    @VisibleForTesting(visibility = Visibility.PRIVATE) +    public static class PacketLossCalculator { +        /** Calculate the packet loss rate between two timestamps */ +        public int getPacketLossRatePercentage( +                @NonNull IpSecTransformState oldState, +                @NonNull IpSecTransformState newState, +                String logPrefix) { +            logVIpSecTransform("oldState", oldState, logPrefix); +            logVIpSecTransform("newState", newState, logPrefix); + +            final int replayWindowSize = oldState.getReplayBitmap().length * 8; +            final long oldSeqHi = oldState.getRxHighestSequenceNumber(); +            final long oldSeqLow = Math.max(0L, oldSeqHi - replayWindowSize + 1); +            final long newSeqHi = newState.getRxHighestSequenceNumber(); +            final long newSeqLow = Math.max(0L, newSeqHi - replayWindowSize + 1); + +            if (oldSeqHi == newSeqHi || newSeqHi < replayWindowSize) { +                // The replay window did not proceed and all packets might have been delivered out +                // of order +                return PACKET_LOSS_UNAVALAIBLE; +            } + +            // Get the expected packet count by assuming there is no packet loss. In this case, SA +            // should receive all packets whose sequence numbers are smaller than the lower bound of +            // the replay window AND the packets received within the window. +            // When the lower bound is 0, it's not possible to tell whether packet with seqNo 0 is +            // received or not. For simplicity just assume that packet is received. +            final long newExpectedPktCnt = newSeqLow + getPacketCntInReplayWindow(newState); +            final long oldExpectedPktCnt = oldSeqLow + getPacketCntInReplayWindow(oldState); + +            final long expectedPktCntDiff = newExpectedPktCnt - oldExpectedPktCnt; +            final long actualPktCntDiff = newState.getPacketCount() - oldState.getPacketCount(); + +            logV( +                    TAG, +                    logPrefix +                            + " expectedPktCntDiff: " +                            + expectedPktCntDiff +                            + " actualPktCntDiff: " +                            + actualPktCntDiff); + +            if (expectedPktCntDiff < 0 +                    || expectedPktCntDiff == 0 +                    || actualPktCntDiff < 0 +                    || actualPktCntDiff > expectedPktCntDiff) { +                logWtf(TAG, "Impossible values for expectedPktCntDiff or" + " actualPktCntDiff"); +                return PACKET_LOSS_UNAVALAIBLE; +            } + +            return 100 - (int) (actualPktCntDiff * 100 / expectedPktCntDiff); +        } +    } + +    private static void logVIpSecTransform( +            String transformTag, IpSecTransformState state, String logPrefix) { +        final String stateString = +                " seqNo: " +                        + state.getRxHighestSequenceNumber() +                        + " | pktCnt: " +                        + state.getPacketCount() +                        + " | pktCntInWindow: " +                        + getPacketCntInReplayWindow(state); +        logV(TAG, logPrefix + " " + transformTag + stateString); +    } + +    /** Get the number of received packets within the replay window */ +    private static long getPacketCntInReplayWindow(@NonNull IpSecTransformState state) { +        return BitSet.valueOf(state.getReplayBitmap()).cardinality(); +    } +} diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java b/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java new file mode 100644 index 000000000000..a79f188713e1 --- /dev/null +++ b/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.vcn.routeselection; + +import static com.android.server.VcnManagementService.LOCAL_LOG; +import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.IpSecTransform; +import android.net.IpSecTransformState; +import android.net.Network; +import android.os.OutcomeReceiver; +import android.util.CloseGuard; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.annotations.VisibleForTesting.Visibility; +import com.android.server.vcn.VcnContext; + +import java.util.Objects; +import java.util.concurrent.Executor; + +/** + * NetworkMetricMonitor is responsible for managing metric monitoring and tracking validation + * results. + * + * <p>This class is flag gated by "network_metric_monitor" + */ +public abstract class NetworkMetricMonitor implements AutoCloseable { +    private static final String TAG = NetworkMetricMonitor.class.getSimpleName(); + +    private static final boolean VDBG = false; // STOPSHIP: if true + +    @NonNull private final CloseGuard mCloseGuard = new CloseGuard(); + +    @NonNull private final VcnContext mVcnContext; +    @NonNull private final Network mNetwork; +    @NonNull private final NetworkMetricMonitorCallback mCallback; + +    private boolean mIsSelectedUnderlyingNetwork; +    private boolean mIsStarted; +    private boolean mIsValidationFailed; + +    protected NetworkMetricMonitor( +            @NonNull VcnContext vcnContext, +            @NonNull Network network, +            @Nullable PersistableBundleWrapper carrierConfig, +            @NonNull NetworkMetricMonitorCallback callback) +            throws IllegalAccessException { +        if (!vcnContext.isFlagNetworkMetricMonitorEnabled()) { +            // Caller error +            logWtf("networkMetricMonitor flag disabled"); +            throw new IllegalAccessException("networkMetricMonitor flag disabled"); +        } + +        mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext"); +        mNetwork = Objects.requireNonNull(network, "Missing network"); +        mCallback = Objects.requireNonNull(callback, "Missing callback"); + +        mIsSelectedUnderlyingNetwork = false; +        mIsStarted = false; +        mIsValidationFailed = false; +    } + +    /** Callback to notify caller of the validation result */ +    public interface NetworkMetricMonitorCallback { +        /** Called when there is a validation result is ready */ +        void onValidationResultReceived(); +    } + +    /** +     * Start monitoring +     * +     * <p>This method might be called on a an already started monitor for updating monitor +     * properties (e.g. IpSecTransform, carrier config) +     * +     * <p>Subclasses MUST call super.start() when overriding this method +     */ +    protected void start() { +        mIsStarted = true; +    } + +    /** +     * Stop monitoring +     * +     * <p>Subclasses MUST call super.stop() when overriding this method +     */ +    public void stop() { +        mIsValidationFailed = false; +        mIsStarted = false; +    } + +    /** Called by the subclasses when the validation result is ready */ +    protected void onValidationResultReceivedInternal(boolean isFailed) { +        mIsValidationFailed = isFailed; +        mCallback.onValidationResultReceived(); +    } + +    /** Called when the underlying network changes to selected or unselected */ +    protected abstract void onSelectedUnderlyingNetworkChanged(); + +    /** +     * Mark the network being monitored selected or unselected +     * +     * <p>Subclasses MUST call super when overriding this method +     */ +    public void setIsSelectedUnderlyingNetwork(boolean isSelectedUnderlyingNetwork) { +        if (mIsSelectedUnderlyingNetwork == isSelectedUnderlyingNetwork) { +            return; +        } + +        mIsSelectedUnderlyingNetwork = isSelectedUnderlyingNetwork; +        onSelectedUnderlyingNetworkChanged(); +    } + +    /** Wrapper that allows injection for testing purposes */ +    @VisibleForTesting(visibility = Visibility.PROTECTED) +    public static class IpSecTransformWrapper { +        @NonNull public final IpSecTransform ipSecTransform; + +        public IpSecTransformWrapper(@NonNull IpSecTransform ipSecTransform) { +            this.ipSecTransform = ipSecTransform; +        } + +        /** Poll an IpSecTransformState */ +        public void getIpSecTransformState( +                @NonNull Executor executor, +                @NonNull OutcomeReceiver<IpSecTransformState, RuntimeException> callback) { +            ipSecTransform.getIpSecTransformState(executor, callback); +        } + +        /** Close this instance and release the underlying resources */ +        public void close() { +            ipSecTransform.close(); +        } + +        @Override +        public int hashCode() { +            return Objects.hash(ipSecTransform); +        } + +        @Override +        public boolean equals(Object o) { +            if (!(o instanceof IpSecTransformWrapper)) { +                return false; +            } + +            final IpSecTransformWrapper other = (IpSecTransformWrapper) o; + +            return Objects.equals(ipSecTransform, other.ipSecTransform); +        } +    } + +    /** Set the IpSecTransform that applied to the Network being monitored */ +    public void setInboundTransform(@NonNull IpSecTransform inTransform) { +        setInboundTransformInternal(new IpSecTransformWrapper(inTransform)); +    } + +    /** +     * Set the IpSecTransform that applied to the Network being monitored * +     * +     * <p>Subclasses MUST call super when overriding this method +     */ +    @VisibleForTesting(visibility = Visibility.PRIVATE) +    public void setInboundTransformInternal(@NonNull IpSecTransformWrapper inTransform) { +        // Subclasses MUST override it if they care +    } + +    /** Update the carrierconfig */ +    public void setCarrierConfig(@Nullable PersistableBundleWrapper carrierConfig) { +        // Subclasses MUST override it if they care +    } + +    public boolean isValidationFailed() { +        return mIsValidationFailed; +    } + +    public boolean isSelectedUnderlyingNetwork() { +        return mIsSelectedUnderlyingNetwork; +    } + +    public boolean isStarted() { +        return mIsStarted; +    } + +    @NonNull +    public VcnContext getVcnContext() { +        return mVcnContext; +    } + +    // Override methods for AutoCloseable. Subclasses MUST call super when overriding this method +    @Override +    public void close() { +        mCloseGuard.close(); + +        stop(); +    } + +    // Override #finalize() to use closeGuard for flagging that #close() was not called +    @SuppressWarnings("Finalize") +    @Override +    protected void finalize() throws Throwable { +        try { +            if (mCloseGuard != null) { +                mCloseGuard.warnIfOpen(); +            } +            close(); +        } finally { +            super.finalize(); +        } +    } + +    private String getClassName() { +        return this.getClass().getSimpleName(); +    } + +    protected String getLogPrefix() { +        return " [Network " + mNetwork + "] "; +    } + +    protected void logV(String msg) { +        if (VDBG) { +            Slog.v(getClassName(), getLogPrefix() + msg); +            LOCAL_LOG.log("[VERBOSE ] " + getClassName() + getLogPrefix() + msg); +        } +    } + +    protected void logInfo(String msg) { +        Slog.i(getClassName(), getLogPrefix() + msg); +        LOCAL_LOG.log("[INFO ] " + getClassName() + getLogPrefix() + msg); +    } + +    protected void logW(String msg) { +        Slog.w(getClassName(), getLogPrefix() + msg); +        LOCAL_LOG.log("[WARN ] " + getClassName() + getLogPrefix() + msg); +    } + +    protected void logWtf(String msg) { +        Slog.wtf(getClassName(), getLogPrefix() + msg); +        LOCAL_LOG.log("[WTF ] " + getClassName() + getLogPrefix() + msg); +    } + +    protected static void logV(String className, String msgWithPrefix) { +        if (VDBG) { +            Slog.wtf(className, msgWithPrefix); +            LOCAL_LOG.log("[VERBOSE ] " + className + msgWithPrefix); +        } +    } + +    protected static void logWtf(String className, String msgWithPrefix) { +        Slog.wtf(className, msgWithPrefix); +        LOCAL_LOG.log("[WTF ] " + className + msgWithPrefix); +    } +} diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java new file mode 100644 index 000000000000..9daba6a79a27 --- /dev/null +++ b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java @@ -0,0 +1,419 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.vcn.routeselection; + +import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY; +import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY; + +import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.PACKET_LOSS_UNAVALAIBLE; +import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.BroadcastReceiver; +import android.content.Intent; +import android.net.IpSecTransformState; +import android.os.OutcomeReceiver; +import android.os.PowerManager; + +import com.android.server.vcn.routeselection.IpSecPacketLossDetector.PacketLossCalculator; +import com.android.server.vcn.routeselection.NetworkMetricMonitor.IpSecTransformWrapper; +import com.android.server.vcn.routeselection.NetworkMetricMonitor.NetworkMetricMonitorCallback; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.Spy; + +import java.util.Arrays; +import java.util.BitSet; +import java.util.concurrent.TimeUnit; + +public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase { +    private static final String TAG = IpSecPacketLossDetectorTest.class.getSimpleName(); + +    private static final int REPLAY_BITMAP_LEN_BYTE = 512; +    private static final int REPLAY_BITMAP_LEN_BIT = REPLAY_BITMAP_LEN_BYTE * 8; +    private static final int IPSEC_PACKET_LOSS_PERCENT_THRESHOLD = 5; +    private static final long POLL_IPSEC_STATE_INTERVAL_MS = TimeUnit.SECONDS.toMillis(30L); + +    @Mock private IpSecTransformWrapper mIpSecTransform; +    @Mock private NetworkMetricMonitorCallback mMetricMonitorCallback; +    @Mock private PersistableBundleWrapper mCarrierConfig; +    @Mock private IpSecPacketLossDetector.Dependencies mDependencies; +    @Spy private PacketLossCalculator mPacketLossCalculator = new PacketLossCalculator(); + +    @Captor private ArgumentCaptor<OutcomeReceiver> mTransformStateReceiverCaptor; +    @Captor private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor; + +    private IpSecPacketLossDetector mIpSecPacketLossDetector; +    private IpSecTransformState mTransformStateInitial; + +    @Before +    public void setUp() throws Exception { +        super.setUp(); +        mTransformStateInitial = newTransformState(0, 0, newReplayBitmap(0)); + +        when(mCarrierConfig.getInt( +                        eq(VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY), anyInt())) +                .thenReturn((int) TimeUnit.MILLISECONDS.toSeconds(POLL_IPSEC_STATE_INTERVAL_MS)); +        when(mCarrierConfig.getInt( +                        eq(VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY), +                        anyInt())) +                .thenReturn(IPSEC_PACKET_LOSS_PERCENT_THRESHOLD); + +        when(mDependencies.getPacketLossCalculator()).thenReturn(mPacketLossCalculator); + +        mIpSecPacketLossDetector = +                new IpSecPacketLossDetector( +                        mVcnContext, +                        mNetwork, +                        mCarrierConfig, +                        mMetricMonitorCallback, +                        mDependencies); +    } + +    private static IpSecTransformState newTransformState( +            long rxSeqNo, long packtCount, byte[] replayBitmap) { +        return new IpSecTransformState.Builder() +                .setRxHighestSequenceNumber(rxSeqNo) +                .setPacketCount(packtCount) +                .setReplayBitmap(replayBitmap) +                .build(); +    } + +    private static byte[] newReplayBitmap(int receivedPktCnt) { +        final BitSet bitSet = new BitSet(REPLAY_BITMAP_LEN_BIT); +        for (int i = 0; i < receivedPktCnt; i++) { +            bitSet.set(i); +        } +        return Arrays.copyOf(bitSet.toByteArray(), REPLAY_BITMAP_LEN_BYTE); +    } + +    private void verifyStopped() { +        assertFalse(mIpSecPacketLossDetector.isStarted()); +        assertFalse(mIpSecPacketLossDetector.isValidationFailed()); +        assertNull(mIpSecPacketLossDetector.getLastTransformState()); + +        // No event scheduled +        mTestLooper.moveTimeForward(POLL_IPSEC_STATE_INTERVAL_MS); +        assertNull(mTestLooper.nextMessage()); +    } + +    @Test +    public void testInitialization() throws Exception { +        assertFalse(mIpSecPacketLossDetector.isSelectedUnderlyingNetwork()); +        verifyStopped(); +    } + +    private OutcomeReceiver<IpSecTransformState, RuntimeException> +            startMonitorAndCaptureStateReceiver() { +        mIpSecPacketLossDetector.setIsSelectedUnderlyingNetwork(true /* setIsSelected */); +        mIpSecPacketLossDetector.setInboundTransformInternal(mIpSecTransform); + +        // Trigger the runnable +        mTestLooper.dispatchAll(); + +        verify(mIpSecTransform) +                .getIpSecTransformState(any(), mTransformStateReceiverCaptor.capture()); +        return mTransformStateReceiverCaptor.getValue(); +    } + +    @Test +    public void testStartMonitor() throws Exception { +        final OutcomeReceiver<IpSecTransformState, RuntimeException> xfrmStateReceiver = +                startMonitorAndCaptureStateReceiver(); + +        assertTrue(mIpSecPacketLossDetector.isStarted()); +        assertFalse(mIpSecPacketLossDetector.isValidationFailed()); +        assertTrue(mIpSecPacketLossDetector.isSelectedUnderlyingNetwork()); +        assertEquals(mIpSecTransform, mIpSecPacketLossDetector.getInboundTransformInternal()); + +        // Mock receiving a state +        xfrmStateReceiver.onResult(mTransformStateInitial); + +        // Verify the first polled state is stored +        assertEquals(mTransformStateInitial, mIpSecPacketLossDetector.getLastTransformState()); +        verify(mPacketLossCalculator, never()) +                .getPacketLossRatePercentage(any(), any(), anyString()); + +        // Verify next poll is scheduled +        assertNull(mTestLooper.nextMessage()); +        mTestLooper.moveTimeForward(POLL_IPSEC_STATE_INTERVAL_MS); +        assertNotNull(mTestLooper.nextMessage()); +    } + +    @Test +    public void testStartedMonitor_enterDozeMoze() throws Exception { +        final OutcomeReceiver<IpSecTransformState, RuntimeException> xfrmStateReceiver = +                startMonitorAndCaptureStateReceiver(); + +        // Mock receiving a state +        xfrmStateReceiver.onResult(mTransformStateInitial); +        assertEquals(mTransformStateInitial, mIpSecPacketLossDetector.getLastTransformState()); + +        // Mock entering doze mode +        final Intent intent = mock(Intent.class); +        when(intent.getAction()).thenReturn(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); +        when(mPowerManagerService.isDeviceIdleMode()).thenReturn(true); + +        verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(), any(), any(), any()); +        final BroadcastReceiver broadcastReceiver = mBroadcastReceiverCaptor.getValue(); +        broadcastReceiver.onReceive(mContext, intent); + +        assertNull(mIpSecPacketLossDetector.getLastTransformState()); +    } + +    @Test +    public void testStartedMonitor_updateInboundTransform() throws Exception { +        final OutcomeReceiver<IpSecTransformState, RuntimeException> xfrmStateReceiver = +                startMonitorAndCaptureStateReceiver(); + +        // Mock receiving a state +        xfrmStateReceiver.onResult(mTransformStateInitial); +        assertEquals(mTransformStateInitial, mIpSecPacketLossDetector.getLastTransformState()); + +        // Update the inbound transform +        final IpSecTransformWrapper newTransform = mock(IpSecTransformWrapper.class); +        mIpSecPacketLossDetector.setInboundTransformInternal(newTransform); + +        // Verifications +        assertNull(mIpSecPacketLossDetector.getLastTransformState()); +        mTestLooper.moveTimeForward(POLL_IPSEC_STATE_INTERVAL_MS); +        mTestLooper.dispatchAll(); +        verify(newTransform).getIpSecTransformState(any(), any()); +    } + +    @Test +    public void testStartedMonitor_updateCarrierConfig() throws Exception { +        startMonitorAndCaptureStateReceiver(); + +        final int additionalPollIntervalMs = (int) TimeUnit.SECONDS.toMillis(10L); +        when(mCarrierConfig.getInt( +                        eq(VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY), anyInt())) +                .thenReturn( +                        (int) +                                TimeUnit.MILLISECONDS.toSeconds( +                                        POLL_IPSEC_STATE_INTERVAL_MS + additionalPollIntervalMs)); +        mIpSecPacketLossDetector.setCarrierConfig(mCarrierConfig); +        mTestLooper.dispatchAll(); + +        // The already scheduled event is still fired with the old timeout +        mTestLooper.moveTimeForward(POLL_IPSEC_STATE_INTERVAL_MS); +        mTestLooper.dispatchAll(); + +        // The next scheduled event will take 10 more seconds to fire +        mTestLooper.moveTimeForward(POLL_IPSEC_STATE_INTERVAL_MS); +        assertNull(mTestLooper.nextMessage()); +        mTestLooper.moveTimeForward(additionalPollIntervalMs); +        assertNotNull(mTestLooper.nextMessage()); +    } + +    @Test +    public void testStopMonitor() throws Exception { +        mIpSecPacketLossDetector.setIsSelectedUnderlyingNetwork(true /* setIsSelected */); +        mIpSecPacketLossDetector.setInboundTransformInternal(mIpSecTransform); + +        assertTrue(mIpSecPacketLossDetector.isStarted()); +        assertNotNull(mTestLooper.nextMessage()); + +        // Unselect the monitor +        mIpSecPacketLossDetector.setIsSelectedUnderlyingNetwork(false /* setIsSelected */); +        verifyStopped(); +    } + +    @Test +    public void testClose() throws Exception { +        mIpSecPacketLossDetector.setIsSelectedUnderlyingNetwork(true /* setIsSelected */); +        mIpSecPacketLossDetector.setInboundTransformInternal(mIpSecTransform); + +        assertTrue(mIpSecPacketLossDetector.isStarted()); +        assertNotNull(mTestLooper.nextMessage()); + +        // Stop the monitor +        mIpSecPacketLossDetector.close(); +        verifyStopped(); +        verify(mIpSecTransform).close(); +    } + +    @Test +    public void testTransformStateReceiverOnResultWhenStopped() throws Exception { +        final OutcomeReceiver<IpSecTransformState, RuntimeException> xfrmStateReceiver = +                startMonitorAndCaptureStateReceiver(); +        xfrmStateReceiver.onResult(mTransformStateInitial); + +        // Unselect the monitor +        mIpSecPacketLossDetector.setIsSelectedUnderlyingNetwork(false /* setIsSelected */); +        verifyStopped(); + +        xfrmStateReceiver.onResult(newTransformState(1, 1, newReplayBitmap(1))); +        verify(mPacketLossCalculator, never()) +                .getPacketLossRatePercentage(any(), any(), anyString()); +    } + +    @Test +    public void testTransformStateReceiverOnError() throws Exception { +        final OutcomeReceiver<IpSecTransformState, RuntimeException> xfrmStateReceiver = +                startMonitorAndCaptureStateReceiver(); +        xfrmStateReceiver.onResult(mTransformStateInitial); + +        xfrmStateReceiver.onError(new RuntimeException("Test")); +        verify(mPacketLossCalculator, never()) +                .getPacketLossRatePercentage(any(), any(), anyString()); +    } + +    private void checkHandleLossRate( +            int mockPacketLossRate, boolean isLastStateExpectedToUpdate, boolean isCallbackExpected) +            throws Exception { +        final OutcomeReceiver<IpSecTransformState, RuntimeException> xfrmStateReceiver = +                startMonitorAndCaptureStateReceiver(); +        doReturn(mockPacketLossRate) +                .when(mPacketLossCalculator) +                .getPacketLossRatePercentage(any(), any(), anyString()); + +        // Mock receiving two states with mTransformStateInitial and an arbitrary transformNew +        final IpSecTransformState transformNew = newTransformState(1, 1, newReplayBitmap(1)); +        xfrmStateReceiver.onResult(mTransformStateInitial); +        xfrmStateReceiver.onResult(transformNew); + +        // Verifications +        verify(mPacketLossCalculator) +                .getPacketLossRatePercentage( +                        eq(mTransformStateInitial), eq(transformNew), anyString()); + +        if (isLastStateExpectedToUpdate) { +            assertEquals(transformNew, mIpSecPacketLossDetector.getLastTransformState()); +        } else { +            assertEquals(mTransformStateInitial, mIpSecPacketLossDetector.getLastTransformState()); +        } + +        if (isCallbackExpected) { +            verify(mMetricMonitorCallback).onValidationResultReceived(); +        } else { +            verify(mMetricMonitorCallback, never()).onValidationResultReceived(); +        } +    } + +    @Test +    public void testHandleLossRate_validationPass() throws Exception { +        checkHandleLossRate( +                2, true /* isLastStateExpectedToUpdate */, true /* isCallbackExpected */); +    } + +    @Test +    public void testHandleLossRate_validationFail() throws Exception { +        checkHandleLossRate( +                22, true /* isLastStateExpectedToUpdate */, true /* isCallbackExpected */); +    } + +    @Test +    public void testHandleLossRate_resultUnavalaible() throws Exception { +        checkHandleLossRate( +                PACKET_LOSS_UNAVALAIBLE, +                false /* isLastStateExpectedToUpdate */, +                false /* isCallbackExpected */); +    } + +    private void checkGetPacketLossRate( +            IpSecTransformState oldState, IpSecTransformState newState, int expectedLossRate) +            throws Exception { +        assertEquals( +                expectedLossRate, +                mPacketLossCalculator.getPacketLossRatePercentage(oldState, newState, TAG)); +    } + +    private void checkGetPacketLossRate( +            IpSecTransformState oldState, +            int rxSeqNo, +            int packetCount, +            int packetInWin, +            int expectedDataLossRate) +            throws Exception { +        final IpSecTransformState newState = +                newTransformState(rxSeqNo, packetCount, newReplayBitmap(packetInWin)); +        checkGetPacketLossRate(oldState, newState, expectedDataLossRate); +    } + +    @Test +    public void testGetPacketLossRate_replayWindowUnchanged() throws Exception { +        checkGetPacketLossRate( +                mTransformStateInitial, mTransformStateInitial, PACKET_LOSS_UNAVALAIBLE); +        checkGetPacketLossRate(mTransformStateInitial, 3000, 2000, 2000, PACKET_LOSS_UNAVALAIBLE); +    } + +    @Test +    public void testGetPacketLossRate_againstInitialState() throws Exception { +        checkGetPacketLossRate(mTransformStateInitial, 7000, 7001, 4096, 0); +        checkGetPacketLossRate(mTransformStateInitial, 7000, 6000, 4096, 15); +        checkGetPacketLossRate(mTransformStateInitial, 7000, 6000, 4000, 14); +    } + +    @Test +    public void testGetPktLossRate_oldHiSeqSmallerThanWinSize_overlappedWithNewWin() +            throws Exception { +        final IpSecTransformState oldState = newTransformState(2000, 1500, newReplayBitmap(1500)); + +        checkGetPacketLossRate(oldState, 5000, 5001, 4096, 0); +        checkGetPacketLossRate(oldState, 5000, 4000, 4096, 29); +        checkGetPacketLossRate(oldState, 5000, 4000, 4000, 27); +    } + +    @Test +    public void testGetPktLossRate_oldHiSeqSmallerThanWinSize_notOverlappedWithNewWin() +            throws Exception { +        final IpSecTransformState oldState = newTransformState(2000, 1500, newReplayBitmap(1500)); + +        checkGetPacketLossRate(oldState, 7000, 7001, 4096, 0); +        checkGetPacketLossRate(oldState, 7000, 5000, 4096, 37); +        checkGetPacketLossRate(oldState, 7000, 5000, 3000, 21); +    } + +    @Test +    public void testGetPktLossRate_oldHiSeqLargerThanWinSize_overlappedWithNewWin() +            throws Exception { +        final IpSecTransformState oldState = newTransformState(10000, 5000, newReplayBitmap(3000)); + +        checkGetPacketLossRate(oldState, 12000, 8096, 4096, 0); +        checkGetPacketLossRate(oldState, 12000, 7000, 4096, 36); +        checkGetPacketLossRate(oldState, 12000, 7000, 3000, 0); +    } + +    @Test +    public void testGetPktLossRate_oldHiSeqLargerThanWinSize_notOverlappedWithNewWin() +            throws Exception { +        final IpSecTransformState oldState = newTransformState(10000, 5000, newReplayBitmap(3000)); + +        checkGetPacketLossRate(oldState, 20000, 16096, 4096, 0); +        checkGetPacketLossRate(oldState, 20000, 14000, 4096, 19); +        checkGetPacketLossRate(oldState, 20000, 14000, 3000, 10); +    } +} diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java index bf84bbeeedad..355c22156a78 100644 --- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java +++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java @@ -20,6 +20,7 @@ import static com.android.server.vcn.VcnTestUtils.setupSystemService;  import static com.android.server.vcn.routeselection.UnderlyingNetworkControllerTest.getLinkPropertiesWithName;  import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn;  import static org.mockito.Mockito.mock;  import static org.mockito.Mockito.spy;  import static org.mockito.Mockito.when; @@ -29,7 +30,12 @@ import android.net.LinkProperties;  import android.net.Network;  import android.net.NetworkCapabilities;  import android.net.TelephonyNetworkSpecifier; +import android.net.vcn.FeatureFlags; +import android.os.Handler; +import android.os.IPowerManager; +import android.os.IThermalService;  import android.os.ParcelUuid; +import android.os.PowerManager;  import android.os.test.TestLooper;  import android.telephony.TelephonyManager; @@ -90,32 +96,49 @@ public abstract class NetworkEvaluationTestBase {      protected static final LinkProperties LINK_PROPERTIES = getLinkPropertiesWithName("test_iface"); +    @Mock protected Context mContext;      @Mock protected Network mNetwork; +    @Mock protected FeatureFlags mFeatureFlags; +    @Mock protected com.android.net.flags.FeatureFlags mCoreNetFeatureFlags;      @Mock protected TelephonySubscriptionSnapshot mSubscriptionSnapshot;      @Mock protected TelephonyManager mTelephonyManager; +    @Mock protected IPowerManager mPowerManagerService;      protected TestLooper mTestLooper;      protected VcnContext mVcnContext; +    protected PowerManager mPowerManager;      @Before -    public void setUp() { +    public void setUp() throws Exception {          MockitoAnnotations.initMocks(this); -        final Context mockContext = mock(Context.class); +        when(mNetwork.getNetId()).thenReturn(-1); +          mTestLooper = new TestLooper();          mVcnContext =                  spy(                          new VcnContext( -                                mockContext, +                                mContext,                                  mTestLooper.getLooper(),                                  mock(VcnNetworkProvider.class),                                  false /* isInTestMode */));          doNothing().when(mVcnContext).ensureRunningOnLooperThread(); +        doReturn(true).when(mVcnContext).isFlagNetworkMetricMonitorEnabled(); +        doReturn(true).when(mVcnContext).isFlagIpSecTransformStateEnabled(); +          setupSystemService( -                mockContext, mTelephonyManager, Context.TELEPHONY_SERVICE, TelephonyManager.class); +                mContext, mTelephonyManager, Context.TELEPHONY_SERVICE, TelephonyManager.class);          when(mTelephonyManager.createForSubscriptionId(SUB_ID)).thenReturn(mTelephonyManager);          when(mTelephonyManager.getNetworkOperator()).thenReturn(PLMN_ID);          when(mTelephonyManager.getSimSpecificCarrierId()).thenReturn(CARRIER_ID); + +        mPowerManager = +                new PowerManager( +                        mContext, +                        mPowerManagerService, +                        mock(IThermalService.class), +                        mock(Handler.class)); +        setupSystemService(mContext, mPowerManager, Context.POWER_SERVICE, PowerManager.class);      }  } diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java index dbf2f514fe89..d85c5150f53f 100644 --- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java +++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java @@ -57,7 +57,7 @@ public class NetworkPriorityClassifierTest extends NetworkEvaluationTestBase {      private UnderlyingNetworkRecord mCellNetworkRecord;      @Before -    public void setUp() { +    public void setUp() throws Exception {          super.setUp();          mWifiNetworkRecord = getTestNetworkRecord(WIFI_NETWORK_CAPABILITIES); diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java index a4567ddc20a1..985e70c9771e 100644 --- a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java +++ b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java @@ -34,7 +34,7 @@ public class UnderlyingNetworkEvaluatorTest extends NetworkEvaluationTestBase {      private PersistableBundleWrapper mCarrierConfig;      @Before -    public void setUp() { +    public void setUp() throws Exception {          super.setUp();          mCarrierConfig = new PersistableBundleWrapper(new PersistableBundle());      } |