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

Commit ddb720a8 authored by Etan Cohen's avatar Etan Cohen
Browse files

[CS] Support "instant failure" from factories

Add a mechanism by which a factory can declare "instant failure" for
a request - which would result in it getting an OnUnavailable()
(even without a timeout).

Factories may only do this iff:
1. They know they are the only factory which may fulfill this
   request (common for transport-specific requests).
2. The know that the request can definitely not be
   fulfilled at any point in the future.

Bug: 31382922
Test: atest ConnectivityServiceTest
Merged-In: I9bce0f4d85fa8cad7f8a9998819f945b778c5ac5
Change-Id: I9bce0f4d85fa8cad7f8a9998819f945b778c5ac5
parent e17575c0
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -3276,9 +3276,9 @@ public class ConnectivityManager {

        /**
         * Called if no network is found in the timeout time specified in
         * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)} call. This callback is not
         * called for the version of {@link #requestNetwork(NetworkRequest, NetworkCallback)}
         * without timeout. When this callback is invoked the associated
         * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)} call or if the
         * requested network request cannot be fulfilled (whether or not a timeout was
         * specified). When this callback is invoked the associated
         * {@link NetworkRequest} will have already been removed and released, as if
         * {@link #unregisterNetworkCallback(NetworkCallback)} had been called.
         */
+62 −0
Original line number Diff line number Diff line
@@ -27,11 +27,13 @@ import android.util.Log;
import android.util.SparseArray;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.AsyncChannel;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Protocol;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;

/**
 * A NetworkFactory is an entity that creates NetworkAgent objects.
@@ -96,7 +98,16 @@ public class NetworkFactory extends Handler {
     */
    private static final int CMD_SET_FILTER = BASE + 3;

    /**
     * Sent by NetworkFactory to ConnectivityService to indicate that a request is
     * unfulfillable.
     * @see #releaseRequestAsUnfulfillableByAnyFactory(NetworkRequest).
     */
    public static final int EVENT_UNFULFILLABLE_REQUEST = BASE + 4;

    private final Context mContext;
    private final ArrayList<Message> mPreConnectedQueue = new ArrayList<Message>();
    private AsyncChannel mAsyncChannel;
    private final String LOG_TAG;

    private final SparseArray<NetworkRequestInfo> mNetworkRequests =
@@ -136,6 +147,36 @@ public class NetworkFactory extends Handler {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
                if (mAsyncChannel != null) {
                    log("Received new connection while already connected!");
                    break;
                }
                if (VDBG) log("NetworkFactory fully connected");
                AsyncChannel ac = new AsyncChannel();
                ac.connected(null, this, msg.replyTo);
                ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
                        AsyncChannel.STATUS_SUCCESSFUL);
                mAsyncChannel = ac;
                for (Message m : mPreConnectedQueue) {
                    ac.sendMessage(m);
                }
                mPreConnectedQueue.clear();
                break;
            }
            case AsyncChannel.CMD_CHANNEL_DISCONNECT: {
                if (VDBG) log("CMD_CHANNEL_DISCONNECT");
                if (mAsyncChannel != null) {
                    mAsyncChannel.disconnect();
                    mAsyncChannel = null;
                }
                break;
            }
            case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
                if (DBG) log("NetworkFactory channel lost");
                mAsyncChannel = null;
                break;
            }
            case CMD_REQUEST_NETWORK: {
                handleAddRequest((NetworkRequest)msg.obj, msg.arg1);
                break;
@@ -267,6 +308,27 @@ public class NetworkFactory extends Handler {
        });
    }

    /**
     * Can be called by a factory to release a request as unfulfillable: the request will be
     * removed, and the caller will get a
     * {@link ConnectivityManager.NetworkCallback#onUnavailable()} callback after this function
     * returns.
     *
     * Note: this should only be called by factory which KNOWS that it is the ONLY factory which
     * is able to fulfill this request!
     */
    protected void releaseRequestAsUnfulfillableByAnyFactory(NetworkRequest r) {
        post(() -> {
            if (DBG) log("releaseRequestAsUnfulfillableByAnyFactory: " + r);
            Message msg = obtainMessage(EVENT_UNFULFILLABLE_REQUEST, r);
            if (mAsyncChannel != null) {
                mAsyncChannel.sendMessage(msg);
            } else {
                mPreConnectedQueue.add(msg);
            }
        });
    }

    // override to do simple mode (request independent)
    protected void startNetwork() { }
    protected void stopNetwork() { }
+32 −8
Original line number Diff line number Diff line
@@ -1062,7 +1062,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
            handleRegisterNetworkRequest(new NetworkRequestInfo(
                    null, networkRequest, new Binder()));
        } else {
            handleReleaseNetworkRequest(networkRequest, Process.SYSTEM_UID);
            handleReleaseNetworkRequest(networkRequest, Process.SYSTEM_UID,
                    /* callOnUnavailable */ false);
        }
    }

@@ -2653,11 +2654,25 @@ public class ConnectivityService extends IConnectivityManager.Stub
            return true;
        }

        private boolean maybeHandleNetworkFactoryMessage(Message msg) {
            switch (msg.what) {
                default:
                    return false;
                case android.net.NetworkFactory.EVENT_UNFULFILLABLE_REQUEST: {
                    handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.sendingUid,
                            /* callOnUnavailable */ true);
                    break;
                }
            }
            return true;
        }

        @Override
        public void handleMessage(Message msg) {
            if (!maybeHandleAsyncChannelMessage(msg) &&
                    !maybeHandleNetworkMonitorMessage(msg) &&
                    !maybeHandleNetworkAgentInfoMessage(msg)) {
            if (!maybeHandleAsyncChannelMessage(msg)
                    && !maybeHandleNetworkMonitorMessage(msg)
                    && !maybeHandleNetworkAgentInfoMessage(msg)
                    && !maybeHandleNetworkFactoryMessage(msg)) {
                maybeHandleNetworkAgentMessage(msg);
            }
        }
@@ -2822,6 +2837,9 @@ public class ConnectivityService extends IConnectivityManager.Stub
        if (mNetworkFactoryInfos.containsKey(msg.replyTo)) {
            if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
                if (VDBG) log("NetworkFactory connected");
                // Finish setting up the full connection
                mNetworkFactoryInfos.get(msg.replyTo).asyncChannel.sendMessage(
                        AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
                // A network factory has connected.  Send it all current NetworkRequests.
                for (NetworkRequestInfo nri : mNetworkRequests.values()) {
                    if (nri.request.isListen()) continue;
@@ -2983,7 +3001,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
        if (existingRequest != null) { // remove the existing request.
            if (DBG) log("Replacing " + existingRequest.request + " with "
                    + nri.request + " because their intents matched.");
            handleReleaseNetworkRequest(existingRequest.request, getCallingUid());
            handleReleaseNetworkRequest(existingRequest.request, getCallingUid(),
                    /* callOnUnavailable */ false);
        }
        handleRegisterNetworkRequest(nri);
    }
@@ -3009,7 +3028,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
            int callingUid) {
        NetworkRequestInfo nri = findExistingNetworkRequestInfo(pendingIntent);
        if (nri != null) {
            handleReleaseNetworkRequest(nri.request, callingUid);
            handleReleaseNetworkRequest(nri.request, callingUid, /* callOnUnavailable */ false);
        }
    }

@@ -3092,7 +3111,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
        callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_UNAVAIL, 0);
    }

    private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid) {
    private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid,
            boolean callOnUnavailable) {
        final NetworkRequestInfo nri =
                getNriForAppRequest(request, callingUid, "release NetworkRequest");
        if (nri == null) {
@@ -3102,6 +3122,9 @@ public class ConnectivityService extends IConnectivityManager.Stub
            log("releasing " + nri.request + " (release request)");
        }
        handleRemoveNetworkRequest(nri);
        if (callOnUnavailable) {
            callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_UNAVAIL, 0);
        }
    }

    private void handleRemoveNetworkRequest(final NetworkRequestInfo nri) {
@@ -3533,7 +3556,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
                    break;
                }
                case EVENT_RELEASE_NETWORK_REQUEST: {
                    handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.arg1);
                    handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.arg1,
                            /* callOnUnavailable */ false);
                    break;
                }
                case EVENT_SET_ACCEPT_UNVALIDATED: {
+64 −1
Original line number Diff line number Diff line
@@ -152,6 +152,7 @@ import android.test.mock.MockContentResolver;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;

import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -748,6 +749,10 @@ public class ConnectivityServiceTest {
        // mExpectations is non-null.
        private boolean mExpectingAdditions;

        // Used to collect the networks requests managed by this factory. This is a duplicate of
        // the internal information stored in the NetworkFactory (which is private).
        private SparseArray<NetworkRequest> mNetworkRequests = new SparseArray<>();

        public MockNetworkFactory(Looper looper, Context context, String logTag,
                NetworkCapabilities filter) {
            super(looper, context, logTag, filter);
@@ -789,6 +794,7 @@ public class ConnectivityServiceTest {
            }

            // Add the request.
            mNetworkRequests.put(request.requestId, request);
            super.handleAddRequest(request, score);

            // Reduce the number of request additions we're waiting for.
@@ -806,6 +812,7 @@ public class ConnectivityServiceTest {
            }

            // Remove the request.
            mNetworkRequests.remove(request.requestId);
            super.handleRemoveRequest(request);

            // Reduce the number of request removals we're waiting for.
@@ -815,6 +822,11 @@ public class ConnectivityServiceTest {
            }
        }

        // Trigger releasing the request as unfulfillable
        public void triggerUnfulfillable(NetworkRequest r) {
            super.releaseRequestAsUnfulfillableByAnyFactory(r);
        }

        private void assertNoExpectations() {
            if (mExpectations != null) {
                fail("Can't add expectation, " + mExpectations.getCount() + " already pending");
@@ -847,9 +859,11 @@ public class ConnectivityServiceTest {
            mExpectations = null;
        }

        public void waitForNetworkRequests(final int count) throws InterruptedException {
        public SparseArray<NetworkRequest> waitForNetworkRequests(final int count)
                throws InterruptedException {
            waitForRequests();
            assertEquals(count, getMyRequestCount());
            return mNetworkRequests;
        }
    }

@@ -3523,6 +3537,55 @@ public class ConnectivityServiceTest {
        networkCallback.assertNoCallback();
    }

    /**
     * Validate the callback flow for a factory releasing a request as unfulfillable.
     */
    @Test
    public void testUnfulfillableNetworkRequest() throws Exception {
        NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
                NetworkCapabilities.TRANSPORT_WIFI).build();
        final TestNetworkCallback networkCallback = new TestNetworkCallback();

        final HandlerThread handlerThread = new HandlerThread("testUnfulfillableNetworkRequest");
        handlerThread.start();
        NetworkCapabilities filter = new NetworkCapabilities()
                .addTransportType(TRANSPORT_WIFI)
                .addCapability(NET_CAPABILITY_INTERNET);
        final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
                mServiceContext, "testFactory", filter);
        testFactory.setScoreFilter(40);

        // Register the factory and expect it to receive the default request.
        testFactory.expectAddRequests(1);
        testFactory.register();
        SparseArray<NetworkRequest> requests = testFactory.waitForNetworkRequests(1);

        assertEquals(1, requests.size()); // have 1 request at this point
        int origRequestId = requests.valueAt(0).requestId;

        // Now file the test request and expect it.
        testFactory.expectAddRequests(1);
        mCm.requestNetwork(nr, networkCallback);
        requests = testFactory.waitForNetworkRequests(2); // have 2 requests at this point

        int newRequestId = 0;
        for (int i = 0; i < requests.size(); ++i) {
            if (requests.valueAt(i).requestId != origRequestId) {
                newRequestId = requests.valueAt(i).requestId;
                break;
            }
        }

        // Simulate the factory releasing the request as unfulfillable and expect onUnavailable!
        testFactory.expectRemoveRequests(1);
        testFactory.triggerUnfulfillable(requests.get(newRequestId));
        networkCallback.expectCallback(CallbackState.UNAVAILABLE, null);
        testFactory.waitForRequests();

        testFactory.unregister();
        handlerThread.quit();
    }

    private static class TestKeepaliveCallback extends PacketKeepaliveCallback {

        public static enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR };