Merge "[NS01] Create NetworkScore"
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
index 43ea589..ff4bf2d 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/core/java/android/net/NetworkAgent.java
@@ -16,6 +16,7 @@
 
 package android.net;
 
+import android.annotation.NonNull;
 import android.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.Build;
@@ -418,7 +419,16 @@
         if (score < 0) {
             throw new IllegalArgumentException("Score must be >= 0");
         }
-        queueOrSendMessage(EVENT_NETWORK_SCORE_CHANGED,  score, 0);
+        final NetworkScore ns = new NetworkScore();
+        ns.putIntExtension(NetworkScore.LEGACY_SCORE, score);
+        updateScore(ns);
+    }
+
+    /**
+     * Called by the bearer code when it has a new NetworkScore for this network.
+     */
+    public void updateScore(@NonNull NetworkScore ns) {
+        queueOrSendMessage(EVENT_NETWORK_SCORE_CHANGED, new NetworkScore(ns));
     }
 
     /**
diff --git a/core/java/android/net/NetworkScore.java b/core/java/android/net/NetworkScore.java
new file mode 100644
index 0000000..1ab6335
--- /dev/null
+++ b/core/java/android/net/NetworkScore.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2019 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 android.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Object representing the quality of a network as perceived by the user.
+ *
+ * A NetworkScore object represents the characteristics of a network that affects how good the
+ * network is considered for a particular use.
+ * @hide
+ */
+public final class NetworkScore implements Parcelable {
+
+    // The key of bundle which is used to get the legacy network score of NetworkAgentInfo.
+    // TODO: Remove this when the transition to NetworkScore is over.
+    public static final String LEGACY_SCORE = "LEGACY_SCORE";
+    @NonNull
+    private final Bundle mExtensions;
+
+    public NetworkScore() {
+        mExtensions = new Bundle();
+    }
+
+    public NetworkScore(@NonNull NetworkScore source) {
+        mExtensions = new Bundle(source.mExtensions);
+    }
+
+    /**
+     * Put the value of parcelable inside the bundle by key.
+     */
+    public void putExtension(@Nullable String key, @Nullable Parcelable value) {
+        mExtensions.putParcelable(key, value);
+    }
+
+    /**
+     * Put the value of int inside the bundle by key.
+     */
+    public void putIntExtension(@Nullable String key, int value) {
+        mExtensions.putInt(key, value);
+    }
+
+    /**
+     * Get the value of non primitive type by key.
+     */
+    public <T extends Parcelable> T getExtension(@Nullable String key) {
+        return mExtensions.getParcelable(key);
+    }
+
+    /**
+     * Get the value of int by key.
+     */
+    public int getIntExtension(@Nullable String key) {
+        return mExtensions.getInt(key);
+    }
+
+    /**
+     * Remove the entry by given key.
+     */
+    public void removeExtension(@Nullable String key) {
+        mExtensions.remove(key);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        synchronized (this) {
+            dest.writeBundle(mExtensions);
+        }
+    }
+
+    public static final @NonNull Creator<NetworkScore> CREATOR = new Creator<NetworkScore>() {
+        @Override
+        public NetworkScore createFromParcel(@NonNull Parcel in) {
+            return new NetworkScore(in);
+        }
+
+        @Override
+        public NetworkScore[] newArray(int size) {
+            return new NetworkScore[size];
+        }
+    };
+
+    private NetworkScore(@NonNull Parcel in) {
+        mExtensions = in.readBundle();
+    }
+
+    // TODO: Modify this method once new fields are added into this class.
+    @Override
+    public boolean equals(@Nullable Object obj) {
+        if (!(obj instanceof NetworkScore)) {
+            return false;
+        }
+        final NetworkScore other = (NetworkScore) obj;
+        return bundlesEqual(mExtensions, other.mExtensions);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 29;
+        for (String key : mExtensions.keySet()) {
+            final Object value = mExtensions.get(key);
+            // The key may be null, so call Objects.hash() is safer.
+            result += 31 * value.hashCode() + 37 * Objects.hash(key);
+        }
+        return result;
+    }
+
+    // mExtensions won't be null since the constructor will create it.
+    private boolean bundlesEqual(@NonNull Bundle bundle1, @NonNull Bundle bundle2) {
+        if (bundle1 == bundle2) {
+            return true;
+        }
+
+        // This is unlikely but it's fine to add this clause here.
+        if (null == bundle1 || null == bundle2) {
+            return false;
+        }
+
+        if (bundle1.size() != bundle2.size()) {
+            return false;
+        }
+
+        for (String key : bundle1.keySet()) {
+            final Object value1 = bundle1.get(key);
+            final Object value2 = bundle2.get(key);
+            if (!Objects.equals(value1, value2)) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index ce0e9e7..81eb4b3 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -97,6 +97,7 @@
 import android.net.NetworkPolicyManager;
 import android.net.NetworkQuotaInfo;
 import android.net.NetworkRequest;
+import android.net.NetworkScore;
 import android.net.NetworkSpecifier;
 import android.net.NetworkStack;
 import android.net.NetworkStackClient;
@@ -2643,7 +2644,8 @@
                     break;
                 }
                 case NetworkAgent.EVENT_NETWORK_SCORE_CHANGED: {
-                    updateNetworkScore(nai, msg.arg1);
+                    final NetworkScore ns = (NetworkScore) msg.obj;
+                    updateNetworkScore(nai, ns);
                     break;
                 }
                 case NetworkAgent.EVENT_SET_EXPLICITLY_SELECTED: {
@@ -5594,9 +5596,11 @@
         // TODO: Instead of passing mDefaultRequest, provide an API to determine whether a Network
         // satisfies mDefaultRequest.
         final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities);
+        final NetworkScore ns = new NetworkScore();
+        ns.putIntExtension(NetworkScore.LEGACY_SCORE, currentScore);
         final NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(),
                 new Network(mNetIdManager.reserveNetId()), new NetworkInfo(networkInfo), lp, nc,
-                currentScore, mContext, mTrackerHandler, new NetworkMisc(networkMisc), this, mNetd,
+                ns, mContext, mTrackerHandler, new NetworkMisc(networkMisc), this, mNetd,
                 mDnsResolver, mNMS, factorySerialNumber);
         // Make sure the network capabilities reflect what the agent info says.
         nai.setNetworkCapabilities(mixInCapabilities(nai, nc));
@@ -6729,7 +6733,8 @@
         }
     }
 
-    private void updateNetworkScore(NetworkAgentInfo nai, int score) {
+    private void updateNetworkScore(NetworkAgentInfo nai, NetworkScore ns) {
+        int score = ns.getIntExtension(NetworkScore.LEGACY_SCORE);
         if (VDBG || DDBG) log("updateNetworkScore for " + nai.name() + " to " + score);
         if (score < 0) {
             loge("updateNetworkScore for " + nai.name() + " got a negative score (" + score +
@@ -6738,7 +6743,7 @@
         }
 
         final int oldScore = nai.getCurrentScore();
-        nai.setCurrentScore(score);
+        nai.setNetworkScore(ns);
 
         rematchAllNetworksAndRequests(nai, oldScore);
 
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 96b7cb3..24a5b7f 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -28,6 +28,7 @@
 import android.net.NetworkMisc;
 import android.net.NetworkMonitorManager;
 import android.net.NetworkRequest;
+import android.net.NetworkScore;
 import android.net.NetworkState;
 import android.os.Handler;
 import android.os.INetworkManagementService;
@@ -227,8 +228,10 @@
     // validated).
     private boolean mLingering;
 
-    // This represents the last score received from the NetworkAgent.
-    private int currentScore;
+    // This represents the characteristics of a network that affects how good the network is
+    // considered for a particular use.
+    @NonNull
+    private NetworkScore mNetworkScore;
 
     // The list of NetworkRequests being satisfied by this Network.
     private final SparseArray<NetworkRequest> mNetworkRequests = new SparseArray<>();
@@ -257,8 +260,8 @@
     private final Handler mHandler;
 
     public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, Network net, NetworkInfo info,
-            LinkProperties lp, NetworkCapabilities nc, int score, Context context, Handler handler,
-            NetworkMisc misc, ConnectivityService connService, INetd netd,
+            LinkProperties lp, NetworkCapabilities nc, @NonNull NetworkScore ns, Context context,
+            Handler handler, NetworkMisc misc, ConnectivityService connService, INetd netd,
             IDnsResolver dnsResolver, INetworkManagementService nms, int factorySerialNumber) {
         this.messenger = messenger;
         asyncChannel = ac;
@@ -266,7 +269,7 @@
         networkInfo = info;
         linkProperties = lp;
         networkCapabilities = nc;
-        currentScore = score;
+        mNetworkScore = ns;
         clatd = new Nat464Xlat(this, netd, dnsResolver, nms);
         mConnService = connService;
         mContext = context;
@@ -483,7 +486,7 @@
             return ConnectivityConstants.EXPLICITLY_SELECTED_NETWORK_SCORE;
         }
 
-        int score = currentScore;
+        int score = mNetworkScore.getIntExtension(NetworkScore.LEGACY_SCORE);
         if (!lastValidated && !pretendValidated && !ignoreWifiUnvalidationPenalty() && !isVPN()) {
             score -= ConnectivityConstants.UNVALIDATED_SCORE_PENALTY;
         }
@@ -512,8 +515,13 @@
         return getCurrentScore(true);
     }
 
-    public void setCurrentScore(int newScore) {
-        currentScore = newScore;
+    public void setNetworkScore(@NonNull NetworkScore ns) {
+        mNetworkScore = ns;
+    }
+
+    @NonNull
+    public NetworkScore getNetworkScore() {
+        return mNetworkScore;
     }
 
     public NetworkState getNetworkState() {
diff --git a/tests/net/common/java/android/net/NetworkScoreTest.kt b/tests/net/common/java/android/net/NetworkScoreTest.kt
new file mode 100644
index 0000000..30836b7
--- /dev/null
+++ b/tests/net/common/java/android/net/NetworkScoreTest.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2019 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 android.net
+
+import android.os.Parcelable
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.testutils.assertParcelSane
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNotEquals
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val TEST_SCORE = 80
+private const val KEY_DEFAULT_CAPABILITIES = "DEFAULT_CAPABILITIES"
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class NetworkScoreTest {
+    @Test
+    fun testParcelNetworkScore() {
+        val networkScore = NetworkScore()
+        val defaultCap = NetworkCapabilities()
+        networkScore.putExtension(KEY_DEFAULT_CAPABILITIES, defaultCap)
+        assertEquals(defaultCap, networkScore.getExtension(KEY_DEFAULT_CAPABILITIES))
+        networkScore.putIntExtension(NetworkScore.LEGACY_SCORE, TEST_SCORE)
+        assertEquals(TEST_SCORE, networkScore.getIntExtension(NetworkScore.LEGACY_SCORE))
+        assertParcelSane(networkScore, 1)
+    }
+
+    @Test
+    fun testNullKeyAndValue() {
+        val networkScore = NetworkScore()
+        val defaultCap = NetworkCapabilities()
+        networkScore.putIntExtension(null, TEST_SCORE)
+        assertEquals(TEST_SCORE, networkScore.getIntExtension(null))
+        networkScore.putExtension(null, defaultCap)
+        assertEquals(defaultCap, networkScore.getExtension(null))
+        networkScore.putExtension(null, null)
+        val result: Parcelable? = networkScore.getExtension(null)
+        assertEquals(null, result)
+    }
+
+    @Test
+    fun testRemoveExtension() {
+        val networkScore = NetworkScore()
+        val defaultCap = NetworkCapabilities()
+        networkScore.putExtension(KEY_DEFAULT_CAPABILITIES, defaultCap)
+        networkScore.putIntExtension(NetworkScore.LEGACY_SCORE, TEST_SCORE)
+        assertEquals(defaultCap, networkScore.getExtension(KEY_DEFAULT_CAPABILITIES))
+        assertEquals(TEST_SCORE, networkScore.getIntExtension(NetworkScore.LEGACY_SCORE))
+        networkScore.removeExtension(KEY_DEFAULT_CAPABILITIES)
+        networkScore.removeExtension(NetworkScore.LEGACY_SCORE)
+        val result: Parcelable? = networkScore.getExtension(KEY_DEFAULT_CAPABILITIES)
+        assertEquals(null, result)
+        assertEquals(0, networkScore.getIntExtension(NetworkScore.LEGACY_SCORE))
+    }
+
+    @Test
+    fun testEqualsNetworkScore() {
+        val ns1 = NetworkScore()
+        val ns2 = NetworkScore()
+        assertTrue(ns1.equals(ns2))
+        assertEquals(ns1.hashCode(), ns2.hashCode())
+
+        ns1.putIntExtension(NetworkScore.LEGACY_SCORE, TEST_SCORE)
+        assertFalse(ns1.equals(ns2))
+        assertNotEquals(ns1.hashCode(), ns2.hashCode())
+        ns2.putIntExtension(NetworkScore.LEGACY_SCORE, TEST_SCORE)
+        assertTrue(ns1.equals(ns2))
+        assertEquals(ns1.hashCode(), ns2.hashCode())
+
+        val defaultCap = NetworkCapabilities()
+        ns1.putExtension(KEY_DEFAULT_CAPABILITIES, defaultCap)
+        assertFalse(ns1.equals(ns2))
+        assertNotEquals(ns1.hashCode(), ns2.hashCode())
+        ns2.putExtension(KEY_DEFAULT_CAPABILITIES, defaultCap)
+        assertTrue(ns1.equals(ns2))
+        assertEquals(ns1.hashCode(), ns2.hashCode())
+
+        ns1.putIntExtension(null, 10)
+        assertFalse(ns1.equals(ns2))
+        assertNotEquals(ns1.hashCode(), ns2.hashCode())
+        ns2.putIntExtension(null, 10)
+        assertTrue(ns1.equals(ns2))
+        assertEquals(ns1.hashCode(), ns2.hashCode())
+    }
+}
diff --git a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
index 142769f..535298f 100644
--- a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
@@ -39,6 +39,7 @@
 import android.net.NetworkFactory;
 import android.net.NetworkInfo;
 import android.net.NetworkMisc;
+import android.net.NetworkScore;
 import android.os.INetworkManagementService;
 import android.text.format.DateUtils;
 
@@ -354,8 +355,10 @@
         NetworkCapabilities caps = new NetworkCapabilities();
         caps.addCapability(0);
         caps.addTransportType(transport);
+        NetworkScore ns = new NetworkScore();
+        ns.putIntExtension(NetworkScore.LEGACY_SCORE, 50);
         NetworkAgentInfo nai = new NetworkAgentInfo(null, null, new Network(netId), info, null,
-                caps, 50, mCtx, null, mMisc, mConnService, mNetd, mDnsResolver, mNMS,
+                caps, ns, mCtx, null, mMisc, mConnService, mNetd, mDnsResolver, mNMS,
                 NetworkFactory.SerialNumber.NONE);
         nai.everValidated = true;
         return nai;