Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 80056e83 authored by Benedict Wong's avatar Benedict Wong
Browse files

Allow soft-start and opportunistic safe mode

This change adds support for opportunistic safe mode, where the VCN will
continue to provide networks, but not restrict underlying networks.
Similarly, this change allows for soft-starting of the VCN, where VCN
underlying networks can be selected without restricting them directly.

Additionally, this change ensures networks are torn down when
VcnGatewayConnections enter safe mode. This change is required due to
changes in the lifecycle of the VcnGatwayConnection, where in safe mode
they are NOT torn down, but allowed to continue retrying. During these
broken-connectivity windows, the VCN network should be torn down to
prevent blackholing traffic.

Bug: 183174340
Test: atest FrameworksVcnTests
Change-Id: I50f2c0e92552281731c843db89e9a9a1ccff5346
parent f8db8d8c
Loading
Loading
Loading
Loading
+7 −13
Original line number Diff line number Diff line
@@ -542,16 +542,7 @@ public class VcnManagementService extends IVcnManagementService.Stub {

        if (mVcns.containsKey(subscriptionGroup)) {
            final Vcn vcn = mVcns.get(subscriptionGroup);
            final int status = vcn.getStatus();
            vcn.updateConfig(config);

            // TODO(b/183174340): Remove this once opportunistic-safe-mode is supported
            // Only notify VcnStatusCallbacks if this VCN was previously in Safe Mode
            if (status == VCN_STATUS_CODE_SAFE_MODE) {
                // TODO(b/181789060): invoke asynchronously after Vcn notifies through VcnCallback
                notifyAllPermissionedStatusCallbacksLocked(
                        subscriptionGroup, VCN_STATUS_CODE_ACTIVE);
            }
        } else {
            startVcnLocked(subscriptionGroup, config);
        }
@@ -941,8 +932,8 @@ public class VcnManagementService extends IVcnManagementService.Stub {
    // TODO(b/180452282): Make name more generic and implement directly with VcnManagementService
    /** Callback for Vcn signals sent up to VcnManagementService. */
    public interface VcnCallback {
        /** Called by a Vcn to signal that it has entered safe mode. */
        void onEnteredSafeMode();
        /** Called by a Vcn to signal that its safe mode status has changed. */
        void onSafeModeStatusChanged(boolean isInSafeMode);

        /** Called by a Vcn to signal that an error occurred. */
        void onGatewayConnectionError(
@@ -1004,15 +995,18 @@ public class VcnManagementService extends IVcnManagementService.Stub {
        }

        @Override
        public void onEnteredSafeMode() {
        public void onSafeModeStatusChanged(boolean isInSafeMode) {
            synchronized (mLock) {
                // Ignore if this subscription group doesn't exist anymore
                if (!mVcns.containsKey(mSubGroup)) {
                    return;
                }

                final int status =
                        isInSafeMode ? VCN_STATUS_CODE_SAFE_MODE : VCN_STATUS_CODE_ACTIVE;

                notifyAllPolicyListenersLocked();
                notifyAllPermissionedStatusCallbacksLocked(mSubGroup, VCN_STATUS_CODE_SAFE_MODE);
                notifyAllPermissionedStatusCallbacksLocked(mSubGroup, status);
            }
        }

+63 −63
Original line number Diff line number Diff line
@@ -96,16 +96,20 @@ public class Vcn extends Handler {
     */
    private static final int MSG_EVENT_GATEWAY_CONNECTION_QUIT = MSG_EVENT_BASE + 3;

    /** Triggers an immediate teardown of the entire Vcn, including GatewayConnections. */
    private static final int MSG_CMD_TEARDOWN = MSG_CMD_BASE;

    /**
     * Causes this VCN to immediately enter safe mode.
     * Triggers reevaluation of safe mode conditions.
     *
     * <p>Upon entering safe mode, the VCN will only provide gateway connections opportunistically,
     * leaving the underlying networks marked as NOT_VCN_MANAGED.
     *
     * <p>Upon entering safe mode, the VCN will unregister its RequestListener, tear down all of its
     * VcnGatewayConnections, and notify VcnManagementService that it is in safe mode.
     * <p>Any VcnGatewayConnection in safe mode will result in the entire Vcn instance being put
     * into safe mode. Upon receiving this message, the Vcn MUST query all VcnGatewayConnections to
     * determine if any are in safe mode.
     */
    private static final int MSG_CMD_ENTER_SAFE_MODE = MSG_CMD_BASE + 1;
    private static final int MSG_EVENT_SAFE_MODE_STATE_CHANGED = MSG_EVENT_BASE + 4;

    /** Triggers an immediate teardown of the entire Vcn, including GatewayConnections. */
    private static final int MSG_CMD_TEARDOWN = MSG_CMD_BASE;

    @NonNull private final VcnContext mVcnContext;
    @NonNull private final ParcelUuid mSubscriptionGroup;
@@ -233,6 +237,11 @@ public class Vcn extends Handler {

    @Override
    public void handleMessage(@NonNull Message msg) {
        if (mCurrentStatus != VCN_STATUS_CODE_ACTIVE
                && mCurrentStatus != VCN_STATUS_CODE_SAFE_MODE) {
            return;
        }

        switch (msg.what) {
            case MSG_EVENT_CONFIG_UPDATED:
                handleConfigUpdated((VcnConfig) msg.obj);
@@ -246,12 +255,12 @@ public class Vcn extends Handler {
            case MSG_EVENT_GATEWAY_CONNECTION_QUIT:
                handleGatewayConnectionQuit((VcnGatewayConnectionConfig) msg.obj);
                break;
            case MSG_EVENT_SAFE_MODE_STATE_CHANGED:
                handleSafeModeStatusChanged();
                break;
            case MSG_CMD_TEARDOWN:
                handleTeardown();
                break;
            case MSG_CMD_ENTER_SAFE_MODE:
                handleEnterSafeMode();
                break;
            default:
                Slog.wtf(getLogTag(), "Unknown msg.what: " + msg.what);
        }
@@ -263,10 +272,8 @@ public class Vcn extends Handler {

        mConfig = config;

        // TODO(b/183174340): Remove this once opportunistic safe mode is supported.
        if (mCurrentStatus == VCN_STATUS_CODE_ACTIVE) {
            // VCN is already active - teardown any GatewayConnections whose configs have been
            // removed and get all current requests
        // Teardown any GatewayConnections whose configs have been removed and get all current
        // requests
        for (final Entry<VcnGatewayConnectionConfig, VcnGatewayConnection> entry :
                mVcnGatewayConnections.entrySet()) {
            final VcnGatewayConnectionConfig gatewayConnectionConfig = entry.getKey();
@@ -277,8 +284,7 @@ public class Vcn extends Handler {
            if (!mConfig.getGatewayConnectionConfigs().contains(gatewayConnectionConfig)) {
                if (gatewayConnection == null) {
                    Slog.wtf(
                                getLogTag(),
                                "Found gatewayConnectionConfig without GatewayConnection");
                            getLogTag(), "Found gatewayConnectionConfig without GatewayConnection");
                } else {
                    gatewayConnection.teardownAsynchronously();
                }
@@ -288,15 +294,6 @@ public class Vcn extends Handler {
        // Trigger a re-evaluation of all NetworkRequests (to make sure any that can be
        // satisfied start a new GatewayConnection)
        mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener);
        } else if (mCurrentStatus == VCN_STATUS_CODE_SAFE_MODE) {
            // If this VCN was not previously active, it is exiting Safe Mode. Re-register the
            // request listener to get NetworkRequests again (and all cached requests).
            mVcnContext.getVcnNetworkProvider().registerListener(mRequestListener);
        } else {
            // Ignored; VCN was not active; config updates ignored.
            return;
        }
        mCurrentStatus = VCN_STATUS_CODE_ACTIVE;
    }

    private void handleTeardown() {
@@ -309,21 +306,27 @@ public class Vcn extends Handler {
        mCurrentStatus = VCN_STATUS_CODE_INACTIVE;
    }

    private void handleEnterSafeMode() {
        // TODO(b/183174340): Remove this once opportunistic-safe-mode is supported
        handleTeardown();
    private void handleSafeModeStatusChanged() {
        boolean hasSafeModeGatewayConnection = false;

        mCurrentStatus = VCN_STATUS_CODE_SAFE_MODE;
        mVcnCallback.onEnteredSafeMode();
        // If any VcnGatewayConnection is in safe mode, mark the entire VCN as being in safe mode
        for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) {
            if (gatewayConnection.isInSafeMode()) {
                hasSafeModeGatewayConnection = true;
                break;
            }
        }

    private void handleNetworkRequested(
            @NonNull NetworkRequest request, int score, int providerId) {
        if (mCurrentStatus != VCN_STATUS_CODE_ACTIVE) {
            Slog.v(getLogTag(), "Received NetworkRequest while inactive. Ignore for now");
            return;
        final int oldStatus = mCurrentStatus;
        mCurrentStatus =
                hasSafeModeGatewayConnection ? VCN_STATUS_CODE_SAFE_MODE : VCN_STATUS_CODE_ACTIVE;
        if (oldStatus != mCurrentStatus) {
            mVcnCallback.onSafeModeStatusChanged(hasSafeModeGatewayConnection);
        }
    }

    private void handleNetworkRequested(
            @NonNull NetworkRequest request, int score, int providerId) {
        if (score > getNetworkScore()) {
            if (VDBG) {
                Slog.v(
@@ -376,21 +379,18 @@ public class Vcn extends Handler {
        mVcnGatewayConnections.remove(config);

        // Trigger a re-evaluation of all NetworkRequests (to make sure any that can be satisfied
        // start a new GatewayConnection), but only if the Vcn is still alive
        if (mCurrentStatus == VCN_STATUS_CODE_ACTIVE) {
        // start a new GatewayConnection). VCN is always alive here, courtesy of the liveness check
        // in handleMessage()
        mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener);
    }
    }

    private void handleSubscriptionsChanged(@NonNull TelephonySubscriptionSnapshot snapshot) {
        mLastSnapshot = snapshot;

        if (mCurrentStatus == VCN_STATUS_CODE_ACTIVE) {
        for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) {
            gatewayConnection.updateSubscriptionSnapshot(mLastSnapshot);
        }
    }
    }

    private boolean isRequestSatisfiedByGatewayConnectionConfig(
            @NonNull NetworkRequest request, @NonNull VcnGatewayConnectionConfig config) {
@@ -418,8 +418,8 @@ public class Vcn extends Handler {
    /** Callback used for passing status signals from a VcnGatewayConnection to its managing Vcn. */
    @VisibleForTesting(visibility = Visibility.PACKAGE)
    public interface VcnGatewayStatusCallback {
        /** Called by a VcnGatewayConnection to indicate that it has entered safe mode. */
        void onEnteredSafeMode();
        /** Called by a VcnGatewayConnection to indicate that it's safe mode status has changed. */
        void onSafeModeStatusChanged();

        /** Callback by a VcnGatewayConnection to indicate that an error occurred. */
        void onGatewayConnectionError(
@@ -445,8 +445,8 @@ public class Vcn extends Handler {
        }

        @Override
        public void onEnteredSafeMode() {
            sendMessage(obtainMessage(MSG_CMD_ENTER_SAFE_MODE));
        public void onSafeModeStatusChanged() {
            sendMessage(obtainMessage(MSG_EVENT_SAFE_MODE_STATE_CHANGED));
        }

        @Override
+92 −27
Original line number Diff line number Diff line
@@ -44,6 +44,7 @@ import android.net.Network;
import android.net.NetworkAgent;
import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkProvider;
import android.net.RouteInfo;
import android.net.TelephonyNetworkSpecifier;
import android.net.Uri;
@@ -92,6 +93,7 @@ import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

/**
 * A single VCN Gateway Connection, providing a single public-facing VCN network.
@@ -503,6 +505,15 @@ public class VcnGatewayConnection extends StateMachine {
     */
    private boolean mIsQuitting = false;

    /**
     * Whether the VcnGatewayConnection is in safe mode.
     *
     * <p>Upon hitting the safe mode timeout, this will be set to {@code true}. In safe mode, this
     * VcnGatewayConnection will continue attempting to connect, and if a successful connection is
     * made, safe mode will be exited.
     */
    private boolean mIsInSafeMode = false;

    /**
     * The token used by the primary/current/active session.
     *
@@ -562,8 +573,7 @@ public class VcnGatewayConnection extends StateMachine {
     * <p>Set in Connected state, always @NonNull in Connected, Migrating states, @Nullable
     * otherwise.
     */
    @VisibleForTesting(visibility = Visibility.PRIVATE)
    NetworkAgent mNetworkAgent;
    private NetworkAgent mNetworkAgent;

    @Nullable private WakeupMessage mTeardownTimeoutAlarm;
    @Nullable private WakeupMessage mDisconnectRequestAlarm;
@@ -628,6 +638,14 @@ public class VcnGatewayConnection extends StateMachine {
        start();
    }

    /** Queries whether this VcnGatewayConnection is in safe mode. */
    public boolean isInSafeMode() {
        // Accessing internal state; must only be done on looper thread.
        mVcnContext.ensureRunningOnLooperThread();

        return mIsInSafeMode;
    }

    /**
     * Asynchronously tears down this GatewayConnection, and any resources used.
     *
@@ -1162,6 +1180,15 @@ public class VcnGatewayConnection extends StateMachine {
            }
        }

        protected void handleSafeModeTimeoutExceeded() {
            mSafeModeTimeoutAlarm = null;

            // Connectivity for this GatewayConnection is broken; tear down the Network.
            teardownNetwork();
            mIsInSafeMode = true;
            mGatewayStatusCallback.onSafeModeStatusChanged();
        }

        protected void logUnexpectedEvent(int what) {
            Slog.d(TAG, String.format(
                    "Unexpected event code %d in state %s", what, this.getClass().getSimpleName()));
@@ -1315,8 +1342,7 @@ public class VcnGatewayConnection extends StateMachine {
                    }
                    break;
                case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
                    mGatewayStatusCallback.onEnteredSafeMode();
                    mSafeModeTimeoutAlarm = null;
                    handleSafeModeTimeoutExceeded();
                    break;
                default:
                    logUnhandledMessage(msg);
@@ -1401,8 +1427,7 @@ public class VcnGatewayConnection extends StateMachine {
                    handleDisconnectRequested((EventDisconnectRequestedInfo) msg.obj);
                    break;
                case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
                    mGatewayStatusCallback.onEnteredSafeMode();
                    mSafeModeTimeoutAlarm = null;
                    handleSafeModeTimeoutExceeded();
                    break;
                default:
                    logUnhandledMessage(msg);
@@ -1434,28 +1459,23 @@ public class VcnGatewayConnection extends StateMachine {
                    buildConnectedLinkProperties(mConnectionConfig, tunnelIface, childConfig);

            final NetworkAgent agent =
                    new NetworkAgent(
                            mVcnContext.getContext(),
                            mVcnContext.getLooper(),
                    mDeps.newNetworkAgent(
                            mVcnContext,
                            TAG,
                            caps,
                            lp,
                            Vcn.getNetworkScore(),
                            new NetworkAgentConfig.Builder().build(),
                            mVcnContext.getVcnNetworkProvider()) {
                        @Override
                        public void onNetworkUnwanted() {
                            mVcnContext.getVcnNetworkProvider(),
                            () -> {
                                Slog.d(TAG, "NetworkAgent was unwanted");
                                teardownAsynchronously();
                        }

                        @Override
                        public void onValidationStatus(int status, @Nullable Uri redirectUri) {
                            } /* networkUnwantedCallback */,
                            (status) -> {
                                if (status == NetworkAgent.VALIDATION_STATUS_VALID) {
                                    clearFailedAttemptCounterAndSafeModeAlarm();
                                }
                        }
                    };
                            } /* validationStatusCallback */);

            agent.register();
            agent.markConnected();
@@ -1469,6 +1489,11 @@ public class VcnGatewayConnection extends StateMachine {
            // Validated connection, clear failed attempt counter
            mFailedAttempts = 0;
            cancelSafeModeAlarm();

            if (mIsInSafeMode) {
                mIsInSafeMode = false;
                mGatewayStatusCallback.onSafeModeStatusChanged();
            }
        }

        protected void applyTransform(
@@ -1587,8 +1612,7 @@ public class VcnGatewayConnection extends StateMachine {
                    handleDisconnectRequested((EventDisconnectRequestedInfo) msg.obj);
                    break;
                case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
                    mGatewayStatusCallback.onEnteredSafeMode();
                    mSafeModeTimeoutAlarm = null;
                    handleSafeModeTimeoutExceeded();
                    break;
                default:
                    logUnhandledMessage(msg);
@@ -1692,8 +1716,7 @@ public class VcnGatewayConnection extends StateMachine {
                    handleDisconnectRequested((EventDisconnectRequestedInfo) msg.obj);
                    break;
                case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
                    mGatewayStatusCallback.onEnteredSafeMode();
                    mSafeModeTimeoutAlarm = null;
                    handleSafeModeTimeoutExceeded();
                    break;
                default:
                    logUnhandledMessage(msg);
@@ -1934,6 +1957,16 @@ public class VcnGatewayConnection extends StateMachine {
        mIkeSession = session;
    }

    @VisibleForTesting(visibility = Visibility.PRIVATE)
    NetworkAgent getNetworkAgent() {
        return mNetworkAgent;
    }

    @VisibleForTesting(visibility = Visibility.PRIVATE)
    void setNetworkAgent(@Nullable NetworkAgent networkAgent) {
        mNetworkAgent = networkAgent;
    }

    @VisibleForTesting(visibility = Visibility.PRIVATE)
    void sendDisconnectRequestedAndAcquireWakelock(String reason, boolean shouldQuit) {
        sendMessageAndAcquireWakeLock(
@@ -2018,6 +2051,38 @@ public class VcnGatewayConnection extends StateMachine {
            return new WakeupMessage(vcnContext.getContext(), handler, tag, runnable);
        }

        /** Builds a new NetworkAgent. */
        public NetworkAgent newNetworkAgent(
                @NonNull VcnContext vcnContext,
                @NonNull String tag,
                @NonNull NetworkCapabilities caps,
                @NonNull LinkProperties lp,
                @NonNull int score,
                @NonNull NetworkAgentConfig nac,
                @NonNull NetworkProvider provider,
                @NonNull Runnable networkUnwantedCallback,
                @NonNull Consumer<Integer> validationStatusCallback) {
            return new NetworkAgent(
                    vcnContext.getContext(),
                    vcnContext.getLooper(),
                    tag,
                    caps,
                    lp,
                    score,
                    nac,
                    provider) {
                @Override
                public void onNetworkUnwanted() {
                    networkUnwantedCallback.run();
                }

                @Override
                public void onValidationStatus(int status, @Nullable Uri redirectUri) {
                    validationStatusCallback.accept(status);
                }
            };
        }

        /** Gets the elapsed real time since boot, in millis. */
        public long getElapsedRealTime() {
            return SystemClock.elapsedRealtime();
+28 −24
Original line number Diff line number Diff line
@@ -536,17 +536,6 @@ public class VcnManagementServiceTest {
        verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_ACTIVE);
    }

    @Test
    public void testSetVcnConfigInSafeModeNotifiesStatusCallback() throws Exception {
        setupSubscriptionAndStartVcn(TEST_SUBSCRIPTION_ID, TEST_UUID_2, false /* isActive */);
        mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_2, mMockStatusCallback, TEST_PACKAGE_NAME);
        verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_SAFE_MODE);

        mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);

        verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_ACTIVE);
    }

    @Test
    public void testClearVcnConfigRequiresNonSystemServer() throws Exception {
        doReturn(Process.SYSTEM_UID).when(mMockDeps).getBinderCallingUid();
@@ -902,7 +891,9 @@ public class VcnManagementServiceTest {
    }

    private void triggerVcnSafeMode(
            @NonNull ParcelUuid subGroup, @NonNull TelephonySubscriptionSnapshot snapshot)
            @NonNull ParcelUuid subGroup,
            @NonNull TelephonySubscriptionSnapshot snapshot,
            boolean isInSafeMode)
            throws Exception {
        verify(mMockDeps)
                .newVcn(
@@ -913,22 +904,32 @@ public class VcnManagementServiceTest {
                        mVcnCallbackCaptor.capture());

        VcnCallback vcnCallback = mVcnCallbackCaptor.getValue();
        vcnCallback.onEnteredSafeMode();
        vcnCallback.onSafeModeStatusChanged(isInSafeMode);
    }

    @Test
    public void testVcnEnteringSafeModeNotifiesPolicyListeners() throws Exception {
    private void verifyVcnSafeModeChangesNotifiesPolicyListeners(boolean enterSafeMode)
            throws Exception {
        TelephonySubscriptionSnapshot snapshot =
                triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1));

        mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);

        triggerVcnSafeMode(TEST_UUID_1, snapshot);
        triggerVcnSafeMode(TEST_UUID_1, snapshot, enterSafeMode);

        verify(mMockPolicyListener).onPolicyChanged();
    }

    private void triggerVcnStatusCallbackOnEnteredSafeMode(
    @Test
    public void testVcnEnteringSafeModeNotifiesPolicyListeners() throws Exception {
        verifyVcnSafeModeChangesNotifiesPolicyListeners(true /* enterSafeMode */);
    }

    @Test
    public void testVcnExitingSafeModeNotifiesPolicyListeners() throws Exception {
        verifyVcnSafeModeChangesNotifiesPolicyListeners(false /* enterSafeMode */);
    }

    private void triggerVcnStatusCallbackOnSafeModeStatusChanged(
            @NonNull ParcelUuid subGroup,
            @NonNull String pkgName,
            int uid,
@@ -951,12 +952,13 @@ public class VcnManagementServiceTest {

        mVcnMgmtSvc.registerVcnStatusCallback(subGroup, mMockStatusCallback, pkgName);

        triggerVcnSafeMode(subGroup, snapshot);
        triggerVcnSafeMode(subGroup, snapshot, true /* enterSafeMode */);
    }

    @Test
    public void testVcnStatusCallbackOnEnteredSafeModeWithCarrierPrivileges() throws Exception {
        triggerVcnStatusCallbackOnEnteredSafeMode(
    public void testVcnStatusCallbackOnSafeModeStatusChangedWithCarrierPrivileges()
            throws Exception {
        triggerVcnStatusCallbackOnSafeModeStatusChanged(
                TEST_UUID_1,
                TEST_PACKAGE_NAME,
                TEST_UID,
@@ -967,8 +969,9 @@ public class VcnManagementServiceTest {
    }

    @Test
    public void testVcnStatusCallbackOnEnteredSafeModeWithoutCarrierPrivileges() throws Exception {
        triggerVcnStatusCallbackOnEnteredSafeMode(
    public void testVcnStatusCallbackOnSafeModeStatusChangedWithoutCarrierPrivileges()
            throws Exception {
        triggerVcnStatusCallbackOnSafeModeStatusChanged(
                TEST_UUID_1,
                TEST_PACKAGE_NAME,
                TEST_UID,
@@ -980,8 +983,9 @@ public class VcnManagementServiceTest {
    }

    @Test
    public void testVcnStatusCallbackOnEnteredSafeModeWithoutLocationPermission() throws Exception {
        triggerVcnStatusCallbackOnEnteredSafeMode(
    public void testVcnStatusCallbackOnSafeModeStatusChangedWithoutLocationPermission()
            throws Exception {
        triggerVcnStatusCallbackOnSafeModeStatusChanged(
                TEST_UUID_1,
                TEST_PACKAGE_NAME,
                TEST_UID,
+59 −13

File changed.

Preview size limit exceeded, changes collapsed.

Loading