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

Commit 67bfec20 authored by Chalard Jean's avatar Chalard Jean
Browse files

Implement setNetworkPreferenceForUser.

Test: FrameworksNetTests
Change-Id: I8f18083b5857289892fe8adea5f5ea3f5dbe0809
parent b1cd4d7b
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -72,6 +72,14 @@ public final class OemNetworkPreferences implements Parcelable {
    @NonNull
    private final Bundle mNetworkMappings;

    /**
     * Return whether this object is empty.
     * @hide
     */
    public boolean isEmpty() {
        return mNetworkMappings.keySet().size() == 0;
    }

    /**
     * Return the currently built application package name to {@link OemNetworkPreference} mappings.
     * @return the current network preferences map.
+174 −12
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID;
import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE;
import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
@@ -215,6 +216,7 @@ import com.android.server.connectivity.NetworkNotificationManager;
import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
import com.android.server.connectivity.NetworkRanker;
import com.android.server.connectivity.PermissionMonitor;
import com.android.server.connectivity.ProfileNetworkPreferences;
import com.android.server.connectivity.ProxyTracker;
import com.android.server.connectivity.QosCallbackTracker;
import com.android.server.net.NetworkPolicyManagerInternal;
@@ -559,8 +561,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
    private static final int EVENT_SET_REQUIRE_VPN_FOR_UIDS = 47;

    /**
     * used internally when setting the default networks for OemNetworkPreferences.
     * obj = OemNetworkPreferences
     * Used internally when setting the default networks for OemNetworkPreferences.
     * obj = Pair<OemNetworkPreferences, listener>
     */
    private static final int EVENT_SET_OEM_NETWORK_PREFERENCE = 48;

@@ -569,6 +571,12 @@ public class ConnectivityService extends IConnectivityManager.Stub
     */
    private static final int EVENT_REPORT_NETWORK_ACTIVITY = 49;

    /**
     * Used internally when setting a network preference for a user profile.
     * obj = Pair<ProfileNetworkPreference, Listener>
     */
    private static final int EVENT_SET_PROFILE_NETWORK_PREFERENCE = 50;

    /**
     * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
     * should be shown.
@@ -1290,11 +1298,16 @@ public class ConnectivityService extends IConnectivityManager.Stub
    }

    private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) {
        return createDefaultNetworkCapabilitiesForUidRange(new UidRange(uid, uid));
    }

    private static NetworkCapabilities createDefaultNetworkCapabilitiesForUidRange(
            @NonNull final UidRange uids) {
        final NetworkCapabilities netCap = new NetworkCapabilities();
        netCap.addCapability(NET_CAPABILITY_INTERNET);
        netCap.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
        netCap.removeCapability(NET_CAPABILITY_NOT_VPN);
        netCap.setSingleUid(uid);
        netCap.setUids(Collections.singleton(uids));
        return netCap;
    }

@@ -4537,6 +4550,12 @@ public class ConnectivityService extends IConnectivityManager.Stub
                    handleSetOemNetworkPreference(arg.first, arg.second);
                    break;
                }
                case EVENT_SET_PROFILE_NETWORK_PREFERENCE: {
                    final Pair<ProfileNetworkPreferences.Preference, IOnCompleteListener> arg =
                            (Pair<ProfileNetworkPreferences.Preference, IOnCompleteListener>)
                                    msg.obj;
                    handleSetProfileNetworkPreference(arg.first, arg.second);
                }
                case EVENT_REPORT_NETWORK_ACTIVITY:
                    mNetworkActivityTracker.handleReportNetworkActivity();
                    break;
@@ -5897,10 +5916,16 @@ public class ConnectivityService extends IConnectivityManager.Stub
    @GuardedBy("mBlockedAppUids")
    private final HashSet<Integer> mBlockedAppUids = new HashSet<>();

    // Current OEM network preferences.
    // Current OEM network preferences. This object must only be written to on the handler thread.
    // Since it is immutable and always non-null, other threads may read it if they only care
    // about seeing a consistent object but not that it is current.
    @NonNull
    private OemNetworkPreferences mOemNetworkPreferences =
            new OemNetworkPreferences.Builder().build();
    // Current per-profile network preferences. This object follows the same threading rules as
    // the OEM network preferences above.
    @NonNull
    private ProfileNetworkPreferences mProfileNetworkPreferences = new ProfileNetworkPreferences();

    // The always-on request for an Internet-capable network that apps without a specific default
    // fall back to.
@@ -9101,13 +9126,19 @@ public class ConnectivityService extends IConnectivityManager.Stub
        mQosCallbackTracker.unregisterCallback(callback);
    }

    private void enforceAutomotiveDevice() {
        final boolean isAutomotiveDevice =
                mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
        if (!isAutomotiveDevice) {
            throw new UnsupportedOperationException(
                    "setOemNetworkPreference() is only available on automotive devices.");
        }
    // Network preference per-profile and OEM network preferences can't be set at the same
    // time, because it is unclear what should happen if both preferences are active for
    // one given UID. To make it possible, the stack would have to clarify what would happen
    // in case both are active at the same time. The implementation may have to be adjusted
    // to implement the resulting rules. For example, a priority could be defined between them,
    // where the OEM preference would be considered less or more important than the enterprise
    // preference ; this would entail implementing the priorities somehow, e.g. by doing
    // UID arithmetic with UID ranges or passing a priority to netd so that the routing rules
    // are set at the right level. Other solutions are possible, e.g. merging of the
    // preferences for the relevant UIDs.
    private static void throwConcurrentPreferenceException() {
        throw new IllegalStateException("Can't set NetworkPreferenceForUser and "
                + "set OemNetworkPreference at the same time");
    }

    /**
@@ -9125,7 +9156,118 @@ public class ConnectivityService extends IConnectivityManager.Stub
    public void setProfileNetworkPreference(@NonNull final UserHandle profile,
            @ConnectivityManager.ProfileNetworkPreference final int preference,
            @Nullable final IOnCompleteListener listener) {
        throw new UnsupportedOperationException("Not implemented yet");
        Objects.requireNonNull(profile);
        PermissionUtils.enforceNetworkStackPermission(mContext);
        if (DBG) {
            log("setProfileNetworkPreference " + profile + " to " + preference);
        }
        if (profile.getIdentifier() < 0) {
            throw new IllegalArgumentException("Must explicitly specify a user handle ("
                    + "UserHandle.CURRENT not supported)");
        }
        final UserManager um;
        try {
            um = mContext.createContextAsUser(profile, 0 /* flags */)
                    .getSystemService(UserManager.class);
        } catch (IllegalStateException e) {
            throw new IllegalArgumentException("Profile does not exist");
        }
        if (!um.isManagedProfile()) {
            throw new IllegalArgumentException("Profile must be a managed profile");
        }
        // Strictly speaking, mOemNetworkPreferences should only be touched on the
        // handler thread. However it is an immutable object, so reading the reference is
        // safe - it's just possible the value is slightly outdated. For the final check,
        // see #handleSetProfileNetworkPreference. But if this can be caught here it is a
        // lot easier to understand, so opportunistically check it.
        if (!mOemNetworkPreferences.isEmpty()) {
            throwConcurrentPreferenceException();
        }
        final NetworkCapabilities nc;
        switch (preference) {
            case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT:
                nc = null;
                break;
            case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE:
                final UidRange uids = UidRange.createForUser(profile);
                nc = createDefaultNetworkCapabilitiesForUidRange(uids);
                nc.addCapability(NET_CAPABILITY_ENTERPRISE);
                nc.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
                break;
            default:
                throw new IllegalArgumentException(
                        "Invalid preference in setProfileNetworkPreference");
        }
        mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_PROFILE_NETWORK_PREFERENCE,
                new Pair<>(new ProfileNetworkPreferences.Preference(profile, nc), listener)));
    }

    private void validateNetworkCapabilitiesOfProfileNetworkPreference(
            @Nullable final NetworkCapabilities nc) {
        if (null == nc) return; // Null caps are always allowed. It means to remove the setting.
        ensureRequestableCapabilities(nc);
    }

    private ArraySet<NetworkRequestInfo> createNrisFromProfileNetworkPreferences(
            @NonNull final ProfileNetworkPreferences prefs) {
        final ArraySet<NetworkRequestInfo> result = new ArraySet<>();
        for (final ProfileNetworkPreferences.Preference pref : prefs.preferences) {
            // The NRI for a user should be comprised of two layers:
            // - The request for the capabilities
            // - The request for the default network, for fallback. Create an image of it to
            //   have the correct UIDs in it (also a request can only be part of one NRI, because
            //   of lookups in 1:1 associations like mNetworkRequests).
            // Note that denying a fallback can be implemented simply by not adding the second
            // request.
            final ArrayList<NetworkRequest> nrs = new ArrayList<>();
            nrs.add(createNetworkRequest(NetworkRequest.Type.REQUEST, pref.capabilities));
            nrs.add(createDefaultRequest());
            setNetworkRequestUids(nrs, pref.capabilities.getUids());
            final NetworkRequestInfo nri = new NetworkRequestInfo(nrs);
            result.add(nri);
        }
        return result;
    }

    private void handleSetProfileNetworkPreference(
            @NonNull final ProfileNetworkPreferences.Preference preference,
            @Nullable final IOnCompleteListener listener) {
        // setProfileNetworkPreference and setOemNetworkPreference are mutually exclusive, in
        // particular because it's not clear what preference should win in case both apply
        // to the same app.
        // The binder call has already checked this, but as mOemNetworkPreferences is only
        // touched on the handler thread, it's theoretically not impossible that it has changed
        // since.
        if (!mOemNetworkPreferences.isEmpty()) {
            logwtf("handleSetProfileNetworkPreference, but OEM network preferences not empty");
            return;
        }

        validateNetworkCapabilitiesOfProfileNetworkPreference(preference.capabilities);

        mProfileNetworkPreferences = mProfileNetworkPreferences.plus(preference);
        final ArraySet<NetworkRequestInfo> nris =
                createNrisFromProfileNetworkPreferences(mProfileNetworkPreferences);
        replaceDefaultNetworkRequestsForPreference(nris);
        // Finally, rematch.
        rematchAllNetworksAndRequests();

        if (null != listener) {
            try {
                listener.onComplete();
            } catch (RemoteException e) {
                loge("Listener for setProfileNetworkPreference has died");
            }
        }
    }

    private void enforceAutomotiveDevice() {
        final boolean isAutomotiveDevice =
                mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
        if (!isAutomotiveDevice) {
            throw new UnsupportedOperationException(
                    "setOemNetworkPreference() is only available on automotive devices.");
        }
    }

    /**
@@ -9148,6 +9290,15 @@ public class ConnectivityService extends IConnectivityManager.Stub
        enforceAutomotiveDevice();
        enforceOemNetworkPreferencesPermission();

        if (!mProfileNetworkPreferences.isEmpty()) {
            // Strictly speaking, mProfileNetworkPreferences should only be touched on the
            // handler thread. However it is an immutable object, so reading the reference is
            // safe - it's just possible the value is slightly outdated. For the final check,
            // see #handleSetOemPreference. But if this can be caught here it is a
            // lot easier to understand, so opportunistically check it.
            throwConcurrentPreferenceException();
        }

        Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null");
        validateOemNetworkPreferences(preference);
        mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_OEM_NETWORK_PREFERENCE,
@@ -9171,6 +9322,17 @@ public class ConnectivityService extends IConnectivityManager.Stub
        if (DBG) {
            log("set OEM network preferences :" + preference.toString());
        }
        // setProfileNetworkPreference and setOemNetworkPreference are mutually exclusive, in
        // particular because it's not clear what preference should win in case both apply
        // to the same app.
        // The binder call has already checked this, but as mOemNetworkPreferences is only
        // touched on the handler thread, it's theoretically not impossible that it has changed
        // since.
        if (!mProfileNetworkPreferences.isEmpty()) {
            logwtf("handleSetOemPreference, but per-profile network preferences not empty");
            return;
        }

        final ArraySet<NetworkRequestInfo> nris =
                new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(preference);
        replaceDefaultNetworkRequestsForPreference(nris);
+87 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.connectivity;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.NetworkCapabilities;
import android.os.UserHandle;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * A data class containing all the per-profile network preferences.
 *
 * A given profile can only have one preference.
 */
public class ProfileNetworkPreferences {
    /**
     * A single preference, as it applies to a given user profile.
     */
    public static class Preference {
        @NonNull public final UserHandle user;
        // Capabilities are only null when sending an object to remove the setting for a user
        @Nullable public final NetworkCapabilities capabilities;

        public Preference(@NonNull final UserHandle user,
                @Nullable final NetworkCapabilities capabilities) {
            this.user = user;
            this.capabilities = null == capabilities ? null : new NetworkCapabilities(capabilities);
        }

        /** toString */
        public String toString() {
            return "[ProfileNetworkPreference user=" + user + " caps=" + capabilities + "]";
        }
    }

    @NonNull public final List<Preference> preferences;

    public ProfileNetworkPreferences() {
        preferences = Collections.EMPTY_LIST;
    }

    private ProfileNetworkPreferences(@NonNull final List<Preference> list) {
        preferences = Collections.unmodifiableList(list);
    }

    /**
     * Returns a new object consisting of this object plus the passed preference.
     *
     * If a preference already exists for the same user, it will be replaced by the passed
     * preference. Passing a Preference object containing a null capabilities object is equivalent
     * to (and indeed, implemented as) removing the preference for this user.
     */
    public ProfileNetworkPreferences plus(@NonNull final Preference pref) {
        final ArrayList<Preference> newPrefs = new ArrayList<>();
        for (final Preference existingPref : preferences) {
            if (!existingPref.user.equals(pref.user)) {
                newPrefs.add(existingPref);
            }
        }
        if (null != pref.capabilities) {
            newPrefs.add(pref);
        }
        return new ProfileNetworkPreferences(newPrefs);
    }

    public boolean isEmpty() {
        return preferences.isEmpty();
    }
}