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

Commit 822c7729 authored by Jack Yu's avatar Jack Yu Committed by Gerrit Code Review
Browse files

Merge changes from topic "iwlan_handover_rules"

* changes:
  Implement RCS Dedicated Bearer metrics - fix not reporting case
  Added IWLAN handover rules support
parents 3266b0d6 11e0b97c
Loading
Loading
Loading
Loading
+40 −5
Original line number Diff line number Diff line
@@ -44,6 +44,7 @@ import android.util.IndentingPrintWriter;

import com.android.internal.R;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.data.DataNetworkController.HandoverRule;
import com.android.internal.telephony.data.DataRetryManager.DataRetryRule;
import com.android.telephony.Rlog;

@@ -214,6 +215,8 @@ public class DataConfigManager extends Handler {
    /** A map of network types to the TCP buffer sizes for that network type */
    private @NonNull final @DataConfigNetworkType Map<String, String> mTcpBufferSizeMap =
            new ConcurrentHashMap<>();
    /** Rules for handover between IWLAN and cellular network. */
    private @NonNull final List<HandoverRule> mHandoverRuleList = new ArrayList<>();

    /**
     * Constructor
@@ -289,6 +292,7 @@ public class DataConfigManager extends Handler {
        updateUnmeteredNetworkTypes();
        updateBandwidths();
        updateTcpBuffers();
        updateHandoverRules();

        log("Data config updated. Config is " + (isConfigCarrierSpecific() ? "" : "not ")
                + "carrier specific.");
@@ -348,9 +352,13 @@ public class DataConfigManager extends Handler {
            String[] dataRetryRulesStrings = mCarrierConfig.getStringArray(
                    CarrierConfigManager.KEY_TELEPHONY_DATA_RETRY_RULES_STRING_ARRAY);
            if (dataRetryRulesStrings != null) {
                Arrays.stream(dataRetryRulesStrings)
                        .map(DataRetryRule::new)
                        .forEach(mDataRetryRules::add);
                for (String ruleString : dataRetryRulesStrings) {
                    try {
                        mDataRetryRules.add(new DataRetryRule(ruleString));
                    } catch (IllegalArgumentException e) {
                        loge("updateDataRetryRules: " + e.getMessage());
                    }
                }
            }
        }
    }
@@ -444,8 +452,8 @@ public class DataConfigManager extends Handler {
    }

    /**
     * @return Whether {@link NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED}
     * is supported by the carrier
     * @return Whether {@link NetworkCapabilities#NET_CAPABILITY_TEMPORARILY_NOT_METERED}
     * is supported by the carrier.
     */
    public boolean isTempNotMeteredSupportedByCarrier() {
        return mCarrierConfig.getBoolean(
@@ -677,6 +685,33 @@ public class DataConfigManager extends Handler {
        return networkTypeToDataConfigNetworkType(networkType);
    }

    /** Update handover rules from carrier config. */
    private void updateHandoverRules() {
        synchronized (this) {
            mHandoverRuleList.clear();
            String[] handoverRulesStrings = mCarrierConfig.getStringArray(
                    CarrierConfigManager.KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY);
            if (handoverRulesStrings != null) {
                for (String ruleString : handoverRulesStrings) {
                    try {
                        mHandoverRuleList.add(new HandoverRule(ruleString));
                    } catch (IllegalArgumentException e) {
                        loge("updateHandoverRules: " + e.getMessage());
                    }
                }
            }
        }
    }

    /**
     * @return Get rules for handover between IWLAN and cellular networks.
     *
     * @see CarrierConfigManager#KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY
     */
    public @NonNull List<HandoverRule> getHandoverRules() {
        return Collections.unmodifiableList(mHandoverRuleList);
    }

    /**
     * Get the data config network type for the given network type
     *
+152 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.internal.telephony.data;

import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
@@ -32,11 +33,14 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.telephony.AccessNetworkConstants;
import android.telephony.AccessNetworkConstants.AccessNetworkType;
import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
import android.telephony.AccessNetworkConstants.TransportType;
import android.telephony.Annotation.DataFailureCause;
import android.telephony.Annotation.NetCapability;
import android.telephony.Annotation.NetworkType;
import android.telephony.Annotation.ValidationStatus;
import android.telephony.CarrierConfigManager;
import android.telephony.DataFailCause;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.NetworkRegistrationInfo.RegistrationState;
@@ -83,12 +87,15 @@ import com.android.telephony.Rlog;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -582,6 +589,151 @@ public class DataNetworkController extends Handler {
        }
    }

    /**
     * This class represent a rule allowing or disallowing handover between IWLAN and cellular
     * networks.
     *
     * @see CarrierConfigManager#KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY
     */
    public static class HandoverRule {
        @Retention(RetentionPolicy.SOURCE)
        @IntDef(prefix = {"RULE_TYPE_"},
                value = {
                        RULE_TYPE_ALLOWED,
                        RULE_TYPE_DISALLOWED,
                })
        public @interface HandoverRuleType {}

        /** Indicating this rule is for allowing handover. */
        public static final int RULE_TYPE_ALLOWED = 1;

        /** Indicating this rule is for disallowing handover. */
        public static final int RULE_TYPE_DISALLOWED = 2;

        private static final String RULE_TAG_SOURCE_ACCESS_NETWORKS = "source";

        private static final String RULE_TAG_TARGET_ACCESS_NETWORKS = "target";

        private static final String RULE_TAG_TYPE = "type";

        private static final String RULE_TAG_ROAMING = "roaming";

        /** Handover rule type. */
        public final @HandoverRuleType int ruleType;

        /** The applicable source access networks for handover. */
        public final @NonNull @RadioAccessNetworkType Set<Integer> sourceAccessNetworks;

        /** The applicable target access networks for handover. */
        public final @NonNull @RadioAccessNetworkType Set<Integer> targetAccessNetworks;

        /** {@code true} indicates this policy is only applicable when the device is roaming. */
        public final boolean isRoaming;

        /**
         * Constructor
         *
         * @param ruleString The rule in string format.
         *
         * @see CarrierConfigManager#KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY
         */
        public HandoverRule(@NonNull String ruleString) {
            if (TextUtils.isEmpty(ruleString)) {
                throw new IllegalArgumentException("illegal rule " + ruleString);
            }

            Set<Integer> source = null, target = null;
            int type = 0;
            boolean roaming = false;

            ruleString = ruleString.trim().toLowerCase(Locale.ROOT);
            String[] expressions = ruleString.split("\\s*,\\s*");
            for (String expression : expressions) {
                String[] tokens = expression.trim().split("\\s*=\\s*");
                if (tokens.length != 2) {
                    throw new IllegalArgumentException("illegal rule " + ruleString + ", tokens="
                            + Arrays.toString(tokens));
                }
                String key = tokens[0].trim();
                String value = tokens[1].trim();
                try {
                    switch (key) {
                        case RULE_TAG_SOURCE_ACCESS_NETWORKS:
                            source = Arrays.stream(value.split("\\s*\\|\\s*"))
                                    .map(String::trim)
                                    .map(AccessNetworkType::fromString)
                                    .collect(Collectors.toSet());
                            break;
                        case RULE_TAG_TARGET_ACCESS_NETWORKS:
                            target = Arrays.stream(value.split("\\s*\\|\\s*"))
                                    .map(String::trim)
                                    .map(AccessNetworkType::fromString)
                                    .collect(Collectors.toSet());
                            break;
                        case RULE_TAG_TYPE:
                            if (value.toLowerCase(Locale.ROOT).equals("allowed")) {
                                type = RULE_TYPE_ALLOWED;
                            } else if (value.toLowerCase(Locale.ROOT).equals("disallowed")) {
                                type = RULE_TYPE_DISALLOWED;
                            } else {
                                throw new IllegalArgumentException("unexpected rule type " + value);
                            }
                            break;
                        case RULE_TAG_ROAMING:
                            roaming = Boolean.parseBoolean(value);
                            break;
                        default:
                            throw new IllegalArgumentException("unexpected key " + key);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    throw new IllegalArgumentException("illegal rule \"" + ruleString + "\", e="
                            + e);
                }
            }

            if (source == null || target == null) {
                throw new IllegalArgumentException("Need to specify both source and target. "
                        + "\"" + ruleString + "\"");
            }

            if (source.contains(AccessNetworkType.UNKNOWN)) {
                throw new IllegalArgumentException("Source access networks contains unknown. "
                        + "\"" + ruleString + "\"");
            }

            if (target.contains(AccessNetworkType.UNKNOWN)) {
                throw new IllegalArgumentException("Target access networks contains unknown. "
                        + "\"" + ruleString + "\"");
            }

            if (type == 0) {
                throw new IllegalArgumentException("Rule type is not specified correctly. "
                        + "\"" + ruleString + "\"");
            }

            if (!source.contains(AccessNetworkType.IWLAN)
                    && !target.contains(AccessNetworkType.IWLAN)) {
                throw new IllegalArgumentException("IWLAN must be specified in either source or "
                        + "target access networks.\"" + ruleString + "\"");
            }

            sourceAccessNetworks = source;
            targetAccessNetworks = target;
            ruleType = type;
            isRoaming = roaming;
        }

        @Override
        public String toString() {
            return "[HandoverRule: type=" + (ruleType == RULE_TYPE_ALLOWED ? "allowed"
                    : "disallowed") + ", source=" + sourceAccessNetworks.stream()
                    .map(AccessNetworkType::toString).collect(Collectors.joining("|"))
                    + ", target=" + targetAccessNetworks.stream().map(AccessNetworkType::toString)
                    .collect(Collectors.joining("|")) + ", isRoaming=" + isRoaming + "]";
        }
    }

    /**
     * Constructor
     *
+56 −34
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

@@ -184,13 +185,15 @@ public class QosCallbackTracker extends Handler {
            log("updateSessions: sessions size=" + sessions.size());

            int bearerState = DEDICATED_BEARER_EVENT_STATE_NONE;

            final List<QosBearerSession> sessionsToAdd = new ArrayList<>();
            final Map<Integer, QosBearerSession> incomingSessions = new HashMap<>();
            final HashSet<Integer> sessionsReportedToMetric = new HashSet<>();
            for (final QosBearerSession incomingSession : sessions) {
                incomingSessions.put(incomingSession.getQosBearerSessionId(), incomingSession);
                int sessionId = incomingSession.getQosBearerSessionId();
                incomingSessions.put(sessionId, incomingSession);

                final QosBearerSession existingSession = mQosBearerSessions.get(
                        incomingSession.getQosBearerSessionId());
                final QosBearerSession existingSession = mQosBearerSessions.get(sessionId);
                for (final int callbackId : mCallbacksToFilter.keySet()) {
                    final IFilter filter = mCallbacksToFilter.get(callbackId);

@@ -214,15 +217,31 @@ public class QosCallbackTracker extends Handler {
                        }
                    }

                    notifyMetricDedicatedBearerEvent(incomingSession, filter, bearerState);
                    // this QosBearerSession has registered QosCallbackId
                    if (!sessionsReportedToMetric.contains(sessionId) && incomingSessionMatch) {
                        // this session has listener
                        notifyMetricDedicatedBearerEvent(incomingSession, bearerState, true);
                        sessionsReportedToMetric.add(sessionId);
                    }
                }

                // this QosBearerSession does not have registered QosCallbackId
                if (!sessionsReportedToMetric.contains(sessionId)) {
                    // no listener is registered to this session
                    bearerState = DEDICATED_BEARER_EVENT_STATE_ADDED;
                    notifyMetricDedicatedBearerEvent(incomingSession, bearerState, false);
                    sessionsReportedToMetric.add(sessionId);
                }
                sessionsToAdd.add(incomingSession);
            }

            final List<Integer> sessionsToRemove = new ArrayList<>();
            sessionsReportedToMetric.clear();
            bearerState = DEDICATED_BEARER_EVENT_STATE_DELETED;
            // Find sessions that no longer exist
            for (final QosBearerSession existingSession : mQosBearerSessions.values()) {
                if (!incomingSessions.containsKey(existingSession.getQosBearerSessionId())) {
                final int sessionId = existingSession.getQosBearerSessionId();
                if (!incomingSessions.containsKey(sessionId)) {
                    for (final int callbackId : mCallbacksToFilter.keySet()) {
                        final IFilter filter = mCallbacksToFilter.get(callbackId);
                        // The filter matches which means it was previously available, and now is
@@ -230,10 +249,15 @@ public class QosCallbackTracker extends Handler {
                        if (doFiltersMatch(existingSession, filter)) {
                            bearerState = DEDICATED_BEARER_EVENT_STATE_DELETED;
                            sendSessionLost(callbackId, existingSession);
                            notifyMetricDedicatedBearerEvent(existingSession, filter, bearerState);
                            notifyMetricDedicatedBearerEvent(existingSession, bearerState, true);
                            sessionsReportedToMetric.add(sessionId);
                        }
                    }
                    sessionsToRemove.add(sessionId);
                    if (!sessionsReportedToMetric.contains(sessionId)) {
                        notifyMetricDedicatedBearerEvent(existingSession, bearerState, false);
                        sessionsReportedToMetric.add(sessionId);
                    }
                    sessionsToRemove.add(existingSession.getQosBearerSessionId());
                }
            }

@@ -410,38 +434,36 @@ public class QosCallbackTracker extends Handler {
        return 0;
    }

    private void notifyMetricDedicatedBearerEvent(final QosBearerSession session,
            final IFilter filter, final int bearerState) {

        int ratAtEnd;
        int qci;
        boolean localConnectionInfoReceived = false;
        boolean remoteConnectionInfoReceived = false;

        QosBearerFilter qosBearerFilter = getMatchingQosBearerFilter(session, filter);
        if (session.getQos() instanceof EpsQos) {
            ratAtEnd = TelephonyManager.NETWORK_TYPE_LTE;
            qci = ((EpsQos) session.getQos()).getQci();
        } else if (session.getQos() instanceof NrQos) {
            ratAtEnd = TelephonyManager.NETWORK_TYPE_NR;
            qci = ((NrQos) session.getQos()).get5Qi();
        } else {
            return;
    private boolean doesLocalConnectionInfoExist(final QosBearerSession qosBearerSession) {
        for (final QosBearerFilter sessionFilter : qosBearerSession.getQosBearerFilterList()) {
            if (!sessionFilter.getLocalAddresses().isEmpty()
                    && sessionFilter.getLocalPortRange().isValid()) {
                return true;
            }
        }
        return false;
    }

        if (qosBearerFilter != null) {
            if (!qosBearerFilter.getLocalAddresses().isEmpty()
                    && qosBearerFilter.getLocalPortRange().isValid()) {
                localConnectionInfoReceived = true;
    private boolean doesRemoteConnectionInfoExist(final QosBearerSession qosBearerSession) {
        for (final QosBearerFilter sessionFilter : qosBearerSession.getQosBearerFilterList()) {
            if (!sessionFilter.getRemoteAddresses().isEmpty()
                    && sessionFilter.getRemotePortRange().isValid()) {
                return true;
            }
            if (!qosBearerFilter.getRemoteAddresses().isEmpty()
                    && qosBearerFilter.getRemotePortRange().isValid()) {
                remoteConnectionInfoReceived = true;
        }
        return false;
    }

        mRcsStats.onImsDedicatedBearerEvent(mPhoneId, ratAtEnd, qci, bearerState,
                localConnectionInfoReceived, remoteConnectionInfoReceived, true);
    private void notifyMetricDedicatedBearerEvent(final QosBearerSession session,
            final int bearerState, final boolean hasListener) {
        final int slotId = mPhoneId;
        int ratAtEnd = getRatInfoFromSessionInfo(session);
        int qci = getQCIFromSessionInfo(session);
        boolean localConnectionInfoReceived = doesLocalConnectionInfoExist(session);
        boolean remoteConnectionInfoReceived = doesRemoteConnectionInfoExist(session);

        mRcsStats.onImsDedicatedBearerEvent(slotId, ratAtEnd, qci, bearerState,
                localConnectionInfoReceived, remoteConnectionInfoReceived, hasListener);
    }

    /**
+50 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static com.android.internal.telephony.data.DataNetworkController.NetworkR

import static com.google.common.truth.Truth.assertThat;

import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -43,6 +44,7 @@ import android.os.Message;
import android.os.PersistableBundle;
import android.os.RegistrantList;
import android.telephony.AccessNetworkConstants;
import android.telephony.AccessNetworkConstants.AccessNetworkType;
import android.telephony.Annotation.NetworkType;
import android.telephony.CarrierConfigManager;
import android.telephony.NetworkRegistrationInfo;
@@ -61,6 +63,7 @@ import com.android.internal.telephony.ISub;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.PhoneSwitcher;
import com.android.internal.telephony.TelephonyTest;
import com.android.internal.telephony.data.DataNetworkController.HandoverRule;

import org.junit.After;
import org.junit.Before;
@@ -743,4 +746,51 @@ public class DataNetworkControllerTest extends TelephonyTest {
        // Verify data is allowed
        verifyInternetConnected();
    }

    @Test
    public void testHandoverRuleFromString() {
        HandoverRule handoverRule = new HandoverRule("source=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, "
                + "target=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, type=allowed");
        assertThat(handoverRule.sourceAccessNetworks).containsExactly(AccessNetworkType.GERAN,
                AccessNetworkType.UTRAN, AccessNetworkType.EUTRAN, AccessNetworkType.NGRAN,
                AccessNetworkType.IWLAN);
        assertThat(handoverRule.targetAccessNetworks).containsExactly(AccessNetworkType.GERAN,
                AccessNetworkType.UTRAN, AccessNetworkType.EUTRAN, AccessNetworkType.NGRAN,
                AccessNetworkType.IWLAN);
        assertThat(handoverRule.ruleType).isEqualTo(HandoverRule.RULE_TYPE_ALLOWED);
        assertThat(handoverRule.isRoaming).isFalse();

        handoverRule = new HandoverRule("source=   NGRAN|     IWLAN, "
                + "target  =    EUTRAN,    type  =    disallowed");
        assertThat(handoverRule.sourceAccessNetworks).containsExactly(AccessNetworkType.NGRAN,
                AccessNetworkType.IWLAN);
        assertThat(handoverRule.targetAccessNetworks).containsExactly(AccessNetworkType.EUTRAN);
        assertThat(handoverRule.ruleType).isEqualTo(HandoverRule.RULE_TYPE_DISALLOWED);
        assertThat(handoverRule.isRoaming).isFalse();

        handoverRule = new HandoverRule("source=   IWLAN, "
                + "target  =    EUTRAN,    type  =    disallowed, roaming = true");
        assertThat(handoverRule.sourceAccessNetworks).containsExactly(AccessNetworkType.IWLAN);
        assertThat(handoverRule.targetAccessNetworks).containsExactly(AccessNetworkType.EUTRAN);
        assertThat(handoverRule.ruleType).isEqualTo(HandoverRule.RULE_TYPE_DISALLOWED);
        assertThat(handoverRule.isRoaming).isTrue();

        assertThrows(IllegalArgumentException.class,
                () -> new HandoverRule("V2hhdCBUaGUgRnVjayBpcyB0aGlzIQ=="));

        assertThrows(IllegalArgumentException.class,
                () -> new HandoverRule("target=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, type=allowed"));

        assertThrows(IllegalArgumentException.class,
                () -> new HandoverRule("source=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, type=allowed"));

        assertThrows(IllegalArgumentException.class,
                () -> new HandoverRule("source=GERAN, target=IWLAN, type=wtf"));

        assertThrows(IllegalArgumentException.class,
                () -> new HandoverRule("source=GERAN, target=NGRAN, type=allowed"));

        assertThrows(IllegalArgumentException.class,
                () -> new HandoverRule("source=IWLAN, target=WTFRAN, type=allowed"));
    }
}