Merge "Add smoke test for jni library" into qt-dev
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 2906710..0e10de8 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -510,7 +510,7 @@
      * The absence of a connection type.
      * @hide
      */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
     public static final int TYPE_NONE        = -1;
 
     /**
@@ -627,7 +627,7 @@
      * {@hide}
      */
     @Deprecated
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
     public static final int TYPE_MOBILE_FOTA = 10;
 
     /**
@@ -645,7 +645,7 @@
      * {@hide}
      */
     @Deprecated
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
     public static final int TYPE_MOBILE_CBS  = 12;
 
     /**
@@ -655,7 +655,7 @@
      * {@hide}
      */
     @Deprecated
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
     public static final int TYPE_WIFI_P2P    = 13;
 
     /**
@@ -674,7 +674,7 @@
      * {@hide}
      */
     @Deprecated
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
     public static final int TYPE_MOBILE_EMERGENCY = 15;
 
     /**
@@ -775,7 +775,7 @@
      */
     public static final String PRIVATE_DNS_DEFAULT_MODE_FALLBACK = PRIVATE_DNS_MODE_OPPORTUNISTIC;
 
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
     private final IConnectivityManager mService;
     /**
      * A kludge to facilitate static access where a Context pointer isn't available, like in the
@@ -867,7 +867,7 @@
      * {@hide}
      */
     @Deprecated
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
     public static boolean isNetworkTypeMobile(int networkType) {
         switch (networkType) {
             case TYPE_MOBILE:
@@ -1304,7 +1304,7 @@
      */
     @Deprecated
     @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
     public LinkProperties getLinkProperties(int networkType) {
         try {
             return mService.getLinkPropertiesForType(networkType);
@@ -3042,7 +3042,7 @@
      */
     @Deprecated
     @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
     public boolean isNetworkSupported(int networkType) {
         try {
             return mService.isNetworkSupported(networkType);
diff --git a/core/java/android/net/DnsResolver.java b/core/java/android/net/DnsResolver.java
index 06c32c6..68826cb 100644
--- a/core/java/android/net/DnsResolver.java
+++ b/core/java/android/net/DnsResolver.java
@@ -93,6 +93,23 @@
     public static final int FLAG_NO_CACHE_STORE = 1 << 1;
     public static final int FLAG_NO_CACHE_LOOKUP = 1 << 2;
 
+    @IntDef(prefix = { "ERROR_" }, value = {
+            ERROR_PARSE,
+            ERROR_SYSTEM
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface DnsError {}
+    /**
+     * Indicates that there was an error parsing the response the query.
+     * The cause of this error is available via getCause() and is a ParseException.
+     */
+    public static final int ERROR_PARSE = 0;
+    /**
+     * Indicates that there was an error sending the query.
+     * The cause of this error is available via getCause() and is an ErrnoException.
+     */
+    public static final int ERROR_SYSTEM = 1;
+
     private static final int NETID_UNSET = 0;
 
     private static final DnsResolver sInstance = new DnsResolver();
@@ -107,97 +124,57 @@
     private DnsResolver() {}
 
     /**
-     * Answer parser for parsing raw answers
+     * Base interface for answer callbacks
      *
-     * @param <T> The type of the parsed answer
+     * @param <T> The type of the answer
      */
-    public interface AnswerParser<T> {
-        /**
-         * Creates a <T> answer by parsing the given raw answer.
-         *
-         * @param rawAnswer the raw answer to be parsed
-         * @return a parsed <T> answer
-         * @throws ParseException if parsing failed
-         */
-        @NonNull T parse(@NonNull byte[] rawAnswer) throws ParseException;
-    }
-
-    /**
-     * Base class for answer callbacks
-     *
-     * @param <T> The type of the parsed answer
-     */
-    public abstract static class AnswerCallback<T> {
-        /** @hide */
-        public final AnswerParser<T> parser;
-
-        public AnswerCallback(@NonNull AnswerParser<T> parser) {
-            this.parser = parser;
-        };
-
+    public interface Callback<T> {
         /**
          * Success response to
-         * {@link android.net.DnsResolver#query query()}.
+         * {@link android.net.DnsResolver#query query()} or
+         * {@link android.net.DnsResolver#rawQuery rawQuery()}.
          *
          * Invoked when the answer to a query was successfully parsed.
          *
-         * @param answer parsed answer to the query.
+         * @param answer <T> answer to the query.
+         * @param rcode The response code in the DNS response.
          *
          * {@see android.net.DnsResolver#query query()}
          */
-        public abstract void onAnswer(@NonNull T answer);
-
+        void onAnswer(@NonNull T answer, int rcode);
         /**
          * Error response to
-         * {@link android.net.DnsResolver#query query()}.
+         * {@link android.net.DnsResolver#query query()} or
+         * {@link android.net.DnsResolver#rawQuery rawQuery()}.
          *
          * Invoked when there is no valid answer to
          * {@link android.net.DnsResolver#query query()}
+         * {@link android.net.DnsResolver#rawQuery rawQuery()}.
          *
-         * @param exception a {@link ParseException} object with additional
+         * @param error a {@link DnsException} object with additional
          *    detail regarding the failure
          */
-        public abstract void onParseException(@NonNull ParseException exception);
-
-        /**
-         * Error response to
-         * {@link android.net.DnsResolver#query query()}.
-         *
-         * Invoked if an error happens when
-         * issuing the DNS query or receiving the result.
-         * {@link android.net.DnsResolver#query query()}
-         *
-         * @param exception an {@link ErrnoException} object with additional detail
-         *    regarding the failure
-         */
-        public abstract void onQueryException(@NonNull ErrnoException exception);
+        void onError(@NonNull DnsException error);
     }
 
     /**
-     * Callback for receiving raw answers
+     * Class to represent DNS error
      */
-    public abstract static class RawAnswerCallback extends AnswerCallback<byte[]> {
-        public RawAnswerCallback() {
-            super(rawAnswer -> rawAnswer);
-        }
-    }
+    public static class DnsException extends Exception {
+       /**
+        * DNS error code as one of the ERROR_* constants
+        */
+        @DnsError public final int code;
 
-    /**
-     * Callback for receiving parsed {@link InetAddress} answers
-     *
-     * Note that if the answer does not contain any IP addresses,
-     * onAnswer will be called with an empty list.
-     */
-    public abstract static class InetAddressAnswerCallback
-            extends AnswerCallback<List<InetAddress>> {
-        public InetAddressAnswerCallback() {
-            super(rawAnswer -> new DnsAddressAnswer(rawAnswer).getAddresses());
+        DnsException(@DnsError int code, @Nullable Throwable cause) {
+            super(cause);
+            this.code = code;
         }
     }
 
     /**
      * Send a raw DNS query.
-     * The answer will be provided asynchronously through the provided {@link AnswerCallback}.
+     * The answer will be provided asynchronously through the provided {@link Callback}.
      *
      * @param network {@link Network} specifying which network to query on.
      *         {@code null} for query on default network.
@@ -206,13 +183,13 @@
      * @param executor The {@link Executor} that the callback should be executed on.
      * @param cancellationSignal used by the caller to signal if the query should be
      *    cancelled. May be {@code null}.
-     * @param callback an {@link AnswerCallback} which will be called to notify the caller
+     * @param callback a {@link Callback} which will be called to notify the caller
      *    of the result of dns query.
      */
-    public <T> void query(@Nullable Network network, @NonNull byte[] query, @QueryFlag int flags,
+    public void rawQuery(@Nullable Network network, @NonNull byte[] query, @QueryFlag int flags,
             @NonNull @CallbackExecutor Executor executor,
             @Nullable CancellationSignal cancellationSignal,
-            @NonNull AnswerCallback<T> callback) {
+            @NonNull Callback<? super byte[]> callback) {
         if (cancellationSignal != null && cancellationSignal.isCanceled()) {
             return;
         }
@@ -220,11 +197,9 @@
         final FileDescriptor queryfd;
         try {
             queryfd = resNetworkSend((network != null
-                ? network.netId : NETID_UNSET), query, query.length, flags);
+                ? network.getNetIdForResolv() : NETID_UNSET), query, query.length, flags);
         } catch (ErrnoException e) {
-            executor.execute(() -> {
-                callback.onQueryException(e);
-            });
+            executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
             return;
         }
 
@@ -237,7 +212,7 @@
 
     /**
      * Send a DNS query with the specified name, class and query type.
-     * The answer will be provided asynchronously through the provided {@link AnswerCallback}.
+     * The answer will be provided asynchronously through the provided {@link Callback}.
      *
      * @param network {@link Network} specifying which network to query on.
      *         {@code null} for query on default network.
@@ -248,14 +223,14 @@
      * @param executor The {@link Executor} that the callback should be executed on.
      * @param cancellationSignal used by the caller to signal if the query should be
      *    cancelled. May be {@code null}.
-     * @param callback an {@link AnswerCallback} which will be called to notify the caller
+     * @param callback a {@link Callback} which will be called to notify the caller
      *    of the result of dns query.
      */
-    public <T> void query(@Nullable Network network, @NonNull String domain,
+    public void rawQuery(@Nullable Network network, @NonNull String domain,
             @QueryClass int nsClass, @QueryType int nsType, @QueryFlag int flags,
             @NonNull @CallbackExecutor Executor executor,
             @Nullable CancellationSignal cancellationSignal,
-            @NonNull AnswerCallback<T> callback) {
+            @NonNull Callback<? super byte[]> callback) {
         if (cancellationSignal != null && cancellationSignal.isCanceled()) {
             return;
         }
@@ -263,11 +238,9 @@
         final FileDescriptor queryfd;
         try {
             queryfd = resNetworkQuery((network != null
-                    ? network.netId : NETID_UNSET), domain, nsClass, nsType, flags);
+                    ? network.getNetIdForResolv() : NETID_UNSET), domain, nsClass, nsType, flags);
         } catch (ErrnoException e) {
-            executor.execute(() -> {
-                callback.onQueryException(e);
-            });
+            executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
             return;
         }
         synchronized (lock)  {
@@ -277,27 +250,28 @@
         }
     }
 
-    private class InetAddressAnswerAccumulator extends InetAddressAnswerCallback {
+    private class InetAddressAnswerAccumulator implements Callback<byte[]> {
         private final List<InetAddress> mAllAnswers;
-        private ParseException mParseException;
-        private ErrnoException mErrnoException;
-        private final InetAddressAnswerCallback mUserCallback;
+        private int mRcode;
+        private DnsException mDnsException;
+        private final Callback<? super List<InetAddress>> mUserCallback;
         private final int mTargetAnswerCount;
         private int mReceivedAnswerCount = 0;
 
-        InetAddressAnswerAccumulator(int size, @NonNull InetAddressAnswerCallback callback) {
+        InetAddressAnswerAccumulator(int size,
+                @NonNull Callback<? super List<InetAddress>> callback) {
             mTargetAnswerCount = size;
             mAllAnswers = new ArrayList<>();
             mUserCallback = callback;
         }
 
-        private boolean maybeReportException() {
-            if (mErrnoException != null) {
-                mUserCallback.onQueryException(mErrnoException);
+        private boolean maybeReportError() {
+            if (mRcode != 0) {
+                mUserCallback.onAnswer(mAllAnswers, mRcode);
                 return true;
             }
-            if (mParseException != null) {
-                mUserCallback.onParseException(mParseException);
+            if (mDnsException != null) {
+                mUserCallback.onError(mDnsException);
                 return true;
             }
             return false;
@@ -305,34 +279,43 @@
 
         private void maybeReportAnswer() {
             if (++mReceivedAnswerCount != mTargetAnswerCount) return;
-            if (mAllAnswers.isEmpty() && maybeReportException()) return;
+            if (mAllAnswers.isEmpty() && maybeReportError()) return;
             // TODO: Do RFC6724 sort.
-            mUserCallback.onAnswer(mAllAnswers);
+            mUserCallback.onAnswer(mAllAnswers, mRcode);
         }
 
         @Override
-        public void onAnswer(@NonNull List<InetAddress> answer) {
-            mAllAnswers.addAll(answer);
+        public void onAnswer(@NonNull byte[] answer, int rcode) {
+            // If at least one query succeeded, return an rcode of 0.
+            // Otherwise, arbitrarily return the first rcode received.
+            if (mReceivedAnswerCount == 0 || rcode == 0) {
+                mRcode = rcode;
+            }
+            try {
+                mAllAnswers.addAll(new DnsAddressAnswer(answer).getAddresses());
+            } catch (ParseException e) {
+                mDnsException = new DnsException(ERROR_PARSE, e);
+            }
             maybeReportAnswer();
         }
 
         @Override
-        public void onParseException(@NonNull ParseException e) {
-            mParseException = e;
-            maybeReportAnswer();
-        }
-
-        @Override
-        public void onQueryException(@NonNull ErrnoException e) {
-            mErrnoException = e;
+        public void onError(@NonNull DnsException error) {
+            mDnsException = error;
             maybeReportAnswer();
         }
     }
 
     /**
-     * Send a DNS query with the specified name, get back a set of InetAddresses asynchronously.
-     * The answer will be provided asynchronously through the provided
-     * {@link InetAddressAnswerCallback}.
+     * Send a DNS query with the specified name on a network with both IPv4 and IPv6,
+     * get back a set of InetAddresses asynchronously.
+     *
+     * This method will examine the connection ability on given network, and query IPv4
+     * and IPv6 if connection is available.
+     *
+     * If at least one query succeeded with valid answer, rcode will be 0
+     *
+     * The answer will be provided asynchronously through the provided {@link Callback}.
      *
      * @param network {@link Network} specifying which network to query on.
      *         {@code null} for query on default network.
@@ -341,13 +324,13 @@
      * @param executor The {@link Executor} that the callback should be executed on.
      * @param cancellationSignal used by the caller to signal if the query should be
      *    cancelled. May be {@code null}.
-     * @param callback an {@link InetAddressAnswerCallback} which will be called to notify the
+     * @param callback a {@link Callback} which will be called to notify the
      *    caller of the result of dns query.
      */
     public void query(@Nullable Network network, @NonNull String domain, @QueryFlag int flags,
             @NonNull @CallbackExecutor Executor executor,
             @Nullable CancellationSignal cancellationSignal,
-            @NonNull InetAddressAnswerCallback callback) {
+            @NonNull Callback<? super List<InetAddress>> callback) {
         if (cancellationSignal != null && cancellationSignal.isCanceled()) {
             return;
         }
@@ -363,11 +346,10 @@
         if (queryIpv6) {
             try {
                 v6fd = resNetworkQuery((network != null
-                        ? network.netId : NETID_UNSET), domain, CLASS_IN, TYPE_AAAA, flags);
+                        ? network.getNetIdForResolv() : NETID_UNSET),
+                        domain, CLASS_IN, TYPE_AAAA, flags);
             } catch (ErrnoException e) {
-                executor.execute(() -> {
-                    callback.onQueryException(e);
-                });
+                executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
                 return;
             }
             queryCount++;
@@ -377,17 +359,18 @@
         // Avoiding gateways drop packets if queries are sent too close together
         try {
             Thread.sleep(SLEEP_TIME_MS);
-        } catch (InterruptedException ex) { }
+        } catch (InterruptedException ex) {
+            Thread.currentThread().interrupt();
+        }
 
         if (queryIpv4) {
             try {
                 v4fd = resNetworkQuery((network != null
-                        ? network.netId : NETID_UNSET), domain, CLASS_IN, TYPE_A, flags);
+                        ? network.getNetIdForResolv() : NETID_UNSET),
+                        domain, CLASS_IN, TYPE_A, flags);
             } catch (ErrnoException e) {
                 if (queryIpv6) resNetworkCancel(v6fd);  // Closes fd, marks it invalid.
-                executor.execute(() -> {
-                    callback.onQueryException(e);
-                });
+                executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
                 return;
             }
             queryCount++;
@@ -413,34 +396,89 @@
         }
     }
 
-    private <T> void registerFDListener(@NonNull Executor executor,
-            @NonNull FileDescriptor queryfd, @NonNull AnswerCallback<T> answerCallback,
+    /**
+     * Send a DNS query with the specified name and query type, get back a set of
+     * InetAddresses asynchronously.
+     *
+     * The answer will be provided asynchronously through the provided {@link Callback}.
+     *
+     * @param network {@link Network} specifying which network to query on.
+     *         {@code null} for query on default network.
+     * @param domain domain name to query
+     * @param nsType dns resource record (RR) type as one of the TYPE_* constants
+     * @param flags flags as a combination of the FLAGS_* constants
+     * @param executor The {@link Executor} that the callback should be executed on.
+     * @param cancellationSignal used by the caller to signal if the query should be
+     *    cancelled. May be {@code null}.
+     * @param callback a {@link Callback} which will be called to notify the caller
+     *    of the result of dns query.
+     */
+    public void query(@Nullable Network network, @NonNull String domain,
+            @QueryType int nsType, @QueryFlag int flags,
+            @NonNull @CallbackExecutor Executor executor,
+            @Nullable CancellationSignal cancellationSignal,
+            @NonNull Callback<? super List<InetAddress>> callback) {
+        if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+            return;
+        }
+        final Object lock = new Object();
+        final FileDescriptor queryfd;
+        try {
+            queryfd = resNetworkQuery((network != null
+                    ? network.getNetIdForResolv() : NETID_UNSET), domain, CLASS_IN, nsType, flags);
+        } catch (ErrnoException e) {
+            executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
+            return;
+        }
+        final InetAddressAnswerAccumulator accumulator =
+                new InetAddressAnswerAccumulator(1, callback);
+        synchronized (lock)  {
+            registerFDListener(executor, queryfd, accumulator, cancellationSignal, lock);
+            if (cancellationSignal == null) return;
+            addCancellationSignal(cancellationSignal, queryfd, lock);
+        }
+    }
+
+    /**
+     * Class to retrieve DNS response
+     *
+     * @hide
+     */
+    public static final class DnsResponse {
+        public final @NonNull byte[] answerbuf;
+        public final int rcode;
+        public DnsResponse(@NonNull byte[] answerbuf, int rcode) {
+            this.answerbuf = answerbuf;
+            this.rcode = rcode;
+        }
+    }
+
+    private void registerFDListener(@NonNull Executor executor,
+            @NonNull FileDescriptor queryfd, @NonNull Callback<? super byte[]> answerCallback,
             @Nullable CancellationSignal cancellationSignal, @NonNull Object lock) {
         Looper.getMainLooper().getQueue().addOnFileDescriptorEventListener(
                 queryfd,
                 FD_EVENTS,
                 (fd, events) -> {
                     executor.execute(() -> {
+                        DnsResponse resp = null;
+                        ErrnoException exception = null;
                         synchronized (lock) {
                             if (cancellationSignal != null && cancellationSignal.isCanceled()) {
                                 return;
                             }
-                            byte[] answerbuf = null;
                             try {
-                                answerbuf = resNetworkResult(fd);  // Closes fd, marks it invalid.
+                                resp = resNetworkResult(fd);  // Closes fd, marks it invalid.
                             } catch (ErrnoException e) {
                                 Log.e(TAG, "resNetworkResult:" + e.toString());
-                                answerCallback.onQueryException(e);
-                                return;
-                            }
-
-                            try {
-                                answerCallback.onAnswer(
-                                        answerCallback.parser.parse(answerbuf));
-                            } catch (ParseException e) {
-                                answerCallback.onParseException(e);
+                                exception = e;
                             }
                         }
+                        if (exception != null) {
+                            answerCallback.onError(new DnsException(ERROR_SYSTEM, exception));
+                            return;
+                        }
+                        answerCallback.onAnswer(resp.answerbuf, resp.rcode);
                     });
                     // Unregister this fd listener
                     return 0;
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index db87c97..c06a132 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -145,9 +145,10 @@
     /**
      * DNS resolver series jni method.
      * Read a result for the query associated with the {@code fd}.
-     * @return a byte array containing blob answer
+     * @return DnsResponse containing blob answer and rcode
      */
-    public static native byte[] resNetworkResult(FileDescriptor fd) throws ErrnoException;
+    public static native DnsResolver.DnsResponse resNetworkResult(FileDescriptor fd)
+            throws ErrnoException;
 
     /**
      * DNS resolver series jni method.
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index dd754f3..28c59db 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -270,7 +270,7 @@
     return jniCreateFileDescriptor(env, fd);
 }
 
-static jbyteArray android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz, jobject javaFd) {
+static jobject android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz, jobject javaFd) {
     int fd = jniGetFDFromFileDescriptor(env, javaFd);
     int rcode;
     std::vector<uint8_t> buf(MAXPACKETSIZE, 0);
@@ -291,7 +291,10 @@
                 reinterpret_cast<jbyte*>(buf.data()));
     }
 
-    return answer;
+    jclass class_DnsResponse = env->FindClass("android/net/DnsResolver$DnsResponse");
+    jmethodID ctor = env->GetMethodID(class_DnsResponse, "<init>", "([BI)V");
+
+    return env->NewObject(class_DnsResponse, ctor, answer, rcode);
 }
 
 static void android_net_utils_resNetworkCancel(JNIEnv *env, jobject thiz, jobject javaFd) {
@@ -354,7 +357,7 @@
     { "setupRaSocket", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_setupRaSocket },
     { "resNetworkSend", "(I[BII)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkSend },
     { "resNetworkQuery", "(ILjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery },
-    { "resNetworkResult", "(Ljava/io/FileDescriptor;)[B", (void*) android_net_utils_resNetworkResult },
+    { "resNetworkResult", "(Ljava/io/FileDescriptor;)Landroid/net/DnsResolver$DnsResponse;", (void*) android_net_utils_resNetworkResult },
     { "resNetworkCancel", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_resNetworkCancel },
 };
 
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index e4c39cc..6c3a169 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -47,6 +47,7 @@
 
 import static com.android.internal.util.Preconditions.checkNotNull;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.BroadcastOptions;
 import android.app.NotificationManager;
@@ -632,7 +633,8 @@
      *    the first network for a given type changes, or if the default network
      *    changes.
      */
-    private class LegacyTypeTracker {
+    @VisibleForTesting
+    static class LegacyTypeTracker {
 
         private static final boolean DBG = true;
         private static final boolean VDBG = false;
@@ -658,10 +660,12 @@
          *  - dump is thread-safe with respect to concurrent add and remove calls.
          */
         private final ArrayList<NetworkAgentInfo> mTypeLists[];
+        @NonNull
+        private final ConnectivityService mService;
 
-        public LegacyTypeTracker() {
-            mTypeLists = (ArrayList<NetworkAgentInfo>[])
-                    new ArrayList[ConnectivityManager.MAX_NETWORK_TYPE + 1];
+        LegacyTypeTracker(@NonNull ConnectivityService service) {
+            mService = service;
+            mTypeLists = new ArrayList[ConnectivityManager.MAX_NETWORK_TYPE + 1];
         }
 
         public void addSupportedType(int type) {
@@ -710,10 +714,10 @@
             }
 
             // Send a broadcast if this is the first network of its type or if it's the default.
-            final boolean isDefaultNetwork = isDefaultNetwork(nai);
+            final boolean isDefaultNetwork = mService.isDefaultNetwork(nai);
             if ((list.size() == 1) || isDefaultNetwork) {
                 maybeLogBroadcast(nai, DetailedState.CONNECTED, type, isDefaultNetwork);
-                sendLegacyNetworkBroadcast(nai, DetailedState.CONNECTED, type);
+                mService.sendLegacyNetworkBroadcast(nai, DetailedState.CONNECTED, type);
             }
         }
 
@@ -735,15 +739,15 @@
 
             if (wasFirstNetwork || wasDefault) {
                 maybeLogBroadcast(nai, state, type, wasDefault);
-                sendLegacyNetworkBroadcast(nai, state, type);
+                mService.sendLegacyNetworkBroadcast(nai, state, type);
             }
 
             if (!list.isEmpty() && wasFirstNetwork) {
                 if (DBG) log("Other network available for type " + type +
                               ", sending connected broadcast");
                 final NetworkAgentInfo replacement = list.get(0);
-                maybeLogBroadcast(replacement, state, type, isDefaultNetwork(replacement));
-                sendLegacyNetworkBroadcast(replacement, state, type);
+                maybeLogBroadcast(replacement, state, type, mService.isDefaultNetwork(replacement));
+                mService.sendLegacyNetworkBroadcast(replacement, state, type);
             }
         }
 
@@ -758,7 +762,7 @@
         // send out another legacy broadcast - currently only used for suspend/unsuspend
         // toggle
         public void update(NetworkAgentInfo nai) {
-            final boolean isDefault = isDefaultNetwork(nai);
+            final boolean isDefault = mService.isDefaultNetwork(nai);
             final DetailedState state = nai.networkInfo.getDetailedState();
             for (int type = 0; type < mTypeLists.length; type++) {
                 final ArrayList<NetworkAgentInfo> list = mTypeLists[type];
@@ -766,7 +770,7 @@
                 final boolean isFirst = contains && (nai == list.get(0));
                 if (isFirst || contains && isDefault) {
                     maybeLogBroadcast(nai, state, type, isDefault);
-                    sendLegacyNetworkBroadcast(nai, state, type);
+                    mService.sendLegacyNetworkBroadcast(nai, state, type);
                 }
             }
         }
@@ -802,7 +806,7 @@
             pw.println();
         }
     }
-    private LegacyTypeTracker mLegacyTypeTracker = new LegacyTypeTracker();
+    private final LegacyTypeTracker mLegacyTypeTracker = new LegacyTypeTracker(this);
 
     /**
      * Helper class which parses out priority arguments and dumps sections according to their
@@ -5357,7 +5361,8 @@
         }
     }
 
-    private boolean isDefaultNetwork(NetworkAgentInfo nai) {
+    @VisibleForTesting
+    protected boolean isDefaultNetwork(NetworkAgentInfo nai) {
         return nai == getDefaultNetwork();
     }
 
@@ -6565,7 +6570,8 @@
         }
     }
 
-    private void sendLegacyNetworkBroadcast(NetworkAgentInfo nai, DetailedState state, int type) {
+    @VisibleForTesting
+    protected void sendLegacyNetworkBroadcast(NetworkAgentInfo nai, DetailedState state, int type) {
         // The NetworkInfo we actually send out has no bearing on the real
         // state of affairs. For example, if the default connection is mobile,
         // and a request for HIPRI has just gone away, we need to pretend that
diff --git a/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java b/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
index 948c690..a1a8e35 100644
--- a/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
+++ b/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
@@ -33,6 +33,7 @@
 import android.util.Pair;
 
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.TrafficStatsConstants;
 
 import libcore.io.IoUtils;
 
@@ -381,7 +382,8 @@
         protected void setupSocket(
                 int sockType, int protocol, long writeTimeout, long readTimeout, int dstPort)
                 throws ErrnoException, IOException {
-            final int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_PROBE);
+            final int oldTag = TrafficStats.getAndSetThreadStatsTag(
+                    TrafficStatsConstants.TAG_SYSTEM_PROBE);
             try {
                 mFileDescriptor = Os.socket(mAddressFamily, sockType, protocol);
             } finally {
diff --git a/tests/net/Android.bp b/tests/net/Android.bp
index 9098f90..689abed 100644
--- a/tests/net/Android.bp
+++ b/tests/net/Android.bp
@@ -63,7 +63,7 @@
 android_test {
     name: "FrameworksNetTests",
     defaults: ["FrameworksNetTests-jni-defaults"],
-    srcs: ["java/**/*.java"],
+    srcs: ["java/**/*.java", "java/**/*.kt"],
     platform_apis: true,
     test_suites: ["device-tests"],
     certificate: "platform",
diff --git a/tests/net/java/com/android/server/LegacyTypeTrackerTest.kt b/tests/net/java/com/android/server/LegacyTypeTrackerTest.kt
new file mode 100644
index 0000000..d983b65
--- /dev/null
+++ b/tests/net/java/com/android/server/LegacyTypeTrackerTest.kt
@@ -0,0 +1,92 @@
+/*
+ * 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 com.android.server
+
+import android.net.ConnectivityManager.TYPE_ETHERNET
+import android.net.ConnectivityManager.TYPE_MOBILE
+import android.net.ConnectivityManager.TYPE_WIFI
+import android.net.ConnectivityManager.TYPE_WIMAX
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.server.ConnectivityService.LegacyTypeTracker
+import com.android.server.connectivity.NetworkAgentInfo
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertSame
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+
+const val UNSUPPORTED_TYPE = TYPE_WIMAX
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class LegacyTypeTrackerTest {
+    private val supportedTypes = arrayOf(TYPE_MOBILE, TYPE_WIFI, TYPE_ETHERNET)
+
+    private val mMockService = mock(ConnectivityService::class.java).apply {
+        doReturn(false).`when`(this).isDefaultNetwork(any())
+    }
+    private val mTracker = LegacyTypeTracker(mMockService).apply {
+        supportedTypes.forEach {
+            addSupportedType(it)
+        }
+    }
+
+    @Test
+    fun testSupportedTypes() {
+        try {
+            mTracker.addSupportedType(supportedTypes[0])
+            fail("Expected IllegalStateException")
+        } catch (expected: IllegalStateException) {}
+        supportedTypes.forEach {
+            assertTrue(mTracker.isTypeSupported(it))
+        }
+        assertFalse(mTracker.isTypeSupported(UNSUPPORTED_TYPE))
+    }
+
+    @Test
+    fun testAddNetwork() {
+        val mobileNai = mock(NetworkAgentInfo::class.java)
+        val wifiNai = mock(NetworkAgentInfo::class.java)
+        mTracker.add(TYPE_MOBILE, mobileNai)
+        mTracker.add(TYPE_WIFI, wifiNai)
+        assertSame(mTracker.getNetworkForType(TYPE_MOBILE), mobileNai)
+        assertSame(mTracker.getNetworkForType(TYPE_WIFI), wifiNai)
+        // Make sure adding a second NAI does not change the results.
+        val secondMobileNai = mock(NetworkAgentInfo::class.java)
+        mTracker.add(TYPE_MOBILE, secondMobileNai)
+        assertSame(mTracker.getNetworkForType(TYPE_MOBILE), mobileNai)
+        assertSame(mTracker.getNetworkForType(TYPE_WIFI), wifiNai)
+        // Make sure removing a network that wasn't added for this type is a no-op.
+        mTracker.remove(TYPE_MOBILE, wifiNai, false /* wasDefault */)
+        assertSame(mTracker.getNetworkForType(TYPE_MOBILE), mobileNai)
+        assertSame(mTracker.getNetworkForType(TYPE_WIFI), wifiNai)
+        // Remove the top network for mobile and make sure the second one becomes the network
+        // of record for this type.
+        mTracker.remove(TYPE_MOBILE, mobileNai, false /* wasDefault */)
+        assertSame(mTracker.getNetworkForType(TYPE_MOBILE), secondMobileNai)
+        assertSame(mTracker.getNetworkForType(TYPE_WIFI), wifiNai)
+        // Make sure adding a network for an unsupported type does not register it.
+        mTracker.add(UNSUPPORTED_TYPE, mobileNai)
+        assertNull(mTracker.getNetworkForType(UNSUPPORTED_TYPE))
+    }
+}