Merge "Fix DevicePolicyManager setTime() behavior / tests"
diff --git a/Android.bp b/Android.bp
index 5f06a63..63c8fb6 100644
--- a/Android.bp
+++ b/Android.bp
@@ -222,7 +222,6 @@
         ":framework-mca-effect-sources",
         ":framework-mca-filterfw-sources",
         ":framework-mca-filterpacks-sources",
-        ":framework-mime-sources",
         ":framework-mms-sources",
         ":framework-opengl-sources",
         ":framework-rs-sources",
@@ -266,7 +265,9 @@
 filegroup {
     name: "framework-all-sources",
     srcs: [
+        ":framework-mime-sources",
         ":framework-non-updatable-sources",
+        ":framework-sdkext-sources",
         ":updatable-media-srcs",
     ],
 }
@@ -365,7 +366,6 @@
 
     static_libs: [
         "framework-internal-utils",
-        "mimemap",
     ],
 
     required: [
@@ -426,6 +426,11 @@
         "libcore-platform-compat-config",
         "services-platform-compat-config",
     ],
+    static_libs: [
+        // If MimeMap ever becomes its own APEX, then this dependency would need to be removed
+        // in favor of an API stubs dependency in java_library "framework" below.
+        "mimemap",
+    ],
     // For backwards compatibility.
     stem: "framework",
 }
@@ -949,7 +954,8 @@
     "--hide CallbackInterface " +
     "--hide MissingPermission --hide BroadcastBehavior " +
     "--hide HiddenSuperclass --hide DeprecationMismatch --hide UnavailableSymbol " +
-    "--hide SdkConstant --hide HiddenTypeParameter --hide Todo --hide Typo "
+    "--hide SdkConstant --hide HiddenTypeParameter --hide Todo --hide Typo " +
+    "--force-convert-to-warning-nullability-annotations +*:-android.*:+android.icu.*:-dalvik.*"
 
 packages_to_document = [
     "android",
@@ -968,6 +974,8 @@
     name: "framework-doc-stubs-default",
     srcs: [
         ":framework-non-updatable-sources",
+        ":framework-mime-sources",
+        ":framework-sdkext-sources",
         "core/java/**/*.logtags",
         "test-base/src/**/*.java",
         ":opt-telephony-srcs",
@@ -1032,6 +1040,7 @@
     name: "metalava-api-stubs-default",
     srcs: [
         ":framework-non-updatable-sources",
+        ":framework-sdkext-sources",
         "core/java/**/*.logtags",
         ":opt-telephony-srcs",
         ":opt-net-voip-srcs",
diff --git a/apex/sdkext/Android.bp b/apex/sdkext/Android.bp
new file mode 100644
index 0000000..60d657d
--- /dev/null
+++ b/apex/sdkext/Android.bp
@@ -0,0 +1,31 @@
+// 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.
+
+apex {
+    name: "com.android.sdkext",
+    manifest: "manifest.json",
+    key: "com.android.sdkext.key",
+    certificate: ":com.android.sdkext.certificate",
+}
+
+apex_key {
+    name: "com.android.sdkext.key",
+    public_key: "com.android.sdkext.avbpubkey",
+    private_key: "com.android.sdkext.pem",
+}
+
+android_app_certificate {
+    name: "com.android.sdkext.certificate",
+    certificate: "com.android.sdkext",
+}
diff --git a/apex/sdkext/OWNERS b/apex/sdkext/OWNERS
new file mode 100644
index 0000000..feb2742
--- /dev/null
+++ b/apex/sdkext/OWNERS
@@ -0,0 +1 @@
+hansson@google.com
diff --git a/apex/sdkext/com.android.sdkext.avbpubkey b/apex/sdkext/com.android.sdkext.avbpubkey
new file mode 100644
index 0000000..8f47741
--- /dev/null
+++ b/apex/sdkext/com.android.sdkext.avbpubkey
Binary files differ
diff --git a/apex/sdkext/com.android.sdkext.pem b/apex/sdkext/com.android.sdkext.pem
new file mode 100644
index 0000000..8164601
--- /dev/null
+++ b/apex/sdkext/com.android.sdkext.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKQIBAAKCAgEAr72pTSavrziDP54AtQZlRclDxJf9HXRZwFRbYx9hWZ4z7ZtO
+pNBDPvPJCiAOVUsILgCQhBUolz2dyLob25Fd0PVp0n9ibIPEQYjTfHjOK40qb57N
+LhEp2ceGiAfsywPSi0TH1PQ6JgbCe/RM4TefI/sj3gYJPka3ksMvylhMIgUVLgME
+kYizhzwHqlLMspB858SioREZdGwcdZrMMIajGFU69Q9ZRDBzhPvxyKhYoObcOtk1
+uVaiE/fNoi3wKGJz2l2vhUuNrQW7MWlVMag+Qes4YACUTk9LZrOVFEJFjWc8xGUi
+ABtfKGs5JgNr/sWnhvifLn8lJuf0/BJmjD+L5QwXYs2cS7gcZJtTM12J94r0Twgw
+wF2lNmIxAE9sYqj5Rh3dIlTPE5vMUECmQEGjIBB/hzT65VxVqSjU/IlS506JTg3p
+IOQtZ15cUzTBpda4jrvqcq6RNVvgBCu2bV5D8Z4z9FUlPyvD+Zq/6lcoJfLtznAs
+G2463hyPAHTGBIcZ5p5bTuGxoAb6ivyqo4b9Qi4yYA6je9HJmuy8T3Mn5JROoeu9
+BH1K54r/mpT4TQPwuKUvRRtBAV2OPHjo+zp0Gd4Y6rxDYxEIdfEae7pQr/QExSPB
+q/QCr9RhixR1mO373LHuja+MxdAxIxugb2HTS61PQo+PbYrhJMcVuxTwJOECAwEA
+AQKCAgAH7ToRrMkH0ji5SdsmTx+KQkW4PFLCXVke/68PjX7KmAQnl3W4oVwnHr/W
+oROEbVn1GTlre7jU+YaAY0SWZrwgjLE1OWGrG1ZizlUbrCdAd6GOX09J4KROml1L
+DXB0x7tbZMLOrCVjSbLD/ITrM6MN8Gnxvbv0/yOQjxU8vzbP4gLOjHxMRCo001RV
+Ll7lPvcjTQ84zJilU6sE8vJ6zdfVZSK/ou2X0cekG+kP7+fvefo8/UcbEPlGhUrV
+IdVPPQGUu90K2hmN0FBdLi8Vik0klAN68Qu/bHwuKbNzsnmIoztucFFUR+fG3u84
+87aPS0L/J3+mjT2Tv6qhJANUGBmrK/h7MkelpKXlRTCITJLX9xP7hfSbJ4f6aLVq
+ZYPPciGxSBbUDgAwvPtOlMDzccg7YsYyiBBO28wh8MN97rePmc0z6nGmjeXhcbCC
+QktG50VYFCyqp5muKgqQmRfRjHFHLWs8GEqgxMeEL3U3HjYfCYr+6E8Sr5OnOBeH
+3buCi1+zgnNYCvbamgY/OJmW7f9h5O31hxmTplc2E1ZuxUGQZthabt1rN3bmNkyf
+KUmPwnIYkDkWBSV5lzyQExfS3/EVvj0EnHhx8faamimNrGo8xCcfnLT3c0WEFVmo
+yIyVRX3EpXJFM2JkeJ21/IEZXTzHSoNxk12CBG8i8lLSflWSMQKCAQEA2ZqVnOuV
+SZfLCUYUUh8Hvhc5zONstfq7ma1Zsttsdaj9t68nLRiBDvLOGnMjDkYZzoSd4fyl
+oy+YqWGBqcqa5kg1NOCH0I46p9d8RcWAfDnB4sqbLgWh70qsvni6igRijmsMDvkA
+U9HeEdPaLCjQ4UXw7GQvN5rRxuRt+OSqV3tV/Pk9JYyYjz7faC8dmbKDrWHHuOvm
+/9y6Xy+L5IgftykNlUeddSCIoMOAadM7BiRjsrHnOYBQ8xBcn0OYafpIswItrgVi
+IrsPJaBFidx8QYK4MVibyka6U0cm28OocDSPtSk/4jrnCEEhLjFUnWwuMXuBGlrd
+W7wP/muoJqb1VwKCAQEAzsAT90kkOCvAcrfGRE3KkUjwWAsQyP8u2+27JIQPqrpW
+GfWAzJXFt80TSp0Zf/Lrq3/SQ9n4AaL4K9dcMoreedoQN9C9JI7zPtZAWNrJVUcV
+dq2gZjBQ78+oK7uQgvFNWxga5D+Nh+Y+9Tp537fc5HIh0Y13PgsxxPk2OnZJTvLX
+HM5H7Aua9ssmqChsrKihuUsDSPozfBz+H7FNHEdKMqVLqJJSK6m0uMxuLovdVfka
+5S7iBMjEGZc46Iz3ckE0pdOiQLooNqfEQqFe5Uou/KZxxKI1NW25rEEBTVyQWt+2
+BNUCfKP7noZ45u5sUY3eJrgI7BrPEDbNS81WYaLchwKCAQA8Q4mHyd6wYO+EA/qA
+u8NDK9+AFMP4qhXme5HJ7Obetwx9IG7zGEQ1xZy6yoQ84cEn5qZq/bNJvFbFIhHs
+2gWIHRtPJ5e1dI5eCVmLYSUyQjSmAIJ1fm3YfY/VuE3BB3HcC11tkBw9GnQr78YO
+UMd4fAw7C4vgFGpgcMbcFUfvrmKkCsqaaZOeqETq75F9DWlWTSwo1HxHA/RBhENz
+6RcPfLkcTJcY5wevrjUUGcHQ86cAyDBHRngkuLVODkRZpU0Y9lN8TFVfVPre6sIX
+ag6nffJRCD8tB+V2RtBGMKunV4ctHt1oY/Oz34W260aJynoIjjG1ANEpJK4xQdNx
+0O9FAoIBAQCz2AGGKemHswdEwveEkuaSWpA3Bekj7lYkmTchHH9EU7JyAkx3qhDD
+QXB2hxGXawf1tsqAmypQwiJ+gGeCz6mW9UkGRF1DX9XX4yc2I5rew2a4RXAxc/Xz
+pP70i8O5I43Wn7FEusOyY2aAis1Y/eb4EQ+56QTAw5wXa3DwidRbCIJ2XDnT6oRy
+CWUnAYMG7ek/9TB2Wq5OWCn2B5S79IdmZsLZb+5qbMT3u1xcwO1Xy8jJc27IGpv6
+ZsDqCTV1/aJ+XQnWpBg28tiV3Sle6pjUzTRJh5AhWcEZRbKMSOiJI/CBY4k2Qq6t
+xuuEdgFjL7T+mTepqehUglcyiPuLEtAhAoIBAQDDQ5pTFOlunfYzm+CIvvImAgy7
+vrEabJYABATytAbXRMMbrKoEdU2ApEDyEW7PgysDnYLAZ+icObnJlBTYvNdlWFly
+XiviGVfpjFWDT9U/gUwFQu2lfEjoiivoGS92USHUy4UMVHiiznnm20VLLkgd3xna
+HUNSDdHIEgzOTmDsKJwMfA9zGckx23JJimPR5ekv6vr6QllYtECs4lTC1gVQAp2f
+5daxHRbkmO6gw1RgQADLkAnYz3aI1jNuHm5VyAZGt/d3JCtZ3Wwwejll8uJ4J09G
+oEtqyY9RVeHK50bLO4lyAXFiE+J6qqXjsGC20cpxeZYW5llMY/dhA6WV4YXV
+-----END RSA PRIVATE KEY-----
diff --git a/apex/sdkext/com.android.sdkext.pk8 b/apex/sdkext/com.android.sdkext.pk8
new file mode 100644
index 0000000..ccc0bf4
--- /dev/null
+++ b/apex/sdkext/com.android.sdkext.pk8
Binary files differ
diff --git a/apex/sdkext/com.android.sdkext.x509.pem b/apex/sdkext/com.android.sdkext.x509.pem
new file mode 100644
index 0000000..45d2ade
--- /dev/null
+++ b/apex/sdkext/com.android.sdkext.x509.pem
@@ -0,0 +1,35 @@
+-----BEGIN CERTIFICATE-----
+MIIGIzCCBAugAwIBAgIUXuDL7QvzQh7S6rihWz2KRvCFVT0wDQYJKoZIhvcNAQEL
+BQAwgZ8xCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH
+DA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYDVQQLDAdBbmRy
+b2lkMRswGQYDVQQDDBJjb20uYW5kcm9pZC5zZGtleHQxIjAgBgkqhkiG9w0BCQEW
+E2FuZHJvaWRAYW5kcm9pZC5jb20wIBcNMTkxMjAyMTQyNDM0WhgPNDc1NzEwMjgx
+NDI0MzRaMIGfMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQG
+A1UEBwwNTW91bnRhaW4gVmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UECwwH
+QW5kcm9pZDEbMBkGA1UEAwwSY29tLmFuZHJvaWQuc2RrZXh0MSIwIAYJKoZIhvcN
+AQkBFhNhbmRyb2lkQGFuZHJvaWQuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
+MIICCgKCAgEAxFvZZ6ES1oqAu1K74/ZxnC3SOhHnLISLBgJEe7DqtdpuNFAwvdVO
+RL/HULhDbjYlOhpU2x3SavDIZZ2lRfiS9Q+M25WftxTRHVjBcpgwbV77TVxPKlAa
+tVN2lUVOY+s4QAVMNIXjC4kCKK/pCQtacH715EtdV47fWdg/Nx4iP/Aord8k3KGI
+9iI2ZOUjaugTRxu5lKRNDrv0bw5rEzyYmDyMud+kR/iS3/5oog57wPE0ffAkZXWE
+p3L2Cejre3ekCizsvVh6EmH6ForKLtL6f0z5Zir1f4R9+YcENspTlJR3pDhg7y3I
+uTQT/iDCtV0l+g2PjGZPEeAQHND3+kDQR7Sno/WC1Nhws6vcu1MdrC+kIh1ewx4y
+8moy/yqb5M98PJDzTSi/AOTB/OiqLXo/T8rjLBmirs9y3fTT6gJ6qXxOWgt8dle9
+7TBfa84Xi8uVY61c+A+YI0nLal7QDPsP3RPv5sJSQ9x9YnweVfD9Q0EOi52sSNu+
+QuN/kcUrMgPgha20VhfH/CkkPDyIp6aZyHHM69MIl+cYEm8vPa5uy3dosuRomT0f
+I4HOBjULDIuj+rIi+Rg3qHvmpuejwZXI/FBNWIhLEUG3ytgksjMaBoYAYflGdrcj
+BQexuF3EO+j4uo7JGjNcaT7wRoCH9gt29VHckDg2qz6VWXrlpmME4UkCAwEAAaNT
+MFEwHQYDVR0OBBYEFISN2nmUHllgPZMZ62U7mU3ZxzlXMB8GA1UdIwQYMBaAFISN
+2nmUHllgPZMZ62U7mU3ZxzlXMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
+BQADggIBAFHIwyBNIVyHXUsDUdcjxfojXQsF/BCL9ehE3pgdkvDfQanaIREWn0nc
+oCFDFkYMRqaXOGC5TKq4OCjXOLsdfODt8HQ3F9J1B0ghQ5tfOdw7xDugNAszqP/Q
+h7kpvqLTycjrqOeZ5KjxEEYtP/KlUmALgOKcTcSH+XhWyxhjF4j24T9F2yJRr3/A
+r1NGU/djH953bHKC8OpJ2teUpDLA4TxVp/EhslH2eVigF80c/w74QPLEWkD9zv/4
+YeRg/R5N83zHs99NtlWMIeHfK6fUbzMyaSZtvm+jK20tkByQb/OQRed+drk25MtL
+68IRvxqri367qRScdpTZbu0ByLO4X8gFdubRUWr+tcO4pZX+DJRVriExbOkU2xhS
+Vtslq23V/hwTuUNm1CXjR70mPS13BTmHrIQDqLoIw/WTQlGh+vxnlAFRIHM3KB2c
+OdzUBu+NcB4aZEd0KKtct600A0DKPr1MQPb5jDq9wEtPSQYwMF0nRFNnXltbrXMd
+4hhwnhKr74fVMUmb+7eQP56XE/Nk4D+npMO54vv1pav+DI2/nxCND9BOLBweY38p
+Tvd2RjesMok0zXuVXiCIu4GEpwo7WkSnv25xrb0Ey2M8QWnGNnCcX7Kv6ip3RdWy
+HiN0G8RJrs/yNEVSDRx8ZhtwTpXVPQxbARbmhNF4/fnolElkmrMP
+-----END CERTIFICATE-----
diff --git a/apex/sdkext/framework/Android.bp b/apex/sdkext/framework/Android.bp
new file mode 100644
index 0000000..bc7857f
--- /dev/null
+++ b/apex/sdkext/framework/Android.bp
@@ -0,0 +1,21 @@
+// 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.
+
+filegroup {
+    name: "framework-sdkext-sources",
+    srcs: [
+        "java/**/*.java",
+    ],
+    path: "java",
+}
diff --git a/apex/sdkext/manifest.json b/apex/sdkext/manifest.json
new file mode 100644
index 0000000..048f5c4
--- /dev/null
+++ b/apex/sdkext/manifest.json
@@ -0,0 +1,4 @@
+{
+  "name": "com.android.sdkext",
+  "version": 1
+}
diff --git a/api/current.txt b/api/current.txt
index bf3b525..ab0c57df 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -28696,6 +28696,7 @@
     method @Nullable public String getInterfaceName();
     method @NonNull public java.util.List<android.net.LinkAddress> getLinkAddresses();
     method public int getMtu();
+    method @Nullable public android.net.IpPrefix getNat64Prefix();
     method @Nullable public String getPrivateDnsServerName();
     method @NonNull public java.util.List<android.net.RouteInfo> getRoutes();
     method public boolean isPrivateDnsActive();
@@ -28706,6 +28707,7 @@
     method public void setInterfaceName(@Nullable String);
     method public void setLinkAddresses(@NonNull java.util.Collection<android.net.LinkAddress>);
     method public void setMtu(int);
+    method public void setNat64Prefix(@Nullable android.net.IpPrefix);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.LinkProperties> CREATOR;
   }
@@ -34211,6 +34213,7 @@
     field public static final int O_MR1 = 27; // 0x1b
     field public static final int P = 28; // 0x1c
     field public static final int Q = 29; // 0x1d
+    field public static final int R = 10000; // 0x2710
   }
 
   public final class Bundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable {
diff --git a/api/system-current.txt b/api/system-current.txt
index 8a4d11c..756b4f1 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4088,6 +4088,7 @@
   public final class IpConfiguration implements android.os.Parcelable {
     ctor public IpConfiguration();
     ctor public IpConfiguration(@NonNull android.net.IpConfiguration);
+    method public int describeContents();
     method @Nullable public android.net.ProxyInfo getHttpProxy();
     method @NonNull public android.net.IpConfiguration.IpAssignment getIpAssignment();
     method @NonNull public android.net.IpConfiguration.ProxySettings getProxySettings();
@@ -4096,6 +4097,7 @@
     method public void setIpAssignment(@NonNull android.net.IpConfiguration.IpAssignment);
     method public void setProxySettings(@NonNull android.net.IpConfiguration.ProxySettings);
     method public void setStaticIpConfiguration(@Nullable android.net.StaticIpConfiguration);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.IpConfiguration> CREATOR;
   }
 
@@ -4148,7 +4150,6 @@
     ctor public LinkProperties(@Nullable android.net.LinkProperties);
     method public boolean addDnsServer(@NonNull java.net.InetAddress);
     method public boolean addLinkAddress(@NonNull android.net.LinkAddress);
-    method @Nullable public android.net.IpPrefix getNat64Prefix();
     method @NonNull public java.util.List<java.net.InetAddress> getPcscfServers();
     method @Nullable public String getTcpBufferSizes();
     method @NonNull public java.util.List<java.net.InetAddress> getValidatedPrivateDnsServers();
@@ -4162,7 +4163,6 @@
     method public boolean removeDnsServer(@NonNull java.net.InetAddress);
     method public boolean removeLinkAddress(@NonNull android.net.LinkAddress);
     method public boolean removeRoute(@NonNull android.net.RouteInfo);
-    method public void setNat64Prefix(@Nullable android.net.IpPrefix);
     method public void setPcscfServers(@NonNull java.util.Collection<java.net.InetAddress>);
     method public void setPrivateDnsServerName(@Nullable String);
     method public void setTcpBufferSizes(@Nullable String);
diff --git a/api/test-current.txt b/api/test-current.txt
index 1fe4274..179f3b8 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1384,7 +1384,6 @@
     ctor public LinkProperties(@Nullable android.net.LinkProperties);
     method public boolean addDnsServer(@NonNull java.net.InetAddress);
     method public boolean addLinkAddress(@NonNull android.net.LinkAddress);
-    method @Nullable public android.net.IpPrefix getNat64Prefix();
     method @NonNull public java.util.List<java.net.InetAddress> getPcscfServers();
     method @Nullable public String getTcpBufferSizes();
     method @NonNull public java.util.List<java.net.InetAddress> getValidatedPrivateDnsServers();
@@ -1398,7 +1397,6 @@
     method public boolean removeDnsServer(@NonNull java.net.InetAddress);
     method public boolean removeLinkAddress(@NonNull android.net.LinkAddress);
     method public boolean removeRoute(@NonNull android.net.RouteInfo);
-    method public void setNat64Prefix(@Nullable android.net.IpPrefix);
     method public void setPcscfServers(@NonNull java.util.Collection<java.net.InetAddress>);
     method public void setPrivateDnsServerName(@Nullable String);
     method public void setTcpBufferSizes(@Nullable String);
diff --git a/core/TEST_MAPPING b/core/TEST_MAPPING
new file mode 100644
index 0000000..fd571c9
--- /dev/null
+++ b/core/TEST_MAPPING
@@ -0,0 +1,24 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksCoreTests",
+      "options": [
+        {
+          "include-filter": "android.view.inputmethod"
+        },
+        {
+          "include-filter": "com.android.internal.inputmethod"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ],
+      "file_patterns": [
+        "core/java/com/android/internal/inputmethod/.*",
+        "core/java/android/view/inputmethod/.*",
+        "core/tests/coretests/src/android/view/inputmethod/.*",
+        "core/tests/coretests/src/com/android/internal/inputmethod/.*"
+      ]
+    }
+  ]
+}
diff --git a/core/java/android/net/IpConfiguration.java b/core/java/android/net/IpConfiguration.java
index 143467b..dddb64d 100644
--- a/core/java/android/net/IpConfiguration.java
+++ b/core/java/android/net/IpConfiguration.java
@@ -191,18 +191,12 @@
                83 * httpProxy.hashCode();
     }
 
-    /**
-     * Implement the Parcelable interface
-     * @hide
-     */
+    /** Implement the Parcelable interface */
     public int describeContents() {
         return 0;
     }
 
-    /**
-     * Implement the Parcelable interface
-     * @hide
-     */
+    /** Implement the Parcelable interface */
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeString(ipAssignment.name());
         dest.writeString(proxySettings.name());
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 0706e75..8e18341 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -762,10 +762,7 @@
      * Returns the NAT64 prefix in use on this link, if any.
      *
      * @return the NAT64 prefix or {@code null}.
-     * @hide
      */
-    @SystemApi
-    @TestApi
     public @Nullable IpPrefix getNat64Prefix() {
         return mNat64Prefix;
     }
@@ -777,10 +774,7 @@
      * 128-bit IPv6 address) are supported or {@code null} for no prefix.
      *
      * @param prefix the NAT64 prefix.
-     * @hide
      */
-    @SystemApi
-    @TestApi
     public void setNat64Prefix(@Nullable IpPrefix prefix) {
         if (prefix != null && prefix.getPrefixLength() != 96) {
             throw new IllegalArgumentException("Only 96-bit prefixes are supported: " + prefix);
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 357c0c9..2e6d2cc 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -502,6 +502,19 @@
     public static final native void restoreCallingWorkSource(long token);
 
     /**
+     * Mark as being built with VINTF-level stability promise. This API should
+     * only ever be invoked by the build system. It means that the interface
+     * represented by this binder is guaranteed to be kept stable for several
+     * years, and the build system also keeps snapshots of these APIs and
+     * invokes the AIDL compiler to make sure that these snapshots are
+     * backwards compatible. Instead of using this API, use an @VintfStability
+     * interface.
+     *
+     * @hide
+     */
+    public final native void markVintfStability();
+
+    /**
      * Flush any Binder commands pending in the current thread to the kernel
      * driver.  This can be
      * useful to call before performing an operation that may block for a long
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index f21956b..1eda4d9 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -992,6 +992,11 @@
          * engaged. It's now time to see if you can dance.</em>
          */
         public static final int Q = 29;
+
+        /**
+         * R.
+         */
+        public static final int R = CUR_DEVELOPMENT;
     }
 
     /** The type of build, like "user" or "eng". */
@@ -1077,7 +1082,7 @@
             return result == 0;
         }
 
-        final String system = SystemProperties.get("ro.build.fingerprint");
+        final String system = SystemProperties.get("ro.system.build.fingerprint");
         final String vendor = SystemProperties.get("ro.vendor.build.fingerprint");
         final String bootimage = SystemProperties.get("ro.bootimage.build.fingerprint");
         final String requiredBootloader = SystemProperties.get("ro.build.expect.bootloader");
@@ -1086,7 +1091,7 @@
         final String currentRadio = SystemProperties.get("gsm.version.baseband");
 
         if (TextUtils.isEmpty(system)) {
-            Slog.e(TAG, "Required ro.build.fingerprint is empty!");
+            Slog.e(TAG, "Required ro.system.build.fingerprint is empty!");
             return false;
         }
 
diff --git a/core/java/android/os/ResultReceiver.aidl b/core/java/android/os/ResultReceiver.aidl
index 28ce6d5..9fd5bc9 100644
--- a/core/java/android/os/ResultReceiver.aidl
+++ b/core/java/android/os/ResultReceiver.aidl
@@ -2,19 +2,19 @@
 **
 ** Copyright 2007, 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 
+** 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 
+**     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 
+** 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.os;
 
-parcelable ResultReceiver;
+@JavaOnlyStableParcelable parcelable ResultReceiver;
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index fe7e508..a236f31 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -30,12 +30,13 @@
 #include <unistd.h>
 
 #include <android-base/stringprintf.h>
-#include <binder/IInterface.h>
-#include <binder/IServiceManager.h>
-#include <binder/IPCThreadState.h>
-#include <binder/Parcel.h>
 #include <binder/BpBinder.h>
+#include <binder/IInterface.h>
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include <binder/Parcel.h>
 #include <binder/ProcessState.h>
+#include <binder/Stability.h>
 #include <cutils/atomic.h>
 #include <log/log.h>
 #include <utils/KeyedVector.h>
@@ -459,6 +460,9 @@
         sp<JavaBBinder> b = mBinder.promote();
         if (b == NULL) {
             b = new JavaBBinder(env, obj);
+            if (mVintf) {
+                ::android::internal::Stability::markVintf(b.get());
+            }
             mBinder = b;
             ALOGV("Creating JavaBinder %p (refs %p) for Object %p, weakCount=%" PRId32 "\n",
                  b.get(), b->getWeakRefs(), obj, b->getWeakRefs()->getWeakCount());
@@ -473,9 +477,18 @@
         return mBinder.promote();
     }
 
+    void markVintf() {
+        mVintf = true;
+    }
+
 private:
     Mutex           mLock;
     wp<JavaBBinder> mBinder;
+
+    // in the future, we might condense this into int32_t stability, or if there
+    // is too much binder state here, we can think about making JavaBBinder an
+    // sp here (avoid recreating it)
+    bool            mVintf = false;
 };
 
 // ----------------------------------------------------------------------------
@@ -962,6 +975,12 @@
     IPCThreadState::self()->restoreCallingWorkSource(token);
 }
 
+static void android_os_Binder_markVintfStability(JNIEnv* env, jobject clazz) {
+    JavaBBinderHolder* jbh =
+        (JavaBBinderHolder*) env->GetLongField(clazz, gBinderOffsets.mObject);
+    jbh->markVintf();
+}
+
 static void android_os_Binder_flushPendingCommands(JNIEnv* env, jobject clazz)
 {
     IPCThreadState::self()->flushCommands();
@@ -1038,6 +1057,7 @@
     // @CriticalNative
     { "clearCallingWorkSource", "()J", (void*)android_os_Binder_clearCallingWorkSource },
     { "restoreCallingWorkSource", "(J)V", (void*)android_os_Binder_restoreCallingWorkSource },
+    { "markVintfStability", "()V", (void*)android_os_Binder_markVintfStability},
     { "flushPendingCommands", "()V", (void*)android_os_Binder_flushPendingCommands },
     { "getNativeBBinderHolder", "()J", (void*)android_os_Binder_getNativeBBinderHolder },
     { "getNativeFinalizer", "()J", (void*)android_os_Binder_getNativeFinalizer },
diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp
index 7b35f4d..7e8721d 100644
--- a/packages/Tethering/Android.bp
+++ b/packages/Tethering/Android.bp
@@ -22,13 +22,12 @@
         ":framework-tethering-shared-srcs",
         ":net-module-utils-srcs",
         ":services-tethering-shared-srcs",
-        ":servicescore-tethering-src",
     ],
     static_libs: [
         "androidx.annotation_annotation",
-        "netd_aidl_interface-java",
+        "netd_aidl_interface-unstable-java",
         "netlink-client",
-        "networkstack-aidl-interfaces-java",
+        "networkstack-aidl-interfaces-unstable-java",
         "android.hardware.tetheroffload.control-V1.0-java",
         "tethering-client",
     ],
@@ -41,20 +40,26 @@
     defaults: ["TetheringAndroidLibraryDefaults"],
 }
 
-cc_library_shared {
+// Due to b/143733063, APK can't access a jni lib that is in APEX (but not in the APK).
+cc_library {
     name: "libtetheroffloadjni",
     srcs: [
         "jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp",
     ],
     shared_libs: [
-        "libnativehelper",
-        "libcutils",
-        "android.hardware.tetheroffload.config@1.0",
+        "libcgrouprc",
+        "libnativehelper_compat_libc++",
+        "libvndksupport",
     ],
     static_libs: [
+        "android.hardware.tetheroffload.config@1.0",
         "liblog",
         "libbase",
+        "libbinderthreadstate",
+        "libcutils",
         "libhidlbase",
+        "libjsoncpp",
+        "libprocessgroup",
         "libutils",
     ],
 
@@ -64,6 +69,8 @@
         "-Wno-unused-parameter",
         "-Wthread-safety",
     ],
+
+    ldflags: ["-Wl,--exclude-libs=ALL,-error-limit=0"],
 }
 
 // Common defaults for compiling the actual APK.
@@ -71,7 +78,12 @@
     name: "TetheringAppDefaults",
     platform_apis: true,
     privileged: true,
+    // Build system doesn't track transitive dependeicies for jni_libs, list all the dependencies
+    // explicitly.
     jni_libs: [
+        "libcgrouprc",
+        "libnativehelper_compat_libc++",
+        "libvndksupport",
         "libtetheroffloadjni",
     ],
     resource_dirs: [
@@ -83,7 +95,16 @@
 }
 
 // Non-updatable tethering running in the system server process for devices not using the module
-// TODO: build in-process tethering APK here.
+android_app {
+    name: "InProcessTethering",
+    defaults: ["TetheringAppDefaults"],
+    static_libs: ["TetheringApiCurrentLib"],
+    certificate: "platform",
+    manifest: "AndroidManifest_InProcess.xml",
+    // InProcessTethering is a replacement for Tethering
+    overrides: ["Tethering"],
+    // TODO: use PlatformNetworkPermissionConfig.
+}
 
 // Updatable tethering packaged as an application
 android_app {
@@ -96,36 +117,3 @@
     // The permission configuration *must* be included to ensure security of the device
     required: ["NetworkPermissionConfig"],
 }
-
-// This group will be removed when tethering migration is done.
-filegroup {
-    name: "tethering-servicescore-srcs",
-    srcs: [
-        "src/com/android/server/connectivity/tethering/EntitlementManager.java",
-        "src/com/android/server/connectivity/tethering/OffloadController.java",
-        "src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java",
-        "src/com/android/server/connectivity/tethering/TetheringConfiguration.java",
-        "src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java",
-    ],
-}
-
-// This group will be removed when tethering migration is done.
-filegroup {
-    name: "tethering-servicesnet-srcs",
-    srcs: [
-        "src/android/net/dhcp/DhcpServerCallbacks.java",
-        "src/android/net/dhcp/DhcpServingParamsParcelExt.java",
-        "src/android/net/ip/IpServer.java",
-        "src/android/net/ip/RouterAdvertisementDaemon.java",
-        "src/android/net/util/InterfaceSet.java",
-        "src/android/net/util/PrefixUtils.java",
-    ],
-}
-
-// This group would be removed when tethering migration is done.
-filegroup {
-    name: "tethering-jni-srcs",
-    srcs: [
-        "jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp",
-    ],
-}
diff --git a/packages/Tethering/AndroidManifest.xml b/packages/Tethering/AndroidManifest.xml
index eb51593..1430ed0 100644
--- a/packages/Tethering/AndroidManifest.xml
+++ b/packages/Tethering/AndroidManifest.xml
@@ -25,5 +25,11 @@
         android:process="com.android.networkstack.process"
         android:extractNativeLibs="false"
         android:persistent="true">
+        <service android:name="com.android.server.connectivity.tethering.TetheringService"
+                 android:permission="android.permission.MAINLINE_NETWORK_STACK">
+            <intent-filter>
+                <action android:name="android.net.ITetheringConnector"/>
+            </intent-filter>
+        </service>
     </application>
 </manifest>
diff --git a/packages/Tethering/AndroidManifest_InProcess.xml b/packages/Tethering/AndroidManifest_InProcess.xml
new file mode 100644
index 0000000..28d405c
--- /dev/null
+++ b/packages/Tethering/AndroidManifest_InProcess.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * 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.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.tethering.inprocess"
+          android:sharedUserId="android.uid.system"
+          android:process="system">
+    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
+    <application>
+        <!-- TODO: Using MAINLINE_NETWORK_STACK instead of NETWORK_STACK when tethering run in the
+                   same process with networkstack -->
+        <service android:name="com.android.server.connectivity.tethering.TetheringService"
+                 android:process="system"
+                 android:permission="android.permission.NETWORK_STACK">
+            <intent-filter>
+                <action android:name="android.net.ITetheringConnector.InProcess"/>
+            </intent-filter>
+        </service>
+    </application>
+</manifest>
diff --git a/packages/Tethering/CleanSpec.mk b/packages/Tethering/CleanSpec.mk
new file mode 100644
index 0000000..70db351
--- /dev/null
+++ b/packages/Tethering/CleanSpec.mk
@@ -0,0 +1,52 @@
+# 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.
+#
+
+# If you don't need to do a full clean build but would like to touch
+# a file or delete some intermediate files, add a clean step to the end
+# of the list.  These steps will only be run once, if they haven't been
+# run before.
+#
+# E.g.:
+#     $(call add-clean-step, touch -c external/sqlite/sqlite3.h)
+#     $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates)
+#
+# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with
+# files that are missing or have been moved.
+#
+# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory.
+# Use $(OUT_DIR) to refer to the "out" directory.
+#
+# If you need to re-do something that's already mentioned, just copy
+# the command and add it to the bottom of the list.  E.g., if a change
+# that you made last week required touching a file and a change you
+# made today requires touching the same file, just copy the old
+# touch step and add it to the end of the list.
+#
+# *****************************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THE BANNER
+# *****************************************************************
+
+# For example:
+#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates)
+#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates)
+#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f)
+#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
+
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/Tethering)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/InProcessTethering)
+
+# ******************************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER
+# ******************************************************************
diff --git a/packages/Tethering/apex/Android.bp b/packages/Tethering/apex/Android.bp
new file mode 100644
index 0000000..bca01ebd
--- /dev/null
+++ b/packages/Tethering/apex/Android.bp
@@ -0,0 +1,35 @@
+//
+// 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.
+//
+
+apex {
+    name: "com.android.tethering.apex",
+    apps: ["Tethering"],
+    manifest: "manifest.json",
+    key: "com.android.tethering.apex.key",
+
+    androidManifest: "AndroidManifest.xml",
+}
+
+apex_key {
+    name: "com.android.tethering.apex.key",
+    public_key: "com.android.tethering.apex.avbpubkey",
+    private_key: "com.android.tethering.apex.pem",
+}
+
+android_app_certificate {
+    name: "com.android.tethering.apex.certificate",
+    certificate: "com.android.tethering.apex",
+}
diff --git a/packages/Tethering/apex/AndroidManifest.xml b/packages/Tethering/apex/AndroidManifest.xml
new file mode 100644
index 0000000..7769b79
--- /dev/null
+++ b/packages/Tethering/apex/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+  package="com.android.tethering.apex">
+  <!-- APEX does not have classes.dex -->
+  <application android:hasCode="false" />
+  <!-- b/145383354: Current minSdk is locked to Q for development cycle, lock it to next version
+                    before ship. -->
+  <uses-sdk
+      android:minSdkVersion="29"
+      android:targetSdkVersion="29"
+  />
+</manifest>
diff --git a/packages/Tethering/apex/com.android.tethering.apex.avbpubkey b/packages/Tethering/apex/com.android.tethering.apex.avbpubkey
new file mode 100644
index 0000000..9c87111
--- /dev/null
+++ b/packages/Tethering/apex/com.android.tethering.apex.avbpubkey
Binary files differ
diff --git a/packages/Tethering/apex/com.android.tethering.apex.pem b/packages/Tethering/apex/com.android.tethering.apex.pem
new file mode 100644
index 0000000..a8cd12e
--- /dev/null
+++ b/packages/Tethering/apex/com.android.tethering.apex.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKQIBAAKCAgEAwloHpMmwszNBEgUVion141BTvF/oJ5g5DlQIYBtmht4tSpc3
+6elWXd+dhMzFxf/RkxSNRsU+dhD11cPKGp9nUYQQGrHEf3xEKwAHJKRMq26TkJ3o
+1TwOO70TaRKKA4ThNiM3VFDX2vy1ijArhZDIBTGVJCUl9HOHiO+ZJG5DKCx3KXbO
+QWz3c+Lbprr1L76dwIsl5kuoAFwgG0J+9BZhHEzIG1lVpGG7RRLxc8eDIxNN/oKT
+gPYBcOxFYqOECKGBBvElf6MxdRv6xG7gooALY2/HDMYUjAJSOosfwzeymugCzMhK
+e+6CSTAaEfUzuVZvMc2qnd1ly7zpLo9x+TOdH5LEVZpSwqmu2n5bqrUnSEAJUvMz
+SSw0YbsLWJZuTiTV7lecSITgqsmwuZyDexDmUkDQChzrTixsQV6S8vsh/FanjWoi
+zBlPneX8Q7/LME3hxHyLbrabxX0zWiyj8iM9h/8Y4mpO/MjEmmavglTAP4J8zrKD
+FBsntCoch9I49IpYBuO6NfKw1h7AUpLf8gARAjFjRxiJVcSgGY/Wt4/pBzJ57T5g
+xPvqxfpPQP0OA2CT8LqqzZIR8jXs8/TquvwLkkY2kRRPXx+azd5oU2A0uonrUY31
+Bc1obfmWPuEMz9bO/i06ETHuWPd4RiUNaB8qEmjYuKJfhv72YNcRwhrAYJECAwEA
+AQKCAgAaQn3b5yCH5fn5zFQPxvpBP35A6ph8mRXEeNg03B7rRCPMe0gjw9JWlrs6
+0Uw7p4gSnmlEUaxR2ZLN0kmBdV5JZlWitbg+HXU8diGA8u4lD6jCloN6JEYsDi0M
+OmQJe6/OV83HB7FStmh1BnMq9dgA06U6IAbT07RRbUY85OUQDYoAQTw3HNkGgHV7
+PrGYROIdvO9fAYPuoIP6Cu8KXee7Iii7gUOQFWBvQdL7+M4gNCCKrevuNc8WCeaK
+IFvbqq67WGPfrhYlo6UrW2vgqPpg8h5r/GuUS0/+9wNQpjrssUKHltxxiFV0PBqZ
+qI7XkPUvPoG6GMsDT0AWeW1F5ZJqEGPN67Xek0BCD0cpUli+nHD0yWGVHtkpHU2D
+qUOZdB2COfBuXRdW1LsYNPg8YjTCPsmGhISLTwiTNcZJeTxoK1y0CcVW9d7Af2aD
+lYzCegscQlXkSZiFj9s90Vd3KdD2XKrH/ADxzsOxQJ89ka004efdQa5/MKs9aChG
+/5XrwBEfN4O92OjY7KqXUAwB7CcVzNymOjD6r07LM24zbkRpwwXlkP0wmjsHBXkh
+8p0ISmY9QRdvhBgYmFmoPWZncM0zym9LI8atBs4CijQ7JjuOQ8HgHg+Se2eppWfe
+t8r6TVkDB8JeNAMxjX9q0G7icf3JjlIrgERZfyXLmpduR9NdkQKCAQEA5rp2fSKh
+RwihHNtJhNktFJuLR9OA++vyfjqhWnB8CrLPo3//LGWW/+WBr8EwXi/76hQpeKlf
+u8SmkTtxIHlTP2Brh2koh1Qf8HKzPHGjZeDFOoVPKHPqe3nV+cv3srd1mS0Eq3BA
+ZFQq+l61f2iiTZKxDroCahNEa8VMzirW6nKb5xhyMPHXgncCUdphHbwAGatas6be
+RUFg4ChH8BwX6jYw7leRUy2K6OqEl0fckT4Laitlb/ezKtwmD4PPE95q5hH0v3SO
+wetHWafiNrOXPn2wQqBrI2y+AfbTjNmQiaIPgcFKAQ7V3n+c3XfGZ9Xfv4L8m/wo
+RZ4ika1zur021QKCAQEA16OUBPA7BnWd+RJFri2kJBG5JZElaV9chO2ZHcXUbFR9
+HIPkWN19bJbki8Ca0w8FUQuS/M7JeeFjoZ194NlczbR899GVmb0X2AUKXilMacs3
+IONxIDczx3KFtsge8ewXRAjQvgE7M3NpmmJfPLPog7spMCbUIxbc3jzjiZgB/J1s
+WytlUTUY/Zy4V1wujkoydgK2KcHcEWG2oIy7EP0RwnL1NhTksXOtBH6+MoRMAT+H
+fcBK6yfJBNBRQzJ0PdkCCLdQPN1VtwRlWjPXZ3ey4fWvZ399wSLUkM2V1jB4GcOZ
++DAgtwFKs9+HfOdV42GgFWFcjP+bkM3bcdrQFnmYzQKCAQAQnf1KpePXqddwrJpu
+5vVINquhUKpJeoTMcoyMZu2IF7i8nctS9z4Yz/63GcLSBcKu6STTe99ZNqCIdS+A
+lzxXpCoaZoh0tqpWNuyRvd12yOlrfY5l63NH0U6H3xjH1k6x6XwcnMkGcMlnnsqT
+koWd8KKv3NWvrhOPb3ZIou03lWmFC02uGLzcuJWCL6gu7AtVzfGKXspDUqIXgs8r
+i9ptE9oSUFw3EWCfxcQm4RYRn9ZSny1/EufkflZ/Z47Sb4Jjb4ehAlQFw1wwKNcx
++V07MvIu2j7dHkfQ/GXgDwtJ3lIfljwuN1NP4wD5Mlcnw0+KC3UGBvMfkHQM6eEb
+4eTBAoIBAQDWfZsqHlpX3n431XkB+9wdFJP5ThrMaVJ51mxLNRBKgO/BgV+NFSNA
+9AZ5DCf0cCh1qPGYDYhSd2LGywT+trac1j7Hse0AcxpYgQsDBkk/oic/y3wm80HJ
+zZw7Z2uAb7nkrnATzt24G8CbE+ZvVvScs3oQr06raH5hgGdD4bN4No4lUVECKbKl
+8VFbdBHK7vqqb6AKgQ4JLAygPduE1nTn2bkXBklESS98HSXK0dVYGH0JFFBw/63v
+39Y05ObC7iwbx1tEb1RnKzQ1OQO1o1aHc/35ENNhXOfa8ONtneCYn/ty50xjPCG2
+MU1vbBv+hIjbO3D3vvhaXKk+4svAz0qxAoIBAQC84FJEjKHJHx17jLeoTuDfuxwX
+6bOQrI3nHbtnFRvPrMryWRDtHLv89Zma3o68/n4vTn5+AnvgYMZifOYlTlIPxinH
+tlE+qCD8KBXUlZdrc+5GGM18lp5tF3Ro4LireH+OhiOAWawaSzDIDYdiR6Kz9NU+
+SjcHKjDObeM6iMEukoaRsufMedpUSrnbzMraAJgBZGay1NZs/o8Icl3OySYPZWEK
+MJxVBMXU9QcUp2GEioYd/eNuP9rwyjq/EIUDJbP2vESAe6+FdGbIgvyYTV/gnKaH
+GcvyMNVZbCMp/wCYNonjlu+18m2w+pVs2uuZLqORkrKYhisK83TKxh4YOWJh
+-----END RSA PRIVATE KEY-----
diff --git a/packages/Tethering/apex/com.android.tethering.apex.pk8 b/packages/Tethering/apex/com.android.tethering.apex.pk8
new file mode 100644
index 0000000..5663246
--- /dev/null
+++ b/packages/Tethering/apex/com.android.tethering.apex.pk8
Binary files differ
diff --git a/packages/Tethering/apex/com.android.tethering.apex.x509.pem b/packages/Tethering/apex/com.android.tethering.apex.x509.pem
new file mode 100644
index 0000000..a5e9401
--- /dev/null
+++ b/packages/Tethering/apex/com.android.tethering.apex.x509.pem
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIGMzCCBBugAwIBAgIUXVtoDaXanhs7ma8VIICambMkj5UwDQYJKoZIhvcNAQEL
+BQAwgacxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH
+DA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYDVQQLDAdBbmRy
+b2lkMSMwIQYDVQQDDBpjb20uYW5kcm9pZC50ZXRoZXJpbmcuYXBleDEiMCAGCSqG
+SIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAgFw0xOTExMjgwNjU4MTRaGA80
+NzU3MTAyNDA2NTgxNFowgacxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9y
+bmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAw
+DgYDVQQLDAdBbmRyb2lkMSMwIQYDVQQDDBpjb20uYW5kcm9pZC50ZXRoZXJpbmcu
+YXBleDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCAiIwDQYJ
+KoZIhvcNAQEBBQADggIPADCCAgoCggIBANwzufMBdOj9XlNwiX+bXl/94G0DklWW
+nzob0jPlubCFfRqYkjCf2eOd28Mu/O1pOBcvobnrs9OTpGzcHkz2h58L5/0UMVTS
+tBugwCE49XF5FHawqVHNZE+s5tDmnp2cufhNc5HXHY4oZKh80/WVdbcKxiLjSY2T
+PgRAfB6E6XByKD3t1cSsc3liRVKADoJOVDvmF+xnyvSV/SN38bvTQk9aVs95mj0W
+yov6gzXBnqN7iQlvkhcijZBnFWxvoNbJ5KFy1abYOrm+ueXje4BcNhVOeRMb4E9N
+eo7+9k1GEI7TYG7laNNcp7UJ1IXCJzv/wBFKRg3f1HB3unKfx2rtKerDnVsr3o7V
+KProkgRNKNhhQ6opNguiH1YMzKpWMaC988n4AQPryPdIOmVIxIC5jJrixdxgzDXT
+qeiwFiXis291uyls08B03PQFlY9oWaY9P8s+4hIUjB6rLl+XZXsLDtDFxXeJ97NB
+8XZN1gBJoBoLknFs0C4LKpmJZB/EBao9tXV9dL/5lydRo6HzQDpjW8QX06CTUM6z
+Lr3LVelhqbsuZsV42yBKl+/LfrvNjBLEPdSevt2oMrlJW7m4iSNaMtDtJ2Oy8fA5
+WSIgLWuMbkaFDza3JzwiMzxbtbJHYiy6rY7aVywo3Vqwr1+KO3cq4eLQq62zUjRY
+e6KJwvgE2YmpAgMBAAGjUzBRMB0GA1UdDgQWBBQ8h1oF5JfKFmJCN8nfimbUK+IR
+wjAfBgNVHSMEGDAWgBQ8h1oF5JfKFmJCN8nfimbUK+IRwjAPBgNVHRMBAf8EBTAD
+AQH/MA0GCSqGSIb3DQEBCwUAA4ICAQAP5hIkAxSyt9hRafMKiFlmXcL277bgoxNd
+qGZYbdcCFjfvM2r0QQcM/K7x2ZslFe7mJSzcyMifpm4YQTEo2yYbzKItXQV+eV1K
+9RNksRGFG9umsdWaSHhfQmcmxtr2nu9rGAgxy5OQFtyXmJPUPEM2cb/YeILwYhuQ
+Ux3kaj/fxGltX1JBag7HnMzCTZK++fRo5nqFVOJQgJH8ZpuzGeM9kZvP1+b55046
+PhSnlqmZoKhG4i5POPvvZvaakh/lM3x/N7lIlSaQpCGf7jmldni4L0/GenULVKzH
+iN73aBfh4GEvE0HRcOoH3L7V6kc3WMMLve0chZBHpoVYbzUJEJOUL4yrmwEehqtf
+xm4vlYg3vqtcE3UnU/UGdMb16t77Nz88LlpBY5ierIt0jZMU0M81ppRhr1uiD2Lj
+091sEA0Bxcw/6Q8QNF2eR7SG7Qwipnms+lw6Vcxve+7DdTrdEA0k3XgpdXp8Ya+2
+PAp9SLVp1UHiGq3qD9Jvm34QmlUWAIUTHZs3DSgs1y3K5eyw/cnzTvUUOljc/n2y
+VF0FFZtJ1dVLrzQ80Ik7apEXpBqkgBGV04/L3QYk4C0/sP+1yk6zjeeeAvDtUcHS
+gLtjAfacQl/kwfVQWfrF7VByLcivApC6EUdvT3cURM5DfZRQ4RcKr1D61VYPnNRH
++/NVbMObwQ==
+-----END CERTIFICATE-----
diff --git a/packages/Tethering/apex/manifest.json b/packages/Tethering/apex/manifest.json
new file mode 100644
index 0000000..3fb62f3
--- /dev/null
+++ b/packages/Tethering/apex/manifest.json
@@ -0,0 +1,4 @@
+{
+  "name": "com.android.tethering.apex",
+  "version": 290000000
+}
diff --git a/packages/Tethering/common/TetheringLib/Android.bp b/packages/Tethering/common/TetheringLib/Android.bp
index 5b01b1e..adc5a72 100644
--- a/packages/Tethering/common/TetheringLib/Android.bp
+++ b/packages/Tethering/common/TetheringLib/Android.bp
@@ -18,8 +18,12 @@
 aidl_interface {
     name: "tethering-aidl-interfaces",
     local_include_dir: "src",
+    include_dirs: ["frameworks/base/core/java"], // For framework parcelables.
     srcs: [
+        "src/android/net/ITetherInternalCallback.aidl",
         "src/android/net/ITetheringConnector.aidl",
+        "src/android/net/TetheringConfigurationParcel.aidl",
+        "src/android/net/TetherStatesParcel.aidl",
     ],
     backend: {
         ndk: {
@@ -33,8 +37,15 @@
 
 java_library {
     name: "tethering-client",
-    platform_apis: true,
+    sdk_version: "system_current",
     static_libs: [
         "tethering-aidl-interfaces-java",
     ],
 }
+
+// This is temporary file group which would be removed after TetheringManager is built
+// into tethering-client. Will be done by aosp/1156906.
+filegroup {
+    name: "tethering-manager",
+    srcs: ["src/android/net/TetheringManager.java"],
+}
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/ITetherInternalCallback.aidl b/packages/Tethering/common/TetheringLib/src/android/net/ITetherInternalCallback.aidl
new file mode 100644
index 0000000..abb00e8
--- /dev/null
+++ b/packages/Tethering/common/TetheringLib/src/android/net/ITetherInternalCallback.aidl
@@ -0,0 +1,34 @@
+/*
+ * 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.net.Network;
+import android.net.TetheringConfigurationParcel;
+import android.net.TetherStatesParcel;
+
+/**
+ * Callback class for receiving tethering changed events
+ * @hide
+ */
+oneway interface ITetherInternalCallback
+{
+    void onUpstreamChanged(in Network network);
+    void onConfigurationChanged(in TetheringConfigurationParcel config);
+    void onTetherStatesChanged(in TetherStatesParcel states);
+    void onCallbackCreated(in Network network, in TetheringConfigurationParcel config,
+            in TetherStatesParcel states);
+}
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl b/packages/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl
index 443481e..bfe502f 100644
--- a/packages/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl
+++ b/packages/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl
@@ -15,6 +15,23 @@
  */
 package android.net;
 
+import android.net.ITetherInternalCallback;
+import android.os.ResultReceiver;
+
 /** @hide */
 oneway interface ITetheringConnector {
+    void tether(String iface);
+
+    void untether(String iface);
+
+    void setUsbTethering(boolean enable);
+
+    void startTethering(int type, in ResultReceiver receiver, boolean showProvisioningUi);
+
+    void stopTethering(int type);
+
+    void requestLatestTetheringEntitlementResult(int type, in ResultReceiver receiver,
+            boolean showEntitlementUi);
+
+    void registerTetherInternalCallback(ITetherInternalCallback callback);
 }
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetherStatesParcel.aidl b/packages/Tethering/common/TetheringLib/src/android/net/TetherStatesParcel.aidl
new file mode 100644
index 0000000..3d842b3
--- /dev/null
+++ b/packages/Tethering/common/TetheringLib/src/android/net/TetherStatesParcel.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+/**
+ * Status details for tethering downstream interfaces.
+ * {@hide}
+ */
+parcelable TetherStatesParcel {
+    String[] availableList;
+    String[] tetheredList;
+    String[] localOnlyList;
+    String[] erroredIfaceList;
+    // List of Last error code corresponding to each errored iface in erroredIfaceList. */
+    // TODO: Improve this as b/143122247.
+    int[] lastErrorList;
+}
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringConfigurationParcel.aidl b/packages/Tethering/common/TetheringLib/src/android/net/TetheringConfigurationParcel.aidl
new file mode 100644
index 0000000..89f3813
--- /dev/null
+++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringConfigurationParcel.aidl
@@ -0,0 +1,37 @@
+/*
+ * 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;
+
+/**
+ * Configuration details for tethering.
+ * @hide
+ */
+parcelable TetheringConfigurationParcel {
+    int subId;
+    String[] tetherableUsbRegexs;
+    String[] tetherableWifiRegexs;
+    String[] tetherableBluetoothRegexs;
+    boolean isDunRequired;
+    boolean chooseUpstreamAutomatically;
+    int[] preferredUpstreamIfaceTypes;
+    String[] legacyDhcpRanges;
+    String[] defaultIPv4DNS;
+    boolean enableLegacyDhcpServer;
+    String[] provisioningApp;
+    String provisioningAppNoUi;
+    int provisioningCheckPeriod;
+}
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
new file mode 100644
index 0000000..eb0d443
--- /dev/null
+++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -0,0 +1,513 @@
+/*
+ * 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 static android.Manifest.permission.NETWORK_STACK;
+import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.util.SharedLog;
+import android.os.ConditionVariable;
+import android.os.IBinder;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+import java.util.StringJoiner;
+
+/**
+ * Service used to communicate with the tethering, which is running in a separate module.
+ * @hide
+ */
+public class TetheringManager {
+    private static final String TAG = TetheringManager.class.getSimpleName();
+
+    private static TetheringManager sInstance;
+
+    @Nullable
+    private ITetheringConnector mConnector;
+    private TetherInternalCallback mCallback;
+    private Network mTetherUpstream;
+    private TetheringConfigurationParcel mTetheringConfiguration;
+    private TetherStatesParcel mTetherStatesParcel;
+
+    private final RemoteCallbackList<ITetheringEventCallback> mTetheringEventCallbacks =
+            new RemoteCallbackList<>();
+    @GuardedBy("mLog")
+    private final SharedLog mLog = new SharedLog(TAG);
+
+    private TetheringManager() { }
+
+    /**
+     * Get the TetheringManager singleton instance.
+     */
+    public static synchronized TetheringManager getInstance() {
+        if (sInstance == null) {
+            sInstance = new TetheringManager();
+        }
+        return sInstance;
+    }
+
+    private class TetheringConnection implements
+            ConnectivityModuleConnector.ModuleServiceCallback {
+        @Override
+        public void onModuleServiceConnected(@NonNull IBinder service) {
+            logi("Tethering service connected");
+            registerTetheringService(service);
+        }
+    }
+
+    private void registerTetheringService(@NonNull IBinder service) {
+        final ITetheringConnector connector = ITetheringConnector.Stub.asInterface(service);
+
+        log("Tethering service registered");
+
+        // Currently TetheringManager instance is only used by ConnectivityService and mConnector
+        // only expect to assign once when system server start and bind tethering service.
+        // STOPSHIP: Change mConnector to final before TetheringManager put into boot classpath.
+        mConnector = connector;
+        mCallback = new TetherInternalCallback();
+        try {
+            mConnector.registerTetherInternalCallback(mCallback);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    private class TetherInternalCallback extends ITetherInternalCallback.Stub {
+        private final ConditionVariable mWaitForCallback = new ConditionVariable(false);
+        private static final int EVENT_CALLBACK_TIMEOUT_MS = 60_000;
+
+        @Override
+        public void onUpstreamChanged(Network network) {
+            mTetherUpstream = network;
+            reportUpstreamChanged(network);
+        }
+
+        @Override
+        public void onConfigurationChanged(TetheringConfigurationParcel config) {
+            mTetheringConfiguration = config;
+        }
+
+        @Override
+        public void onTetherStatesChanged(TetherStatesParcel states) {
+            mTetherStatesParcel = states;
+        }
+
+        @Override
+        public void onCallbackCreated(Network network, TetheringConfigurationParcel config,
+                TetherStatesParcel states) {
+            mTetherUpstream = network;
+            mTetheringConfiguration = config;
+            mTetherStatesParcel = states;
+            mWaitForCallback.open();
+        }
+
+        boolean awaitCallbackCreation() {
+            return mWaitForCallback.block(EVENT_CALLBACK_TIMEOUT_MS);
+        }
+    }
+
+    private void reportUpstreamChanged(Network network) {
+        final int length = mTetheringEventCallbacks.beginBroadcast();
+        try {
+            for (int i = 0; i < length; i++) {
+                try {
+                    mTetheringEventCallbacks.getBroadcastItem(i).onUpstreamChanged(network);
+                } catch (RemoteException e) {
+                    // Not really very much to do here.
+                }
+            }
+        } finally {
+            mTetheringEventCallbacks.finishBroadcast();
+        }
+    }
+
+    /**
+     * Start the tethering service. Should be called only once on device startup.
+     *
+     * <p>This method will start the tethering service either in the network stack process,
+     * or inside the system server on devices that do not support the tethering module.
+     *
+     * {@hide}
+     */
+    public void start() {
+        // Using MAINLINE_NETWORK_STACK permission after cutting off the dpendency of system server.
+        ConnectivityModuleConnector.getInstance().startModuleService(
+                ITetheringConnector.class.getName(), NETWORK_STACK,
+                new TetheringConnection());
+        log("Tethering service start requested");
+    }
+
+    /**
+     * Attempt to tether the named interface.  This will setup a dhcp server
+     * on the interface, forward and NAT IP v4 packets and forward DNS requests
+     * to the best active upstream network interface.  Note that if no upstream
+     * IP network interface is available, dhcp will still run and traffic will be
+     * allowed between the tethered devices and this device, though upstream net
+     * access will of course fail until an upstream network interface becomes
+     * active. Note: return value do not have any meaning. It is better to use
+     * #getTetherableIfaces() to ensure corresponding interface is available for
+     * tethering before calling #tether().
+     *
+     * @deprecated The only usages should be in PanService and Wifi P2P which
+     * need direct access.
+     *
+     * {@hide}
+     */
+    @Deprecated
+    public int tether(@NonNull String iface) {
+        try {
+            mConnector.tether(iface);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        return TETHER_ERROR_NO_ERROR;
+    }
+
+    /**
+     * Stop tethering the named interface.
+     *
+     * @deprecated
+     * {@hide}
+     */
+    @Deprecated
+    public int untether(@NonNull String iface) {
+        try {
+            mConnector.untether(iface);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        return TETHER_ERROR_NO_ERROR;
+    }
+
+    /**
+     * Attempt to both alter the mode of USB and Tethering of USB. WARNING: New client should not
+     * use this API anymore. All clients should use #startTethering or #stopTethering which
+     * encapsulate proper entitlement logic. If the API is used and an entitlement check is needed,
+     * downstream USB tethering will be enabled but will not have any upstream.
+     *
+     * @deprecated
+     * {@hide}
+     */
+    @Deprecated
+    public int setUsbTethering(boolean enable) {
+        try {
+            mConnector.setUsbTethering(enable);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        return TETHER_ERROR_NO_ERROR;
+    }
+
+    /**
+     * Starts tethering and runs tether provisioning for the given type if needed. If provisioning
+     * fails, stopTethering will be called automatically.
+     *
+     * {@hide}
+     */
+    // TODO: improve the usage of ResultReceiver, b/145096122
+    public void startTethering(int type, @NonNull ResultReceiver receiver,
+            boolean showProvisioningUi) {
+        try {
+            mConnector.startTethering(type, receiver, showProvisioningUi);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Stops tethering for the given type. Also cancels any provisioning rechecks for that type if
+     * applicable.
+     *
+     * {@hide}
+     */
+    public void stopTethering(int type) {
+        try {
+            mConnector.stopTethering(type);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Request the latest value of the tethering entitlement check.
+     *
+     * Note: Allow privileged apps who have TETHER_PRIVILEGED permission to access. If it turns
+     * out some such apps are observed to abuse this API, change to per-UID limits on this API
+     * if it's really needed.
+     */
+    // TODO: improve the usage of ResultReceiver, b/145096122
+    public void requestLatestTetheringEntitlementResult(int type, @NonNull ResultReceiver receiver,
+            boolean showEntitlementUi) {
+        try {
+            mConnector.requestLatestTetheringEntitlementResult(type, receiver, showEntitlementUi);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Register tethering event callback.
+     *
+     * {@hide}
+     */
+    public void registerTetheringEventCallback(@NonNull ITetheringEventCallback callback) {
+        mTetheringEventCallbacks.register(callback);
+    }
+
+    /**
+     * Unregister tethering event callback.
+     *
+     * {@hide}
+     */
+    public void unregisterTetheringEventCallback(@NonNull ITetheringEventCallback callback) {
+        mTetheringEventCallbacks.unregister(callback);
+    }
+
+    /**
+     * Get a more detailed error code after a Tethering or Untethering
+     * request asynchronously failed.
+     *
+     * {@hide}
+     */
+    public int getLastTetherError(@NonNull String iface) {
+        if (!mCallback.awaitCallbackCreation()) {
+            throw new NullPointerException("callback was not ready yet");
+        }
+        if (mTetherStatesParcel == null) return TETHER_ERROR_NO_ERROR;
+
+        int i = 0;
+        for (String errored : mTetherStatesParcel.erroredIfaceList) {
+            if (iface.equals(errored)) return mTetherStatesParcel.lastErrorList[i];
+
+            i++;
+        }
+        return TETHER_ERROR_NO_ERROR;
+    }
+
+    /**
+     * Get the list of regular expressions that define any tetherable
+     * USB network interfaces.  If USB tethering is not supported by the
+     * device, this list should be empty.
+     *
+     * {@hide}
+     */
+    public @NonNull String[] getTetherableUsbRegexs() {
+        if (!mCallback.awaitCallbackCreation()) {
+            throw new NullPointerException("callback was not ready yet");
+        }
+        return mTetheringConfiguration.tetherableUsbRegexs;
+    }
+
+    /**
+     * Get the list of regular expressions that define any tetherable
+     * Wifi network interfaces.  If Wifi tethering is not supported by the
+     * device, this list should be empty.
+     *
+     * {@hide}
+     */
+    public @NonNull String[] getTetherableWifiRegexs() {
+        if (!mCallback.awaitCallbackCreation()) {
+            throw new NullPointerException("callback was not ready yet");
+        }
+        return mTetheringConfiguration.tetherableWifiRegexs;
+    }
+
+    /**
+     * Get the list of regular expressions that define any tetherable
+     * Bluetooth network interfaces.  If Bluetooth tethering is not supported by the
+     * device, this list should be empty.
+     *
+     * {@hide}
+     */
+    public @NonNull String[] getTetherableBluetoothRegexs() {
+        if (!mCallback.awaitCallbackCreation()) {
+            throw new NullPointerException("callback was not ready yet");
+        }
+        return mTetheringConfiguration.tetherableBluetoothRegexs;
+    }
+
+    /**
+     * Get the set of tetherable, available interfaces.  This list is limited by
+     * device configuration and current interface existence.
+     *
+     * {@hide}
+     */
+    public @NonNull String[] getTetherableIfaces() {
+        if (!mCallback.awaitCallbackCreation()) {
+            throw new NullPointerException("callback was not ready yet");
+        }
+        if (mTetherStatesParcel == null) return new String[0];
+        return mTetherStatesParcel.availableList;
+    }
+
+    /**
+     * Get the set of tethered interfaces.
+     *
+     * {@hide}
+     */
+    public @NonNull String[] getTetheredIfaces() {
+        if (!mCallback.awaitCallbackCreation()) {
+            throw new NullPointerException("callback was not ready yet");
+        }
+        if (mTetherStatesParcel == null) return new String[0];
+        return mTetherStatesParcel.tetheredList;
+    }
+
+    /**
+     * Get the set of interface names which attempted to tether but
+     * failed.
+     *
+     * {@hide}
+     */
+    public @NonNull String[] getTetheringErroredIfaces() {
+        if (!mCallback.awaitCallbackCreation()) {
+            throw new NullPointerException("callback was not ready yet");
+        }
+        if (mTetherStatesParcel == null) return new String[0];
+        return mTetherStatesParcel.erroredIfaceList;
+    }
+
+    /**
+     * Get the set of tethered dhcp ranges.
+     *
+     * @deprecated This API just return the default value which is not used in DhcpServer.
+     * {@hide}
+     */
+    @Deprecated
+    public @NonNull String[] getTetheredDhcpRanges() {
+        if (!mCallback.awaitCallbackCreation()) {
+            throw new NullPointerException("callback was not ready yet");
+        }
+        return mTetheringConfiguration.legacyDhcpRanges;
+    }
+
+    /**
+     * Check if the device allows for tethering.
+     *
+     * {@hide}
+     */
+    public boolean hasTetherableConfiguration() {
+        if (!mCallback.awaitCallbackCreation()) {
+            throw new NullPointerException("callback was not ready yet");
+        }
+        final boolean hasDownstreamConfiguration =
+                (mTetheringConfiguration.tetherableUsbRegexs.length != 0)
+                || (mTetheringConfiguration.tetherableWifiRegexs.length != 0)
+                || (mTetheringConfiguration.tetherableBluetoothRegexs.length != 0);
+        final boolean hasUpstreamConfiguration =
+                (mTetheringConfiguration.preferredUpstreamIfaceTypes.length != 0)
+                || mTetheringConfiguration.chooseUpstreamAutomatically;
+
+        return hasDownstreamConfiguration && hasUpstreamConfiguration;
+    }
+
+    /**
+     * Log a message in the local log.
+     */
+    private void log(@NonNull String message) {
+        synchronized (mLog) {
+            mLog.log(message);
+        }
+    }
+
+    /**
+     * Log a condition that should never happen.
+     */
+    private void logWtf(@NonNull String message, @Nullable Throwable e) {
+        Slog.wtf(TAG, message);
+        synchronized (mLog) {
+            mLog.e(message, e);
+        }
+    }
+
+    /**
+     * Log a ERROR level message in the local and system logs.
+     */
+    private void loge(@NonNull String message, @Nullable Throwable e) {
+        synchronized (mLog) {
+            mLog.e(message, e);
+        }
+    }
+
+    /**
+     * Log a INFO level message in the local and system logs.
+     */
+    private void logi(@NonNull String message) {
+        synchronized (mLog) {
+            mLog.i(message);
+        }
+    }
+
+    /**
+     * Dump TetheringManager logs to the specified {@link PrintWriter}.
+     */
+    public void dump(@NonNull PrintWriter pw) {
+        // dump is thread-safe on SharedLog
+        mLog.dump(null, pw, null);
+
+        pw.print("subId: ");
+        pw.println(mTetheringConfiguration.subId);
+
+        dumpStringArray(pw, "tetherableUsbRegexs",
+                mTetheringConfiguration.tetherableUsbRegexs);
+        dumpStringArray(pw, "tetherableWifiRegexs",
+                mTetheringConfiguration.tetherableWifiRegexs);
+        dumpStringArray(pw, "tetherableBluetoothRegexs",
+                mTetheringConfiguration.tetherableBluetoothRegexs);
+
+        pw.print("isDunRequired: ");
+        pw.println(mTetheringConfiguration.isDunRequired);
+
+        pw.print("chooseUpstreamAutomatically: ");
+        pw.println(mTetheringConfiguration.chooseUpstreamAutomatically);
+
+        dumpStringArray(pw, "legacyDhcpRanges", mTetheringConfiguration.legacyDhcpRanges);
+        dumpStringArray(pw, "defaultIPv4DNS", mTetheringConfiguration.defaultIPv4DNS);
+
+        dumpStringArray(pw, "provisioningApp", mTetheringConfiguration.provisioningApp);
+        pw.print("provisioningAppNoUi: ");
+        pw.println(mTetheringConfiguration.provisioningAppNoUi);
+
+        pw.print("enableLegacyDhcpServer: ");
+        pw.println(mTetheringConfiguration.enableLegacyDhcpServer);
+
+        pw.println();
+    }
+
+    private static void dumpStringArray(@NonNull PrintWriter pw, @NonNull String label,
+            @Nullable String[] values) {
+        pw.print(label);
+        pw.print(": ");
+
+        if (values != null) {
+            final StringJoiner sj = new StringJoiner(", ", "[", "]");
+            for (String value : values) sj.add(value);
+
+            pw.print(sj.toString());
+        } else {
+            pw.print("null");
+        }
+
+        pw.println();
+    }
+}
diff --git a/packages/Tethering/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp b/packages/Tethering/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp
index 3eaf488..663154a 100644
--- a/packages/Tethering/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp
+++ b/packages/Tethering/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp
@@ -145,4 +145,18 @@
             gMethods, NELEM(gMethods));
 }
 
+extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
+    JNIEnv *env;
+    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+        ALOGE("ERROR: GetEnv failed");
+        return JNI_ERR;
+    }
+
+    if (register_android_server_connectivity_tethering_OffloadHardwareInterface(env) < 0) {
+        return JNI_ERR;
+    }
+
+    return JNI_VERSION_1_6;
+}
+
 }; // namespace android
diff --git a/packages/Tethering/src/android/net/util/BaseNetdUnsolicitedEventListener.java b/packages/Tethering/src/android/net/util/BaseNetdUnsolicitedEventListener.java
new file mode 100644
index 0000000..3218c0b
--- /dev/null
+++ b/packages/Tethering/src/android/net/util/BaseNetdUnsolicitedEventListener.java
@@ -0,0 +1,70 @@
+/*
+ * 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.util;
+
+import android.net.INetdUnsolicitedEventListener;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Base {@link INetdUnsolicitedEventListener} that provides no-op implementations which can be
+ * overridden.
+ */
+public class BaseNetdUnsolicitedEventListener extends INetdUnsolicitedEventListener.Stub {
+
+    @Override
+    public void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs,
+            int uid) { }
+
+    @Override
+    public void onQuotaLimitReached(@NonNull String alertName, @NonNull String ifName) { }
+
+    @Override
+    public void onInterfaceDnsServerInfo(@NonNull String ifName, long lifetimeS,
+            @NonNull String[] servers) { }
+
+    @Override
+    public void onInterfaceAddressUpdated(@NonNull String addr, String ifName, int flags,
+            int scope) { }
+
+    @Override
+    public void onInterfaceAddressRemoved(@NonNull String addr, @NonNull String ifName, int flags,
+            int scope) { }
+
+    @Override
+    public void onInterfaceAdded(@NonNull String ifName) { }
+
+    @Override
+    public void onInterfaceRemoved(@NonNull String ifName) { }
+
+    @Override
+    public void onInterfaceChanged(@NonNull String ifName, boolean up) { }
+
+    @Override
+    public void onInterfaceLinkStateChanged(@NonNull String ifName, boolean up) { }
+
+    @Override
+    public void onRouteChanged(boolean updated, @NonNull String route, @NonNull String gateway,
+            @NonNull String ifName) { }
+
+    @Override
+    public void onStrictCleartextDetected(int uid, @NonNull String hex) { }
+
+    @Override
+    public int getInterfaceVersion() {
+        return INetdUnsolicitedEventListener.VERSION;
+    }
+}
diff --git a/services/net/java/android/net/util/VersionedBroadcastListener.java b/packages/Tethering/src/android/net/util/VersionedBroadcastListener.java
similarity index 91%
rename from services/net/java/android/net/util/VersionedBroadcastListener.java
rename to packages/Tethering/src/android/net/util/VersionedBroadcastListener.java
index 107c404..e2804ab 100644
--- a/services/net/java/android/net/util/VersionedBroadcastListener.java
+++ b/packages/Tethering/src/android/net/util/VersionedBroadcastListener.java
@@ -39,10 +39,6 @@
 public class VersionedBroadcastListener {
     private static final boolean DBG = false;
 
-    public interface IntentCallback {
-        public void run(Intent intent);
-    }
-
     private final String mTag;
     private final Context mContext;
     private final Handler mHandler;
@@ -61,6 +57,7 @@
         mGenerationNumber = new AtomicInteger(0);
     }
 
+    /** Start listening to intent broadcast. */
     public void startListening() {
         if (DBG) Log.d(mTag, "startListening");
         if (mReceiver != null) return;
@@ -69,6 +66,7 @@
         mContext.registerReceiver(mReceiver, mFilter, null, mHandler);
     }
 
+    /** Stop listening to intent broadcast. */
     public void stopListening() {
         if (DBG) Log.d(mTag, "stopListening");
         if (mReceiver == null) return;
@@ -85,8 +83,7 @@
         // Used to verify this receiver is still current.
         public final int generationNumber;
 
-        public Receiver(
-                String tag, AtomicInteger atomicGenerationNumber, Consumer<Intent> callback) {
+        Receiver(String tag, AtomicInteger atomicGenerationNumber, Consumer<Intent> callback) {
             this.tag = tag;
             this.atomicGenerationNumber = atomicGenerationNumber;
             this.callback = callback;
@@ -98,8 +95,8 @@
             final int currentGenerationNumber = atomicGenerationNumber.get();
 
             if (DBG) {
-                Log.d(tag, "receiver generationNumber=" + generationNumber +
-                        ", current generationNumber=" + currentGenerationNumber);
+                Log.d(tag, "receiver generationNumber=" + generationNumber
+                        + ", current generationNumber=" + currentGenerationNumber);
             }
             if (generationNumber != currentGenerationNumber) return;
 
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java b/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
index 6b0f1de..ba5d08d 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
@@ -47,6 +47,7 @@
 import android.os.PersistableBundle;
 import android.os.ResultReceiver;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.telephony.CarrierConfigManager;
@@ -55,7 +56,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.StateMachine;
-import com.android.server.connectivity.MockableSystemProperties;
 
 import java.io.PrintWriter;
 
@@ -94,7 +94,6 @@
     private final ArraySet<Integer> mCurrentTethers;
     private final Context mContext;
     private final int mPermissionChangeMessageCode;
-    private final MockableSystemProperties mSystemProperties;
     private final SharedLog mLog;
     private final SparseIntArray mEntitlementCacheValue;
     private final EntitlementHandler mHandler;
@@ -110,12 +109,12 @@
     private TetheringConfigurationFetcher mFetcher;
 
     public EntitlementManager(Context ctx, StateMachine tetherMasterSM, SharedLog log,
-            int permissionChangeMessageCode, MockableSystemProperties systemProperties) {
+            int permissionChangeMessageCode) {
+
         mContext = ctx;
         mLog = log.forSubComponent(TAG);
         mCurrentTethers = new ArraySet<Integer>();
         mCellularPermitted = new SparseIntArray();
-        mSystemProperties = systemProperties;
         mEntitlementCacheValue = new SparseIntArray();
         mTetherMasterSM = tetherMasterSM;
         mPermissionChangeMessageCode = permissionChangeMessageCode;
@@ -287,7 +286,7 @@
      */
     @VisibleForTesting
     protected boolean isTetherProvisioningRequired(final TetheringConfiguration config) {
-        if (mSystemProperties.getBoolean(DISABLE_PROVISIONING_SYSPROP_KEY, false)
+        if (SystemProperties.getBoolean(DISABLE_PROVISIONING_SYSPROP_KEY, false)
                 || config.provisioningApp.length == 0) {
             return false;
         }
@@ -526,8 +525,8 @@
                     handleMaybeRunProvisioning(config);
                     break;
                 case EVENT_GET_ENTITLEMENT_VALUE:
-                    handleGetLatestTetheringEntitlementValue(msg.arg1, (ResultReceiver) msg.obj,
-                            toBool(msg.arg2));
+                    handleRequestLatestTetheringEntitlementValue(msg.arg1,
+                            (ResultReceiver) msg.obj, toBool(msg.arg2));
                     break;
                 default:
                     mLog.log("Unknown event: " + msg.what);
@@ -651,15 +650,15 @@
     }
 
     /** Get the last value of the tethering entitlement check. */
-    public void getLatestTetheringEntitlementResult(int downstream, ResultReceiver receiver,
+    public void requestLatestTetheringEntitlementResult(int downstream, ResultReceiver receiver,
             boolean showEntitlementUi) {
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_GET_ENTITLEMENT_VALUE,
                 downstream, encodeBool(showEntitlementUi), receiver));
 
     }
 
-    private void handleGetLatestTetheringEntitlementValue(int downstream, ResultReceiver receiver,
-            boolean showEntitlementUi) {
+    private void handleRequestLatestTetheringEntitlementValue(int downstream,
+            ResultReceiver receiver, boolean showEntitlementUi) {
         final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration();
         if (!isTetherProvisioningRequired(config)) {
             receiver.send(TETHER_ERROR_NO_ERROR, null);
diff --git a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java b/packages/Tethering/src/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java
similarity index 92%
rename from services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java
rename to packages/Tethering/src/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java
index 8186343..edfe3ca 100644
--- a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java
@@ -82,6 +82,7 @@
         mNextSubnetId = 0;
     }
 
+    /** Add active downstream to ipv6 tethering candidate list. */
     public void addActiveDownstream(IpServer downstream, int mode) {
         if (findDownstream(downstream) == null) {
             // Adding a new downstream appends it to the list. Adding a
@@ -97,6 +98,7 @@
         }
     }
 
+    /** Remove downstream from ipv6 tethering candidate list. */
     public void removeActiveDownstream(IpServer downstream) {
         stopIPv6TetheringOn(downstream);
         if (mActiveDownstreams.remove(findDownstream(downstream))) {
@@ -112,6 +114,11 @@
         }
     }
 
+    /**
+     * Call when upstream NetworkState may be changed.
+     * If upstream has ipv6 for tethering, update this new NetworkState
+     * to IpServer. Otherwise stop ipv6 tethering on downstream interfaces.
+     */
     public void updateUpstreamNetworkState(NetworkState ns) {
         if (VDBG) {
             Log.d(TAG, "updateUpstreamNetworkState: " + toDebugString(ns));
@@ -122,8 +129,8 @@
             return;
         }
 
-        if (mUpstreamNetworkState != null &&
-            !ns.network.equals(mUpstreamNetworkState.network)) {
+        if (mUpstreamNetworkState != null
+                && !ns.network.equals(mUpstreamNetworkState.network)) {
             stopIPv6TetheringOnAllInterfaces();
         }
 
@@ -221,8 +228,8 @@
 
         for (RouteInfo routeInfo : lp.getRoutes()) {
             final IpPrefix destination = routeInfo.getDestination();
-            if ((destination.getAddress() instanceof Inet6Address) &&
-                (destination.getPrefixLength() <= 64)) {
+            if ((destination.getAddress() instanceof Inet6Address)
+                    && (destination.getPrefixLength() <= 64)) {
                 v6only.addRoute(routeInfo);
             }
         }
@@ -242,12 +249,12 @@
     // TODO: Delete this and switch to LinkAddress#isGlobalPreferred once we
     // announce our own IPv6 address as DNS server.
     private static boolean isIPv6GlobalAddress(InetAddress ip) {
-        return (ip instanceof Inet6Address) &&
-               !ip.isAnyLocalAddress() &&
-               !ip.isLoopbackAddress() &&
-               !ip.isLinkLocalAddress() &&
-               !ip.isSiteLocalAddress() &&
-               !ip.isMulticastAddress();
+        return (ip instanceof Inet6Address)
+               && !ip.isAnyLocalAddress()
+               && !ip.isLoopbackAddress()
+               && !ip.isLinkLocalAddress()
+               && !ip.isSiteLocalAddress()
+               && !ip.isMulticastAddress();
     }
 
     private static LinkProperties getUniqueLocalConfig(byte[] ulp, short subnetId) {
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
index 01339a4..00a6773 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
@@ -107,6 +107,8 @@
     public OffloadHardwareInterface(Handler h, SharedLog log) {
         mHandler = h;
         mLog = log.forSubComponent(TAG);
+
+        System.loadLibrary("tetheroffloadjni");
     }
 
     /** Get default value indicating whether offload is supported. */
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
similarity index 89%
rename from services/core/java/com/android/server/connectivity/Tethering.java
rename to packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
index acedc36..f1228129 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.connectivity;
+package com.android.server.connectivity.tethering;
 
 import static android.hardware.usb.UsbManager.USB_CONFIGURED;
 import static android.hardware.usb.UsbManager.USB_CONNECTED;
@@ -47,8 +47,6 @@
 import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
-import static com.android.server.ConnectivityService.SHORT_ARG;
-
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -62,9 +60,10 @@
 import android.content.IntentFilter;
 import android.content.res.Resources;
 import android.hardware.usb.UsbManager;
+import android.net.INetd;
 import android.net.INetworkPolicyManager;
 import android.net.INetworkStatsService;
-import android.net.ITetheringEventCallback;
+import android.net.ITetherInternalCallback;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
@@ -72,7 +71,10 @@
 import android.net.NetworkInfo;
 import android.net.NetworkState;
 import android.net.NetworkUtils;
+import android.net.TetherStatesParcel;
+import android.net.TetheringConfigurationParcel;
 import android.net.ip.IpServer;
+import android.net.util.BaseNetdUnsolicitedEventListener;
 import android.net.util.InterfaceSet;
 import android.net.util.PrefixUtils;
 import android.net.util.SharedLog;
@@ -87,13 +89,10 @@
 import android.os.INetworkManagementService;
 import android.os.Looper;
 import android.os.Message;
-import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.os.UserManagerInternal;
-import android.os.UserManagerInternal.UserRestrictionsListener;
 import android.telephony.PhoneStateListener;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
@@ -110,15 +109,6 @@
 import com.android.internal.util.Protocol;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
-import com.android.server.LocalServices;
-import com.android.server.connectivity.tethering.EntitlementManager;
-import com.android.server.connectivity.tethering.IPv6TetheringCoordinator;
-import com.android.server.connectivity.tethering.OffloadController;
-import com.android.server.connectivity.tethering.TetheringConfiguration;
-import com.android.server.connectivity.tethering.TetheringDependencies;
-import com.android.server.connectivity.tethering.TetheringInterfaceUtils;
-import com.android.server.connectivity.tethering.UpstreamNetworkMonitor;
-import com.android.server.net.BaseNetworkObserver;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -127,32 +117,32 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.Set;
 
 /**
- * @hide
  *
  * This class holds much of the business logic to allow Android devices
  * to act as IP gateways via USB, BT, and WiFi interfaces.
  */
-public class Tethering extends BaseNetworkObserver {
+public class Tethering {
 
-    private final static String TAG = Tethering.class.getSimpleName();
-    private final static boolean DBG = false;
-    private final static boolean VDBG = false;
+    private static final String TAG = Tethering.class.getSimpleName();
+    private static final boolean DBG = false;
+    private static final boolean VDBG = false;
 
-    private static final Class[] messageClasses = {
+    private static final Class[] sMessageClasses = {
             Tethering.class, TetherMasterSM.class, IpServer.class
     };
     private static final SparseArray<String> sMagicDecoderRing =
-            MessageUtils.findMessageNames(messageClasses);
+            MessageUtils.findMessageNames(sMessageClasses);
 
     private static class TetherState {
         public final IpServer ipServer;
         public int lastState;
         public int lastError;
 
-        public TetherState(IpServer ipServer) {
+        TetherState(IpServer ipServer) {
             this.ipServer = ipServer;
             // Assume all state machines start out available and with no errors.
             lastState = IpServer.STATE_AVAILABLE;
@@ -177,6 +167,7 @@
     private final Context mContext;
     private final ArrayMap<String, TetherState> mTetherStates;
     private final BroadcastReceiver mStateReceiver;
+    // Stopship: replace mNMService before production.
     private final INetworkManagementService mNMService;
     private final INetworkStatsService mStatsService;
     private final INetworkPolicyManager mPolicyManager;
@@ -191,10 +182,13 @@
     private final TetheringDependencies mDeps;
     private final EntitlementManager mEntitlementMgr;
     private final Handler mHandler;
-    private final RemoteCallbackList<ITetheringEventCallback> mTetheringEventCallbacks =
-            new RemoteCallbackList<>();
     private final PhoneStateListener mPhoneStateListener;
+    private final INetd mNetd;
+    private final NetdCallback mNetdCallback;
+    private final UserRestrictionActionListener mTetheringRestriction;
     private int mActiveDataSubId = INVALID_SUBSCRIPTION_ID;
+    // All the usage of mTetherInternalCallback should run in the same thread.
+    private ITetherInternalCallback mTetherInternalCallback = null;
 
     private volatile TetheringConfiguration mConfig;
     private InterfaceSet mCurrentUpstreamIfaceSet;
@@ -205,18 +199,17 @@
     // True iff. WiFi tethering should be started when soft AP is ready.
     private boolean mWifiTetherRequested;
     private Network mTetherUpstream;
+    private TetherStatesParcel mTetherStatesParcel;
 
-    public Tethering(Context context, INetworkManagementService nmService,
-            INetworkStatsService statsService, INetworkPolicyManager policyManager,
-            Looper looper, MockableSystemProperties systemProperties,
-            TetheringDependencies deps) {
-        mLog.mark("constructed");
-        mContext = context;
-        mNMService = nmService;
-        mStatsService = statsService;
-        mPolicyManager = policyManager;
-        mLooper = looper;
+    public Tethering(TetheringDependencies deps) {
+        mLog.mark("Tethering.constructed");
         mDeps = deps;
+        mContext = mDeps.getContext();
+        mNMService = mDeps.getINetworkManagementService();
+        mStatsService = mDeps.getINetworkStatsService();
+        mPolicyManager = mDeps.getINetworkPolicyManager();
+        mNetd = mDeps.getINetd(mContext);
+        mLooper = mDeps.getTetheringLooper();
 
         mPublicSync = new Object();
 
@@ -239,7 +232,7 @@
         // EntitlementManager will send EVENT_UPSTREAM_PERMISSION_CHANGED when cellular upstream
         // permission is changed according to entitlement check result.
         mEntitlementMgr = mDeps.getEntitlementManager(mContext, mTetherMasterSM, mLog,
-                TetherMasterSM.EVENT_UPSTREAM_PERMISSION_CHANGED, systemProperties);
+                TetherMasterSM.EVENT_UPSTREAM_PERMISSION_CHANGED);
         mEntitlementMgr.setOnUiEntitlementFailedListener((int downstream) -> {
             mLog.log("OBSERVED UiEnitlementFailed");
             stopTethering(downstream);
@@ -278,10 +271,22 @@
 
         mStateReceiver = new StateReceiver();
 
+        mNetdCallback = new NetdCallback();
+        try {
+            mNetd.registerUnsolicitedEventListener(mNetdCallback);
+        } catch (RemoteException e) {
+            mLog.e("Unable to register netd UnsolicitedEventListener");
+        }
+
+        final UserManager userManager = (UserManager) mContext.getSystemService(
+                    Context.USER_SERVICE);
+        mTetheringRestriction = new UserRestrictionActionListener(userManager, this);
+
         // Load tethering configuration.
         updateConfiguration();
 
         startStateMachineUpdaters(mHandler);
+        startTrackDefaultNetwork();
     }
 
     private void startStateMachineUpdaters(Handler handler) {
@@ -295,6 +300,7 @@
         filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
         filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
         filter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
+        filter.addAction(UserManager.ACTION_USER_RESTRICTIONS_CHANGED);
         mContext.registerReceiver(mStateReceiver, filter, null, handler);
 
         filter = new IntentFilter();
@@ -303,11 +309,6 @@
         filter.addDataScheme("file");
         mContext.registerReceiver(mStateReceiver, filter, null, handler);
 
-        final UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
-        // This check is useful only for some unit tests; example: ConnectivityServiceTest.
-        if (umi != null) {
-            umi.addUserRestrictionsListener(new TetheringUserRestrictionListener(this));
-        }
     }
 
     private WifiManager getWifiManager() {
@@ -318,6 +319,7 @@
     private void updateConfiguration() {
         mConfig = mDeps.generateTetheringConfiguration(mContext, mLog, mActiveDataSubId);
         mUpstreamNetworkMonitor.updateMobileRequiresDun(mConfig.isDunRequired);
+        reportConfigurationChanged(mConfig.toStableParcelable());
     }
 
     private void maybeDunSettingChanged() {
@@ -327,10 +329,31 @@
         updateConfiguration();
     }
 
-    @Override
-    public void interfaceStatusChanged(String iface, boolean up) {
+    private class NetdCallback extends BaseNetdUnsolicitedEventListener {
+        @Override
+        public void onInterfaceChanged(String ifName, boolean up) {
+            mHandler.post(() -> interfaceStatusChanged(ifName, up));
+        }
+
+        @Override
+        public void onInterfaceLinkStateChanged(String ifName, boolean up) {
+            mHandler.post(() -> interfaceLinkStateChanged(ifName, up));
+        }
+
+        @Override
+        public void onInterfaceAdded(String ifName) {
+            mHandler.post(() -> interfaceAdded(ifName));
+        }
+
+        @Override
+        public void onInterfaceRemoved(String ifName) {
+            mHandler.post(() -> interfaceRemoved(ifName));
+        }
+    }
+
+    void interfaceStatusChanged(String iface, boolean up) {
         // Never called directly: only called from interfaceLinkStateChanged.
-        // See NetlinkHandler.cpp:71.
+        // See NetlinkHandler.cpp: notifyInterfaceChanged.
         if (VDBG) Log.d(TAG, "interfaceStatusChanged " + iface + ", " + up);
         synchronized (mPublicSync) {
             if (up) {
@@ -349,8 +372,7 @@
         }
     }
 
-    @Override
-    public void interfaceLinkStateChanged(String iface, boolean up) {
+    void interfaceLinkStateChanged(String iface, boolean up) {
         interfaceStatusChanged(iface, up);
     }
 
@@ -369,28 +391,27 @@
         return TETHERING_INVALID;
     }
 
-    @Override
-    public void interfaceAdded(String iface) {
+    void interfaceAdded(String iface) {
         if (VDBG) Log.d(TAG, "interfaceAdded " + iface);
         synchronized (mPublicSync) {
             maybeTrackNewInterfaceLocked(iface);
         }
     }
 
-    @Override
-    public void interfaceRemoved(String iface) {
+
+    void interfaceRemoved(String iface) {
         if (VDBG) Log.d(TAG, "interfaceRemoved " + iface);
         synchronized (mPublicSync) {
             stopTrackingInterfaceLocked(iface);
         }
     }
 
-    public void startTethering(int type, ResultReceiver receiver, boolean showProvisioningUi) {
+    void startTethering(int type, ResultReceiver receiver, boolean showProvisioningUi) {
         mEntitlementMgr.startProvisioningIfNeeded(type, showProvisioningUi);
         enableTetheringInternal(type, true /* enabled */, receiver);
     }
 
-    public void stopTethering(int type) {
+    void stopTethering(int type) {
         enableTetheringInternal(type, false /* disabled */, null);
         mEntitlementMgr.stopProvisioningIfNeeded(type);
     }
@@ -434,8 +455,8 @@
                     mLog.e("setWifiTethering: failed to get WifiManager!");
                     return TETHER_ERROR_SERVICE_UNAVAIL;
                 }
-                if ((enable && mgr.startSoftAp(null /* use existing wifi config */)) ||
-                    (!enable && mgr.stopSoftAp())) {
+                if ((enable && mgr.startSoftAp(null /* use existing wifi config */))
+                        || (!enable && mgr.stopSoftAp())) {
                     mWifiTetherRequested = enable;
                     return TETHER_ERROR_NO_ERROR;
                 }
@@ -450,8 +471,8 @@
     private void setBluetoothTethering(final boolean enable, final ResultReceiver receiver) {
         final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
         if (adapter == null || !adapter.isEnabled()) {
-            Log.w(TAG, "Tried to enable bluetooth tethering with null or disabled adapter. null: " +
-                    (adapter == null));
+            Log.w(TAG, "Tried to enable bluetooth tethering with null or disabled adapter. null: "
+                    + (adapter == null));
             sendTetherResult(receiver, TETHER_ERROR_SERVICE_UNAVAIL);
             return;
         }
@@ -487,7 +508,7 @@
         }, BluetoothProfile.PAN);
     }
 
-    public int tether(String iface) {
+    int tether(String iface) {
         return tether(iface, IpServer.STATE_TETHERED);
     }
 
@@ -515,7 +536,7 @@
         }
     }
 
-    public int untether(String iface) {
+    int untether(String iface) {
         if (DBG) Log.d(TAG, "Untethering " + iface);
         synchronized (mPublicSync) {
             TetherState tetherState = mTetherStates.get(iface);
@@ -532,19 +553,19 @@
         }
     }
 
-    public void untetherAll() {
+    void untetherAll() {
         stopTethering(TETHERING_WIFI);
         stopTethering(TETHERING_WIFI_P2P);
         stopTethering(TETHERING_USB);
         stopTethering(TETHERING_BLUETOOTH);
     }
 
-    public int getLastTetherError(String iface) {
+    int getLastTetherError(String iface) {
         synchronized (mPublicSync) {
             TetherState tetherState = mTetherStates.get(iface);
             if (tetherState == null) {
-                Log.e(TAG, "Tried to getLastTetherError on an unknown iface :" + iface +
-                        ", ignoring");
+                Log.e(TAG, "Tried to getLastTetherError on an unknown iface :" + iface
+                        + ", ignoring");
                 return TETHER_ERROR_UNKNOWN_IFACE;
             }
             return tetherState.lastError;
@@ -559,12 +580,14 @@
         final ArrayList<String> tetherList = new ArrayList<>();
         final ArrayList<String> localOnlyList = new ArrayList<>();
         final ArrayList<String> erroredList = new ArrayList<>();
+        final ArrayList<Integer> lastErrorList = new ArrayList<>();
 
         boolean wifiTethered = false;
         boolean usbTethered = false;
         boolean bluetoothTethered = false;
 
         final TetheringConfiguration cfg = mConfig;
+        final TetherStatesParcel mTetherStatesParcel = new TetherStatesParcel();
 
         synchronized (mPublicSync) {
             for (int i = 0; i < mTetherStates.size(); i++) {
@@ -572,6 +595,7 @@
                 String iface = mTetherStates.keyAt(i);
                 if (tetherState.lastError != TETHER_ERROR_NO_ERROR) {
                     erroredList.add(iface);
+                    lastErrorList.add(tetherState.lastError);
                 } else if (tetherState.lastState == IpServer.STATE_AVAILABLE) {
                     availableList.add(iface);
                 } else if (tetherState.lastState == IpServer.STATE_LOCAL_ONLY) {
@@ -588,9 +612,21 @@
                 }
             }
         }
+
+        mTetherStatesParcel.availableList = availableList.toArray(new String[0]);
+        mTetherStatesParcel.tetheredList = tetherList.toArray(new String[0]);
+        mTetherStatesParcel.localOnlyList = localOnlyList.toArray(new String[0]);
+        mTetherStatesParcel.erroredIfaceList = erroredList.toArray(new String[0]);
+        mTetherStatesParcel.lastErrorList = new int[lastErrorList.size()];
+        Iterator<Integer> iterator = lastErrorList.iterator();
+        for (int i = 0; i < lastErrorList.size(); i++) {
+            mTetherStatesParcel.lastErrorList[i] = iterator.next().intValue();
+        }
+        reportTetherStateChanged(mTetherStatesParcel);
+
         final Intent bcast = new Intent(ACTION_TETHER_STATE_CHANGED);
-        bcast.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING |
-                Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        bcast.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
+                | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
         bcast.putStringArrayListExtra(EXTRA_AVAILABLE_TETHER, availableList);
         bcast.putStringArrayListExtra(EXTRA_ACTIVE_LOCAL_ONLY, localOnlyList);
         bcast.putStringArrayListExtra(EXTRA_ACTIVE_TETHER, tetherList);
@@ -638,16 +674,16 @@
         }
         int icon = 0;
         switch(id) {
-          case SystemMessage.NOTE_TETHER_USB:
-            icon = com.android.internal.R.drawable.stat_sys_tether_usb;
-            break;
-          case SystemMessage.NOTE_TETHER_BLUETOOTH:
-            icon = com.android.internal.R.drawable.stat_sys_tether_bluetooth;
-            break;
-          case SystemMessage.NOTE_TETHER_GENERAL:
-          default:
-            icon = com.android.internal.R.drawable.stat_sys_tether_general;
-            break;
+            case SystemMessage.NOTE_TETHER_USB:
+                icon = com.android.internal.R.drawable.stat_sys_tether_usb;
+                break;
+            case SystemMessage.NOTE_TETHER_BLUETOOTH:
+                icon = com.android.internal.R.drawable.stat_sys_tether_bluetooth;
+                break;
+            case SystemMessage.NOTE_TETHER_GENERAL:
+            default:
+                icon = com.android.internal.R.drawable.stat_sys_tether_general;
+                break;
         }
 
         if (mLastNotificationId != 0) {
@@ -679,8 +715,8 @@
         }
 
         if (mTetheredNotificationBuilder == null) {
-            mTetheredNotificationBuilder =
-                    new Notification.Builder(mContext, SystemNotificationChannels.NETWORK_STATUS);
+            mTetheredNotificationBuilder = new Notification.Builder(mContext,
+                    SystemNotificationChannels.NETWORK_STATUS);
             mTetheredNotificationBuilder.setWhen(0)
                     .setOngoing(true)
                     .setColor(mContext.getColor(
@@ -701,7 +737,7 @@
     @VisibleForTesting
     protected void clearTetheredNotification() {
         NotificationManager notificationManager =
-            (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
         if (notificationManager != null && mLastNotificationId != 0) {
             notificationManager.cancelAsUser(null, mLastNotificationId,
                     UserHandle.ALL);
@@ -726,14 +762,17 @@
             } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
                 mLog.log("OBSERVED configuration changed");
                 updateConfiguration();
+            } else if (action.equals(UserManager.ACTION_USER_RESTRICTIONS_CHANGED)) {
+                mLog.log("OBSERVED user restrictions changed");
+                handleUserRestrictionAction();
             }
         }
 
         private void handleConnectivityAction(Intent intent) {
             final NetworkInfo networkInfo =
                     (NetworkInfo) intent.getParcelableExtra(EXTRA_NETWORK_INFO);
-            if (networkInfo == null ||
-                    networkInfo.getDetailedState() == NetworkInfo.DetailedState.FAILED) {
+            if (networkInfo == null
+                    || networkInfo.getDetailedState() == NetworkInfo.DetailedState.FAILED) {
                 return;
             }
 
@@ -832,25 +871,35 @@
                 }
             }
         }
+
+        private void handleUserRestrictionAction() {
+            mTetheringRestriction.onUserRestrictionsChanged();
+        }
     }
 
     @VisibleForTesting
-    protected static class TetheringUserRestrictionListener implements UserRestrictionsListener {
+    protected static class UserRestrictionActionListener {
+        private final UserManager mUserManager;
         private final Tethering mWrapper;
+        public boolean mDisallowTethering;
 
-        public TetheringUserRestrictionListener(Tethering wrapper) {
+        public UserRestrictionActionListener(UserManager um, Tethering wrapper) {
+            mUserManager = um;
             mWrapper = wrapper;
+            mDisallowTethering = false;
         }
 
-        public void onUserRestrictionsChanged(int userId,
-                                              Bundle newRestrictions,
-                                              Bundle prevRestrictions) {
+        public void onUserRestrictionsChanged() {
+            // getUserRestrictions gets restriction for this process' user, which is the primary
+            // user. This is fine because DISALLOW_CONFIG_TETHERING can only be set on the primary
+            // user. See UserManager.DISALLOW_CONFIG_TETHERING.
+            final Bundle restrictions = mUserManager.getUserRestrictions();
             final boolean newlyDisallowed =
-                    newRestrictions.getBoolean(UserManager.DISALLOW_CONFIG_TETHERING);
-            final boolean previouslyDisallowed =
-                    prevRestrictions.getBoolean(UserManager.DISALLOW_CONFIG_TETHERING);
-            final boolean tetheringDisallowedChanged = (newlyDisallowed != previouslyDisallowed);
+                    restrictions.getBoolean(UserManager.DISALLOW_CONFIG_TETHERING);
+            final boolean prevDisallowed = mDisallowTethering;
+            mDisallowTethering = newlyDisallowed;
 
+            final boolean tetheringDisallowedChanged = (newlyDisallowed != prevDisallowed);
             if (!tetheringDisallowedChanged) {
                 return;
             }
@@ -888,8 +937,8 @@
             }
         }
 
-        mLog.log("Error disabling Wi-Fi IP serving; " +
-                (TextUtils.isEmpty(ifname) ? "no interface name specified"
+        mLog.log("Error disabling Wi-Fi IP serving; "
+                + (TextUtils.isEmpty(ifname) ? "no interface name specified"
                                            : "specified interface: " + ifname));
     }
 
@@ -928,8 +977,8 @@
             changeInterfaceState(ifname, ipServingMode);
         } else {
             mLog.e(String.format(
-                   "Cannot enable IP serving in mode %s on missing interface name",
-                   ipServingMode));
+                    "Cannot enable IP serving in mode %s on missing interface name",
+                    ipServingMode));
         }
     }
 
@@ -989,11 +1038,11 @@
         }
     }
 
-    public TetheringConfiguration getTetheringConfiguration() {
+    TetheringConfiguration getTetheringConfiguration() {
         return mConfig;
     }
 
-    public boolean hasTetherableConfiguration() {
+    boolean hasTetherableConfiguration() {
         final TetheringConfiguration cfg = mConfig;
         final boolean hasDownstreamConfiguration =
                 (cfg.tetherableUsbRegexs.length != 0)
@@ -1007,19 +1056,19 @@
 
     // TODO - update callers to use getTetheringConfiguration(),
     // which has only final members.
-    public String[] getTetherableUsbRegexs() {
+    String[] getTetherableUsbRegexs() {
         return copy(mConfig.tetherableUsbRegexs);
     }
 
-    public String[] getTetherableWifiRegexs() {
+    String[] getTetherableWifiRegexs() {
         return copy(mConfig.tetherableWifiRegexs);
     }
 
-    public String[] getTetherableBluetoothRegexs() {
+    String[] getTetherableBluetoothRegexs() {
         return copy(mConfig.tetherableBluetoothRegexs);
     }
 
-    public int setUsbTethering(boolean enable) {
+    int setUsbTethering(boolean enable) {
         if (VDBG) Log.d(TAG, "setUsbTethering(" + enable + ")");
         UsbManager usbManager = (UsbManager) mContext.getSystemService(Context.USB_SERVICE);
         if (usbManager == null) {
@@ -1035,7 +1084,7 @@
     }
 
     // TODO review API - figure out how to delete these entirely.
-    public String[] getTetheredIfaces() {
+    String[] getTetheredIfaces() {
         ArrayList<String> list = new ArrayList<String>();
         synchronized (mPublicSync) {
             for (int i = 0; i < mTetherStates.size(); i++) {
@@ -1048,7 +1097,7 @@
         return list.toArray(new String[list.size()]);
     }
 
-    public String[] getTetherableIfaces() {
+    String[] getTetherableIfaces() {
         ArrayList<String> list = new ArrayList<String>();
         synchronized (mPublicSync) {
             for (int i = 0; i < mTetherStates.size(); i++) {
@@ -1061,13 +1110,13 @@
         return list.toArray(new String[list.size()]);
     }
 
-    public String[] getTetheredDhcpRanges() {
+    String[] getTetheredDhcpRanges() {
         // TODO: this is only valid for the old DHCP server. Latest search suggests it is only used
         // by WifiP2pServiceImpl to start dnsmasq: remove/deprecate after migrating callers.
         return mConfig.legacyDhcpRanges;
     }
 
-    public String[] getErroredIfaces() {
+    String[] getErroredIfaces() {
         ArrayList<String> list = new ArrayList<String>();
         synchronized (mPublicSync) {
             for (int i = 0; i < mTetherStates.size(); i++) {
@@ -1518,8 +1567,8 @@
                         }
 
                         if (DBG) {
-                            Log.d(TAG, "TetherModeAlive still has " + mNotifyList.size() +
-                                    " live requests:");
+                            Log.d(TAG, "TetherModeAlive still has " + mNotifyList.size()
+                                    + " live requests:");
                             for (IpServer o : mNotifyList) {
                                 Log.d(TAG, "  " + o);
                             }
@@ -1588,7 +1637,7 @@
                         transitionTo(mInitialState);
                         break;
                     default:
-                       retValue = false;
+                        retValue = false;
                 }
                 return retValue;
             }
@@ -1625,7 +1674,7 @@
                 notify(IpServer.CMD_START_TETHERING_ERROR);
                 try {
                     mNMService.setIpForwardingEnabled(false);
-                } catch (Exception e) {}
+                } catch (Exception e) { }
             }
         }
 
@@ -1636,7 +1685,7 @@
                 notify(IpServer.CMD_STOP_TETHERING_ERROR);
                 try {
                     mNMService.setIpForwardingEnabled(false);
-                } catch (Exception e) {}
+                } catch (Exception e) { }
             }
         }
 
@@ -1647,10 +1696,10 @@
                 notify(IpServer.CMD_SET_DNS_FORWARDERS_ERROR);
                 try {
                     mNMService.stopTethering();
-                } catch (Exception e) {}
+                } catch (Exception e) { }
                 try {
                     mNMService.setIpForwardingEnabled(false);
-                } catch (Exception e) {}
+                } catch (Exception e) { }
             }
         }
 
@@ -1731,55 +1780,66 @@
         }
     }
 
-    public void systemReady() {
+    private void startTrackDefaultNetwork() {
         mUpstreamNetworkMonitor.startTrackDefaultNetwork(mDeps.getDefaultNetworkRequest(),
                 mEntitlementMgr);
     }
 
     /** Get the latest value of the tethering entitlement check. */
-    public void getLatestTetheringEntitlementResult(int type, ResultReceiver receiver,
+    void requestLatestTetheringEntitlementResult(int type, ResultReceiver receiver,
             boolean showEntitlementUi) {
         if (receiver != null) {
-            mEntitlementMgr.getLatestTetheringEntitlementResult(type, receiver, showEntitlementUi);
+            mEntitlementMgr.requestLatestTetheringEntitlementResult(type, receiver,
+                    showEntitlementUi);
         }
     }
 
     /** Register tethering event callback */
-    public void registerTetheringEventCallback(ITetheringEventCallback callback) {
+    void registerTetherInternalCallback(ITetherInternalCallback callback) {
         mHandler.post(() -> {
+            mTetherInternalCallback = callback;
             try {
-                callback.onUpstreamChanged(mTetherUpstream);
+                mTetherInternalCallback.onCallbackCreated(mTetherUpstream,
+                        mConfig.toStableParcelable(), mTetherStatesParcel);
             } catch (RemoteException e) {
                 // Not really very much to do here.
             }
-            mTetheringEventCallbacks.register(callback);
-        });
-    }
-
-    /** Unregister tethering event callback */
-    public void unregisterTetheringEventCallback(ITetheringEventCallback callback) {
-        mHandler.post(() -> {
-            mTetheringEventCallbacks.unregister(callback);
         });
     }
 
     private void reportUpstreamChanged(Network network) {
-        final int length = mTetheringEventCallbacks.beginBroadcast();
+        // Don't need to synchronized mTetherInternalCallback because all the usage of this variable
+        // should run at the same thread.
+        if (mTetherInternalCallback == null) return;
+
         try {
-            for (int i = 0; i < length; i++) {
-                try {
-                    mTetheringEventCallbacks.getBroadcastItem(i).onUpstreamChanged(network);
-                } catch (RemoteException e) {
-                    // Not really very much to do here.
-                }
-            }
-        } finally {
-            mTetheringEventCallbacks.finishBroadcast();
+            mTetherInternalCallback.onUpstreamChanged(network);
+        } catch (RemoteException e) {
+            // Not really very much to do here.
         }
     }
 
-    @Override
-    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+    private void reportConfigurationChanged(TetheringConfigurationParcel config) {
+        if (mTetherInternalCallback == null) return;
+
+        try {
+            mTetherInternalCallback.onConfigurationChanged(config);
+        } catch (RemoteException e) {
+            // Not really very much to do here.
+        }
+    }
+
+    private void reportTetherStateChanged(TetherStatesParcel states) {
+        if (mTetherInternalCallback == null) return;
+
+        try {
+            mTetherInternalCallback.onTetherStatesChanged(states);
+        } catch (RemoteException e) {
+            // Not really very much to do here.
+        }
+    }
+
+    void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         // Binder.java closes the resource for us.
         @SuppressWarnings("resource")
         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
@@ -1838,7 +1898,7 @@
 
         pw.println("Log:");
         pw.increaseIndent();
-        if (argsContain(args, SHORT_ARG)) {
+        if (argsContain(args, "--short")) {
             pw.println("<log removed for brevity>");
         } else {
             mLog.dump(fd, pw, args);
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
index ca9b168..0ab4d63 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
@@ -38,6 +38,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.net.ConnectivityManager;
+import android.net.TetheringConfigurationParcel;
 import android.net.util.SharedLog;
 import android.provider.Settings;
 import android.telephony.SubscriptionManager;
@@ -384,4 +385,32 @@
         }
         return false;
     }
+
+    /**
+     * Convert this TetheringConfiguration to a TetheringConfigurationParcel.
+     */
+    public TetheringConfigurationParcel toStableParcelable() {
+        final TetheringConfigurationParcel parcel = new TetheringConfigurationParcel();
+        parcel.subId = subId;
+        parcel.tetherableUsbRegexs = tetherableUsbRegexs;
+        parcel.tetherableWifiRegexs = tetherableWifiRegexs;
+        parcel.tetherableBluetoothRegexs = tetherableBluetoothRegexs;
+        parcel.isDunRequired = isDunRequired;
+        parcel.chooseUpstreamAutomatically = chooseUpstreamAutomatically;
+
+        int[] preferredTypes = new int[preferredUpstreamIfaceTypes.size()];
+        int index = 0;
+        for (Integer type : preferredUpstreamIfaceTypes) {
+            preferredTypes[index++] = type;
+        }
+        parcel.preferredUpstreamIfaceTypes = preferredTypes;
+
+        parcel.legacyDhcpRanges = legacyDhcpRanges;
+        parcel.defaultIPv4DNS = defaultIPv4DNS;
+        parcel.enableLegacyDhcpServer = enableLegacyDhcpServer;
+        parcel.provisioningApp = provisioningApp;
+        parcel.provisioningAppNoUi = provisioningAppNoUi;
+        parcel.provisioningCheckPeriod = provisioningCheckPeriod;
+        return parcel;
+    }
 }
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java
new file mode 100644
index 0000000..0ba8412
--- /dev/null
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2017 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.tethering;
+
+import android.content.Context;
+import android.net.INetd;
+import android.net.INetworkPolicyManager;
+import android.net.INetworkStatsService;
+import android.net.NetworkRequest;
+import android.net.ip.IpServer;
+import android.net.util.SharedLog;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.INetworkManagementService;
+import android.os.Looper;
+import android.os.ServiceManager;
+
+import com.android.internal.util.StateMachine;
+
+import java.util.ArrayList;
+
+
+/**
+ * Capture tethering dependencies, for injection.
+ *
+ * @hide
+ */
+public class TetheringDependencies {
+    /**
+     * Get a reference to the offload hardware interface to be used by tethering.
+     */
+    public OffloadHardwareInterface getOffloadHardwareInterface(Handler h, SharedLog log) {
+        return new OffloadHardwareInterface(h, log);
+    }
+
+    /**
+     * Get a reference to the UpstreamNetworkMonitor to be used by tethering.
+     */
+    public UpstreamNetworkMonitor getUpstreamNetworkMonitor(Context ctx, StateMachine target,
+            SharedLog log, int what) {
+        return new UpstreamNetworkMonitor(ctx, target, log, what);
+    }
+
+    /**
+     * Get a reference to the IPv6TetheringCoordinator to be used by tethering.
+     */
+    public IPv6TetheringCoordinator getIPv6TetheringCoordinator(
+            ArrayList<IpServer> notifyList, SharedLog log) {
+        return new IPv6TetheringCoordinator(notifyList, log);
+    }
+
+    /**
+     * Get dependencies to be used by IpServer.
+     */
+    public IpServer.Dependencies getIpServerDependencies() {
+        return new IpServer.Dependencies();
+    }
+
+    /**
+     * Indicates whether tethering is supported on the device.
+     */
+    public boolean isTetheringSupported() {
+        return true;
+    }
+
+    /**
+     * Get the NetworkRequest that should be fulfilled by the default network.
+     */
+    public NetworkRequest getDefaultNetworkRequest() {
+        return null;
+    }
+
+    /**
+     * Get a reference to the EntitlementManager to be used by tethering.
+     */
+    public EntitlementManager getEntitlementManager(Context ctx, StateMachine target,
+            SharedLog log, int what) {
+        return new EntitlementManager(ctx, target, log, what);
+    }
+
+    /**
+     * Generate a new TetheringConfiguration according to input sub Id.
+     */
+    public TetheringConfiguration generateTetheringConfiguration(Context ctx, SharedLog log,
+            int subId) {
+        return new TetheringConfiguration(ctx, log, subId);
+    }
+
+    /**
+     * Get a reference to INetworkManagementService to registerTetheringStatsProvider from
+     * OffloadController. Note: This should be removed soon by Usage refactor work in R
+     * development cycle.
+     */
+    public INetworkManagementService getINetworkManagementService() {
+        return INetworkManagementService.Stub.asInterface(
+                ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
+    }
+
+    /**
+     *  Get a reference to INetworkStatsService to force update tethering usage.
+     *  Note: This should be removed in R development cycle.
+     */
+    public INetworkStatsService getINetworkStatsService() {
+        return INetworkStatsService.Stub.asInterface(
+                ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
+    }
+
+    /**
+     * Get a reference to INetworkPolicyManager to be used by tethering.
+     */
+    public INetworkPolicyManager getINetworkPolicyManager() {
+        return INetworkPolicyManager.Stub.asInterface(
+                ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
+    }
+
+    /**
+     * Get a reference to INetd to be used by tethering.
+     */
+    public INetd getINetd(Context context) {
+        return INetd.Stub.asInterface(
+                (IBinder) context.getSystemService(Context.NETD_SERVICE));
+    }
+
+    /**
+     * Get tethering thread looper.
+     */
+    public Looper getTetheringLooper() {
+        return null;
+    }
+
+    /**
+     *  Get Context of TetheringSerice.
+     */
+    public Context getContext() {
+        return null;
+    }
+}
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java
similarity index 100%
rename from services/core/java/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java
rename to packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java
new file mode 100644
index 0000000..456f2f7
--- /dev/null
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java
@@ -0,0 +1,178 @@
+/*
+ * 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.connectivity.tethering;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.ITetherInternalCallback;
+import android.net.ITetheringConnector;
+import android.net.NetworkRequest;
+import android.net.util.SharedLog;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.ResultReceiver;
+import android.os.SystemProperties;
+import android.provider.Settings;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Android service used to manage tethering.
+ *
+ * <p>The service returns a binder for the system server to communicate with the tethering.
+ */
+public class TetheringService extends Service {
+    private static final String TAG = TetheringService.class.getSimpleName();
+
+    private final SharedLog mLog = new SharedLog(TAG);
+    private TetheringConnector mConnector;
+    private Context mContext;
+    private TetheringDependencies mDeps;
+    private Tethering mTethering;
+
+    @Override
+    public void onCreate() {
+        mLog.mark("onCreate");
+        mDeps = getTetheringDependencies();
+        mContext = mDeps.getContext();
+        mTethering = makeTethering(mDeps);
+    }
+
+    /**
+     * Make a reference to Tethering object.
+     */
+    @VisibleForTesting
+    public Tethering makeTethering(TetheringDependencies deps) {
+        return new Tethering(deps);
+    }
+
+    /**
+     * Create a binder connector for the system server to communicate with the tethering.
+     */
+    private synchronized IBinder makeConnector() {
+        if (mConnector == null) {
+            mConnector = new TetheringConnector(mTethering);
+        }
+        return mConnector;
+    }
+
+    @NonNull
+    @Override
+    public IBinder onBind(Intent intent) {
+        mLog.mark("onBind");
+        return makeConnector();
+    }
+
+    private static class TetheringConnector extends ITetheringConnector.Stub {
+        private final Tethering mService;
+
+        TetheringConnector(Tethering tether) {
+            mService = tether;
+        }
+
+        @Override
+        public void tether(String iface) {
+            mService.tether(iface);
+        }
+
+        @Override
+        public void untether(String iface) {
+            mService.untether(iface);
+        }
+
+        @Override
+        public void setUsbTethering(boolean enable) {
+            mService.setUsbTethering(enable);
+        }
+
+        @Override
+        public void startTethering(int type, ResultReceiver receiver, boolean showProvisioningUi) {
+            mService.startTethering(type, receiver, showProvisioningUi);
+        }
+
+        @Override
+        public void stopTethering(int type) {
+            mService.stopTethering(type);
+        }
+
+        @Override
+        public void requestLatestTetheringEntitlementResult(int type, ResultReceiver receiver,
+                boolean showEntitlementUi) {
+            mService.requestLatestTetheringEntitlementResult(type, receiver, showEntitlementUi);
+        }
+
+        @Override
+        public void registerTetherInternalCallback(ITetherInternalCallback callback) {
+            mService.registerTetherInternalCallback(callback);
+        }
+    }
+
+    @Override
+    protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer,
+                @Nullable String[] args) {
+        mTethering.dump(fd, writer, args);
+    }
+
+    /**
+     * An injection method for testing.
+     */
+    @VisibleForTesting
+    public TetheringDependencies getTetheringDependencies() {
+        if (mDeps == null) {
+            mDeps = new TetheringDependencies() {
+                @Override
+                public NetworkRequest getDefaultNetworkRequest() {
+                    ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService(
+                            Context.CONNECTIVITY_SERVICE);
+                    return cm.getDefaultRequest();
+                }
+
+                @Override
+                public Looper getTetheringLooper() {
+                    final HandlerThread tetherThread = new HandlerThread("android.tethering");
+                    tetherThread.start();
+                    return tetherThread.getLooper();
+                }
+
+                @Override
+                public boolean isTetheringSupported() {
+                    int defaultVal =
+                            SystemProperties.get("ro.tether.denied").equals("true") ? 0 : 1;
+                    boolean tetherSupported = Settings.Global.getInt(mContext.getContentResolver(),
+                            Settings.Global.TETHER_SUPPORTED, defaultVal) != 0;
+                    return tetherSupported;
+                }
+
+                @Override
+                public Context getContext() {
+                    return TetheringService.this;
+                }
+            };
+        }
+
+        return mDeps;
+    }
+}
diff --git a/packages/Tethering/tests/unit/Android.bp b/packages/Tethering/tests/unit/Android.bp
index 363be18..5b018df 100644
--- a/packages/Tethering/tests/unit/Android.bp
+++ b/packages/Tethering/tests/unit/Android.bp
@@ -18,7 +18,6 @@
     name: "TetheringTests",
     certificate: "platform",
     srcs: [
-        ":servicescore-tethering-src",
         "src/**/*.java",
     ],
     test_suites: ["device-tests"],
@@ -41,17 +40,3 @@
         "libstaticjvmtiagent",
     ],
 }
-
-// This group would be removed when tethering migration is done.
-filegroup {
-    name: "tethering-tests-src",
-    srcs: [
-        "src/com/android/server/connectivity/tethering/EntitlementManagerTest.java",
-        "src/com/android/server/connectivity/tethering/OffloadControllerTest.java",
-        "src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java",
-        "src/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java",
-        "src/android/net/dhcp/DhcpServingParamsParcelExtTest.java",
-        "src/android/net/ip/IpServerTest.java",
-        "src/android/net/util/InterfaceSetTest.java",
-    ],
-}
diff --git a/tests/net/java/android/net/util/VersionedBroadcastListenerTest.java b/packages/Tethering/tests/unit/src/android/net/util/VersionedBroadcastListenerTest.java
similarity index 96%
rename from tests/net/java/android/net/util/VersionedBroadcastListenerTest.java
rename to packages/Tethering/tests/unit/src/android/net/util/VersionedBroadcastListenerTest.java
index 0d27d5b..5a9b6e3 100644
--- a/tests/net/java/android/net/util/VersionedBroadcastListenerTest.java
+++ b/packages/Tethering/tests/unit/src/android/net/util/VersionedBroadcastListenerTest.java
@@ -51,7 +51,9 @@
     private VersionedBroadcastListener mListener;
     private int mCallbackCount;
 
-    private void doCallback() { mCallbackCount++; }
+    private void doCallback() {
+        mCallbackCount++;
+    }
 
     private class MockContext extends BroadcastInterceptingContext {
         MockContext(Context base) {
@@ -96,7 +98,7 @@
         mListener.startListening();
         for (int i = 0; i < 5; i++) {
             sendBroadcast();
-            assertEquals(i+1, mCallbackCount);
+            assertEquals(i + 1, mCallbackCount);
         }
         mListener.stopListening();
     }
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
index 5217e26..99cf9e9 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
@@ -24,6 +24,9 @@
 import static android.net.ConnectivityManager.TETHER_ERROR_PROVISION_FAILED;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -44,6 +47,7 @@
 import android.os.Message;
 import android.os.PersistableBundle;
 import android.os.ResultReceiver;
+import android.os.SystemProperties;
 import android.os.test.TestLooper;
 import android.provider.Settings;
 import android.telephony.CarrierConfigManager;
@@ -57,7 +61,6 @@
 import com.android.internal.util.StateMachine;
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.server.connectivity.MockableSystemProperties;
 
 import org.junit.After;
 import org.junit.Before;
@@ -65,6 +68,8 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
 
 import java.util.ArrayList;
 import java.util.concurrent.CountDownLatch;
@@ -80,7 +85,6 @@
 
     @Mock private CarrierConfigManager mCarrierConfigManager;
     @Mock private Context mContext;
-    @Mock private MockableSystemProperties mSystemProperties;
     @Mock private Resources mResources;
     @Mock private SharedLog mLog;
     @Mock private EntitlementManager.OnUiEntitlementFailedListener mEntitlementFailedListener;
@@ -95,6 +99,7 @@
     private TestStateMachine mSM;
     private WrappedEntitlementManager mEnMgr;
     private TetheringConfiguration mConfig;
+    private MockitoSession mMockingSession;
 
     private class MockContext extends BroadcastInterceptingContext {
         MockContext(Context base) {
@@ -118,8 +123,8 @@
         public int silentProvisionCount = 0;
 
         public WrappedEntitlementManager(Context ctx, StateMachine target,
-                SharedLog log, int what, MockableSystemProperties systemProperties) {
-            super(ctx, target, log, what, systemProperties);
+                SharedLog log, int what) {
+            super(ctx, target, log, what);
         }
 
         public void reset() {
@@ -144,6 +149,15 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        mMockingSession = mockitoSession()
+                .initMocks(this)
+                .spyStatic(SystemProperties.class)
+                .strictness(Strictness.WARN)
+                .startMocking();
+        // Don't disable tethering provisioning unless requested.
+        doReturn(false).when(
+                () -> SystemProperties.getBoolean(
+                eq(EntitlementManager.DISABLE_PROVISIONING_SYSPROP_KEY), anyBoolean()));
 
         when(mResources.getStringArray(R.array.config_tether_dhcp_range))
             .thenReturn(new String[0]);
@@ -161,8 +175,7 @@
         mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
         mMockContext = new MockContext(mContext);
         mSM = new TestStateMachine();
-        mEnMgr = new WrappedEntitlementManager(mMockContext, mSM, mLog, EVENT_EM_UPDATE,
-                mSystemProperties);
+        mEnMgr = new WrappedEntitlementManager(mMockContext, mSM, mLog, EVENT_EM_UPDATE);
         mEnMgr.setOnUiEntitlementFailedListener(mEntitlementFailedListener);
         mConfig = new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
         mEnMgr.setTetheringConfigurationFetcher(() -> {
@@ -176,6 +189,7 @@
             mSM.quit();
             mSM = null;
         }
+        mMockingSession.finishMocking();
     }
 
     private void setupForRequiredProvisioning() {
@@ -184,9 +198,6 @@
                 .thenReturn(PROVISIONING_APP_NAME);
         when(mResources.getString(R.string.config_mobile_hotspot_provision_app_no_ui))
                 .thenReturn(PROVISIONING_NO_UI_APP_NAME);
-       // Don't disable tethering provisioning unless requested.
-        when(mSystemProperties.getBoolean(eq(EntitlementManager.DISABLE_PROVISIONING_SYSPROP_KEY),
-                anyBoolean())).thenReturn(false);
         // Act like the CarrierConfigManager is present and ready unless told otherwise.
         when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE))
                 .thenReturn(mCarrierConfigManager);
@@ -244,7 +255,7 @@
     }
 
     @Test
-    public void testGetLastEntitlementCacheValue() throws Exception {
+    public void testRequestLastEntitlementCacheValue() throws Exception {
         final CountDownLatch mCallbacklatch = new CountDownLatch(1);
         // 1. Entitlement check is not required.
         mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
@@ -255,7 +266,7 @@
                 mCallbacklatch.countDown();
             }
         };
-        mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true);
+        mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true);
         mLooper.dispatchAll();
         callbackTimeoutHelper(mCallbacklatch);
         assertEquals(0, mEnMgr.uiProvisionCount);
@@ -270,7 +281,7 @@
                 mCallbacklatch.countDown();
             }
         };
-        mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false);
+        mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false);
         mLooper.dispatchAll();
         callbackTimeoutHelper(mCallbacklatch);
         assertEquals(0, mEnMgr.uiProvisionCount);
@@ -284,7 +295,7 @@
                 mCallbacklatch.countDown();
             }
         };
-        mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true);
+        mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true);
         mLooper.dispatchAll();
         callbackTimeoutHelper(mCallbacklatch);
         assertEquals(1, mEnMgr.uiProvisionCount);
@@ -298,7 +309,7 @@
                 mCallbacklatch.countDown();
             }
         };
-        mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false);
+        mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false);
         mLooper.dispatchAll();
         callbackTimeoutHelper(mCallbacklatch);
         assertEquals(0, mEnMgr.uiProvisionCount);
@@ -312,7 +323,7 @@
                 mCallbacklatch.countDown();
             }
         };
-        mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true);
+        mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true);
         mLooper.dispatchAll();
         callbackTimeoutHelper(mCallbacklatch);
         assertEquals(1, mEnMgr.uiProvisionCount);
@@ -326,7 +337,7 @@
                 mCallbacklatch.countDown();
             }
         };
-        mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true);
+        mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true);
         mLooper.dispatchAll();
         callbackTimeoutHelper(mCallbacklatch);
         assertEquals(0, mEnMgr.uiProvisionCount);
@@ -339,7 +350,7 @@
                 mCallbacklatch.countDown();
             }
         };
-        mEnMgr.getLatestTetheringEntitlementResult(TETHERING_USB, receiver, false);
+        mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_USB, receiver, false);
         mLooper.dispatchAll();
         callbackTimeoutHelper(mCallbacklatch);
         assertEquals(0, mEnMgr.uiProvisionCount);
diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
similarity index 83%
rename from tests/net/java/com/android/server/connectivity/TetheringTest.java
rename to packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
index 9e5717b..0273ed3 100644
--- a/tests/net/java/com/android/server/connectivity/TetheringTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.connectivity;
+package com.android.server.connectivity.tethering;
 
 import static android.hardware.usb.UsbManager.USB_CONFIGURED;
 import static android.hardware.usb.UsbManager.USB_CONNECTED;
@@ -39,7 +39,9 @@
 import static android.provider.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.argThat;
@@ -70,7 +72,7 @@
 import android.net.INetd;
 import android.net.INetworkPolicyManager;
 import android.net.INetworkStatsService;
-import android.net.ITetheringEventCallback;
+import android.net.ITetherInternalCallback;
 import android.net.InterfaceConfiguration;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
@@ -82,6 +84,8 @@
 import android.net.NetworkState;
 import android.net.NetworkUtils;
 import android.net.RouteInfo;
+import android.net.TetherStatesParcel;
+import android.net.TetheringConfigurationParcel;
 import android.net.dhcp.DhcpServerCallbacks;
 import android.net.dhcp.DhcpServingParamsParcel;
 import android.net.dhcp.IDhcpServer;
@@ -98,6 +102,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.INetworkManagementService;
+import android.os.Looper;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -109,6 +114,7 @@
 import android.telephony.TelephonyManager;
 import android.test.mock.MockContentResolver;
 
+import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -116,11 +122,6 @@
 import com.android.internal.util.StateMachine;
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.server.connectivity.tethering.IPv6TetheringCoordinator;
-import com.android.server.connectivity.tethering.OffloadHardwareInterface;
-import com.android.server.connectivity.tethering.TetheringConfiguration;
-import com.android.server.connectivity.tethering.TetheringDependencies;
-import com.android.server.connectivity.tethering.UpstreamNetworkMonitor;
 
 import org.junit.After;
 import org.junit.Before;
@@ -154,7 +155,6 @@
     @Mock private INetworkManagementService mNMService;
     @Mock private INetworkStatsService mStatsService;
     @Mock private INetworkPolicyManager mPolicyManager;
-    @Mock private MockableSystemProperties mSystemProperties;
     @Mock private OffloadHardwareInterface mOffloadHardwareInterface;
     @Mock private Resources mResources;
     @Mock private TelephonyManager mTelephonyManager;
@@ -166,6 +166,7 @@
     @Mock private RouterAdvertisementDaemon mRouterAdvertisementDaemon;
     @Mock private IDhcpServer mDhcpServer;
     @Mock private INetd mNetd;
+    @Mock private UserManager mUserManager;
 
     private final MockIpServerDependencies mIpServerDependencies =
             spy(new MockIpServerDependencies());
@@ -184,28 +185,37 @@
     private Tethering mTethering;
     private PhoneStateListener mPhoneStateListener;
 
-    private class MockContext extends BroadcastInterceptingContext {
-        MockContext(Context base) {
+    private class TestContext extends BroadcastInterceptingContext {
+        TestContext(Context base) {
             super(base);
         }
 
         @Override
-        public ApplicationInfo getApplicationInfo() { return mApplicationInfo; }
+        public ApplicationInfo getApplicationInfo() {
+            return mApplicationInfo;
+        }
 
         @Override
-        public ContentResolver getContentResolver() { return mContentResolver; }
+        public ContentResolver getContentResolver() {
+            return mContentResolver;
+        }
 
         @Override
-        public String getPackageName() { return "TetheringTest"; }
+        public String getPackageName() {
+            return "TetheringTest";
+        }
 
         @Override
-        public Resources getResources() { return mResources; }
+        public Resources getResources() {
+            return mResources;
+        }
 
         @Override
         public Object getSystemService(String name) {
             if (Context.WIFI_SERVICE.equals(name)) return mWifiManager;
             if (Context.USB_SERVICE.equals(name)) return mUsbManager;
             if (Context.TELEPHONY_SERVICE.equals(name)) return mTelephonyManager;
+            if (Context.USER_SERVICE.equals(name)) return mUserManager;
             return super.getSystemService(name);
         }
 
@@ -266,14 +276,14 @@
     }
 
     public class MockTetheringDependencies extends TetheringDependencies {
-        StateMachine upstreamNetworkMonitorMasterSM;
-        ArrayList<IpServer> ipv6CoordinatorNotifyList;
-        int isTetheringSupportedCalls;
+        StateMachine mUpstreamNetworkMonitorMasterSM;
+        ArrayList<IpServer> mIpv6CoordinatorNotifyList;
+        int mIsTetheringSupportedCalls;
 
         public void reset() {
-            upstreamNetworkMonitorMasterSM = null;
-            ipv6CoordinatorNotifyList = null;
-            isTetheringSupportedCalls = 0;
+            mUpstreamNetworkMonitorMasterSM = null;
+            mIpv6CoordinatorNotifyList = null;
+            mIsTetheringSupportedCalls = 0;
         }
 
         @Override
@@ -284,14 +294,14 @@
         @Override
         public UpstreamNetworkMonitor getUpstreamNetworkMonitor(Context ctx,
                 StateMachine target, SharedLog log, int what) {
-            upstreamNetworkMonitorMasterSM = target;
+            mUpstreamNetworkMonitorMasterSM = target;
             return mUpstreamNetworkMonitor;
         }
 
         @Override
         public IPv6TetheringCoordinator getIPv6TetheringCoordinator(
                 ArrayList<IpServer> notifyList, SharedLog log) {
-            ipv6CoordinatorNotifyList = notifyList;
+            mIpv6CoordinatorNotifyList = notifyList;
             return mIPv6TetheringCoordinator;
         }
 
@@ -302,7 +312,7 @@
 
         @Override
         public boolean isTetheringSupported() {
-            isTetheringSupportedCalls++;
+            mIsTetheringSupportedCalls++;
             return true;
         }
 
@@ -311,6 +321,36 @@
                 int subId) {
             return new MockTetheringConfiguration(ctx, log, subId);
         }
+
+        @Override
+        public INetworkManagementService getINetworkManagementService() {
+            return mNMService;
+        }
+
+        @Override
+        public INetworkStatsService getINetworkStatsService() {
+            return mStatsService;
+        }
+
+        @Override
+        public INetworkPolicyManager getINetworkPolicyManager() {
+            return mPolicyManager;
+        }
+
+        @Override
+        public INetd getINetd(Context context) {
+            return mNetd;
+        }
+
+        @Override
+        public Looper getTetheringLooper() {
+            return mLooper.getLooper();
+        }
+
+        @Override
+        public Context getContext() {
+            return mServiceContext;
+        }
     }
 
     private static NetworkState buildMobileUpstreamState(boolean withIPv4, boolean withIPv6,
@@ -345,7 +385,7 @@
 
 
         final NetworkCapabilities capabilities = new NetworkCapabilities()
-                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);;
+                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
         return new NetworkState(info, prop, capabilities, new Network(100), null, "netid");
     }
 
@@ -390,7 +430,7 @@
         when(mRouterAdvertisementDaemon.start())
                 .thenReturn(true);
 
-        mServiceContext = new MockContext(mContext);
+        mServiceContext = new TestContext(mContext);
         mContentResolver = new MockContentResolver(mServiceContext);
         mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
         Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0);
@@ -403,9 +443,9 @@
         };
         mServiceContext.registerReceiver(mBroadcastReceiver,
                 new IntentFilter(ACTION_TETHER_STATE_CHANGED));
-        mTetheringDependencies.reset();
         mTethering = makeTethering();
         verify(mNMService).registerTetheringStatsProvider(any(), anyString());
+        verify(mNetd).registerUnsolicitedEventListener(any());
         final ArgumentCaptor<PhoneStateListener> phoneListenerCaptor =
                 ArgumentCaptor.forClass(PhoneStateListener.class);
         verify(mTelephonyManager).listen(phoneListenerCaptor.capture(),
@@ -414,9 +454,8 @@
     }
 
     private Tethering makeTethering() {
-        return new Tethering(mServiceContext, mNMService, mStatsService, mPolicyManager,
-                mLooper.getLooper(), mSystemProperties,
-                mTetheringDependencies);
+        mTetheringDependencies.reset();
+        return new Tethering(mTetheringDependencies);
     }
 
     @After
@@ -507,7 +546,7 @@
         // it creates a IpServer and sends out a broadcast indicating that the
         // interface is "available".
         if (emulateInterfaceStatusChanged) {
-            assertEquals(1, mTetheringDependencies.isTetheringSupportedCalls);
+            assertEquals(1, mTetheringDependencies.mIsTetheringSupportedCalls);
             verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
             verify(mWifiManager).updateInterfaceIpState(
                     TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
@@ -584,7 +623,7 @@
         verify(mUpstreamNetworkMonitor, times(1)).startObserveAllNetworks();
         // This will be called twice, one is on entering IpServer.STATE_AVAILABLE,
         // and another one is on IpServer.STATE_TETHERED/IpServer.STATE_LOCAL_ONLY.
-        assertEquals(2, mTetheringDependencies.isTetheringSupportedCalls);
+        assertEquals(2, mTetheringDependencies.mIsTetheringSupportedCalls);
 
         // Emulate externally-visible WifiManager effects, when hotspot mode
         // is being torn down.
@@ -617,8 +656,7 @@
                 argThat(sm -> sm.linkProperties().getInterfaceName().equals(TEST_USB_IFNAME)),
                 eq(IpServer.STATE_TETHERED));
 
-        for (IpServer ipSrv :
-                mTetheringDependencies.ipv6CoordinatorNotifyList) {
+        for (IpServer ipSrv : mTetheringDependencies.mIpv6CoordinatorNotifyList) {
             NetworkState ipv6OnlyState = buildMobileUpstreamState(false, true, false);
             ipSrv.sendMessage(IpServer.CMD_IPV6_TETHER_UPDATE, 0, 0,
                     upstreamState.linkProperties.isIpv6Provisioned()
@@ -650,7 +688,7 @@
     @Test
     public void workingMobileUsbTethering_IPv4LegacyDhcp() {
         Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 1);
-        mTethering = makeTethering();
+        sendConfigurationChanged();
         final NetworkState upstreamState = buildMobileIPv4UpstreamState();
         runUsbTethering(upstreamState);
         sendIPv6TetherUpdates(upstreamState);
@@ -719,7 +757,7 @@
                 .thenReturn(upstreamState);
 
         // Upstream LinkProperties changed: UpstreamNetworkMonitor sends EVENT_ON_LINKPROPERTIES.
-        mTetheringDependencies.upstreamNetworkMonitorMasterSM.sendMessage(
+        mTetheringDependencies.mUpstreamNetworkMonitorMasterSM.sendMessage(
                 Tethering.TetherMasterSM.EVENT_UPSTREAM_CALLBACK,
                 UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES,
                 0,
@@ -784,7 +822,7 @@
         sendWifiApStateChanged(WIFI_AP_STATE_ENABLED);
         mLooper.dispatchAll();
 
-        assertEquals(1, mTetheringDependencies.isTetheringSupportedCalls);
+        assertEquals(1, mTetheringDependencies.mIsTetheringSupportedCalls);
         verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
         verify(mWifiManager).updateInterfaceIpState(
                 TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
@@ -828,7 +866,7 @@
         verify(mUpstreamNetworkMonitor, times(1)).registerMobileNetworkRequest();
         // This will be called twice, one is on entering IpServer.STATE_AVAILABLE,
         // and another one is on IpServer.STATE_TETHERED/IpServer.STATE_LOCAL_ONLY.
-        assertEquals(2, mTetheringDependencies.isTetheringSupportedCalls);
+        assertEquals(2, mTetheringDependencies.mIsTetheringSupportedCalls);
 
         /////
         // We do not currently emulate any upstream being found.
@@ -901,7 +939,7 @@
                 TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_TETHERED);
         // There are 3 state change event:
         // AVAILABLE -> STATE_TETHERED -> STATE_AVAILABLE.
-        assertEquals(3, mTetheringDependencies.isTetheringSupportedCalls);
+        assertEquals(3, mTetheringDependencies.mIsTetheringSupportedCalls);
         verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
         // This is called, but will throw.
         verify(mNMService, times(1)).setIpForwardingEnabled(true);
@@ -918,26 +956,26 @@
         verifyNoMoreInteractions(mNMService);
     }
 
-    private void userRestrictionsListenerBehaviour(
-        boolean currentDisallow, boolean nextDisallow, String[] activeTetheringIfacesList,
-        int expectedInteractionsWithShowNotification) throws  Exception {
-        final int userId = 0;
-        final Bundle currRestrictions = new Bundle();
+    private void runUserRestrictionsChange(
+            boolean currentDisallow, boolean nextDisallow, String[] activeTetheringIfacesList,
+            int expectedInteractionsWithShowNotification) throws  Exception {
         final Bundle newRestrictions = new Bundle();
-        Tethering tethering = mock(Tethering.class);
-        Tethering.TetheringUserRestrictionListener turl =
-                new Tethering.TetheringUserRestrictionListener(tethering);
-
-        currRestrictions.putBoolean(UserManager.DISALLOW_CONFIG_TETHERING, currentDisallow);
         newRestrictions.putBoolean(UserManager.DISALLOW_CONFIG_TETHERING, nextDisallow);
-        when(tethering.getTetheredIfaces()).thenReturn(activeTetheringIfacesList);
+        final Tethering mockTethering = mock(Tethering.class);
+        when(mockTethering.getTetheredIfaces()).thenReturn(activeTetheringIfacesList);
+        when(mUserManager.getUserRestrictions()).thenReturn(newRestrictions);
 
-        turl.onUserRestrictionsChanged(userId, newRestrictions, currRestrictions);
+        final Tethering.UserRestrictionActionListener ural =
+                new Tethering.UserRestrictionActionListener(mUserManager, mockTethering);
+        ural.mDisallowTethering = currentDisallow;
 
-        verify(tethering, times(expectedInteractionsWithShowNotification))
+        ural.onUserRestrictionsChanged();
+
+        verify(mockTethering, times(expectedInteractionsWithShowNotification))
                 .showTetheredNotification(anyInt(), eq(false));
 
-        verify(tethering, times(expectedInteractionsWithShowNotification)).untetherAll();
+        verify(mockTethering, times(expectedInteractionsWithShowNotification))
+                .untetherAll();
     }
 
     @Test
@@ -947,7 +985,7 @@
         final boolean nextDisallow = true;
         final int expectedInteractionsWithShowNotification = 0;
 
-        userRestrictionsListenerBehaviour(currDisallow, nextDisallow, emptyActiveIfacesList,
+        runUserRestrictionsChange(currDisallow, nextDisallow, emptyActiveIfacesList,
                 expectedInteractionsWithShowNotification);
     }
 
@@ -958,7 +996,7 @@
         final boolean nextDisallow = true;
         final int expectedInteractionsWithShowNotification = 1;
 
-        userRestrictionsListenerBehaviour(currDisallow, nextDisallow, nonEmptyActiveIfacesList,
+        runUserRestrictionsChange(currDisallow, nextDisallow, nonEmptyActiveIfacesList,
                 expectedInteractionsWithShowNotification);
     }
 
@@ -969,7 +1007,7 @@
         final boolean nextDisallow = false;
         final int expectedInteractionsWithShowNotification = 0;
 
-        userRestrictionsListenerBehaviour(currDisallow, nextDisallow, nonEmptyActiveIfacesList,
+        runUserRestrictionsChange(currDisallow, nextDisallow, nonEmptyActiveIfacesList,
                 expectedInteractionsWithShowNotification);
     }
 
@@ -980,7 +1018,7 @@
         final boolean nextDisallow = false;
         final int expectedInteractionsWithShowNotification = 0;
 
-        userRestrictionsListenerBehaviour(currDisallow, nextDisallow, nonEmptyActiveIfacesList,
+        runUserRestrictionsChange(currDisallow, nextDisallow, nonEmptyActiveIfacesList,
                 expectedInteractionsWithShowNotification);
     }
 
@@ -991,27 +1029,59 @@
         boolean currDisallow = true;
         boolean nextDisallow = true;
 
-        userRestrictionsListenerBehaviour(currDisallow, nextDisallow, nonEmptyActiveIfacesList,
+        runUserRestrictionsChange(currDisallow, nextDisallow, nonEmptyActiveIfacesList,
                 expectedInteractionsWithShowNotification);
 
         currDisallow = false;
         nextDisallow = false;
 
-        userRestrictionsListenerBehaviour(currDisallow, nextDisallow, nonEmptyActiveIfacesList,
+        runUserRestrictionsChange(currDisallow, nextDisallow, nonEmptyActiveIfacesList,
                 expectedInteractionsWithShowNotification);
     }
 
-    private class TestTetheringEventCallback extends ITetheringEventCallback.Stub {
+    private class TestTetherInternalCallback extends ITetherInternalCallback.Stub {
         private final ArrayList<Network> mActualUpstreams = new ArrayList<>();
+        private final ArrayList<TetheringConfigurationParcel> mTetheringConfigs =
+                new ArrayList<>();
+        private final ArrayList<TetherStatesParcel> mTetherStates = new ArrayList<>();
 
+        // This function will remove the recorded callbacks, so it must be called once for
+        // each callback. If this is called after multiple callback, the order matters.
+        // onCallbackCreated counts as the first call to expectUpstreamChanged with
+        // @see onCallbackCreated.
         public void expectUpstreamChanged(Network... networks) {
+            if (networks == null) {
+                assertNoUpstreamChangeCallback();
+                return;
+            }
+
             final ArrayList<Network> expectedUpstreams =
                     new ArrayList<Network>(Arrays.asList(networks));
             for (Network upstream : expectedUpstreams) {
                 // throws OOB if no expectations
                 assertEquals(mActualUpstreams.remove(0), upstream);
             }
-            assertNoCallback();
+            assertNoUpstreamChangeCallback();
+        }
+
+        // This function will remove the recorded callbacks, so it must be called once
+        // for each callback. If this is called after multiple callback, the order matters.
+        // onCallbackCreated counts as the first call to onConfigurationChanged with
+        // @see onCallbackCreated.
+        public void expectConfigurationChanged(TetheringConfigurationParcel... tetherConfigs) {
+            final ArrayList<TetheringConfigurationParcel> expectedTetherConfig =
+                    new ArrayList<TetheringConfigurationParcel>(Arrays.asList(tetherConfigs));
+            for (TetheringConfigurationParcel config : expectedTetherConfig) {
+                // throws OOB if no expectations
+                final TetheringConfigurationParcel actualConfig = mTetheringConfigs.remove(0);
+                assertTetherConfigParcelEqual(actualConfig, config);
+            }
+            assertNoConfigChangeCallback();
+        }
+
+        public TetherStatesParcel pollTetherStatesChanged() {
+            assertStateChangeCallback();
+            return mTetherStates.remove(0);
         }
 
         @Override
@@ -1019,48 +1089,93 @@
             mActualUpstreams.add(network);
         }
 
-        public void assertNoCallback() {
+        @Override
+        public void onConfigurationChanged(TetheringConfigurationParcel config) {
+            mTetheringConfigs.add(config);
+        }
+
+        @Override
+        public void onTetherStatesChanged(TetherStatesParcel states) {
+            mTetherStates.add(states);
+        }
+
+        @Override
+        public void onCallbackCreated(Network network, TetheringConfigurationParcel config,
+                TetherStatesParcel states) {
+            mActualUpstreams.add(network);
+            mTetheringConfigs.add(config);
+            mTetherStates.add(states);
+        }
+
+        public void assertNoUpstreamChangeCallback() {
             assertTrue(mActualUpstreams.isEmpty());
         }
+
+        public void assertNoConfigChangeCallback() {
+            assertTrue(mTetheringConfigs.isEmpty());
+        }
+
+        public void assertStateChangeCallback() {
+            assertFalse(mTetherStates.isEmpty());
+        }
+
+        private void assertTetherConfigParcelEqual(@NonNull TetheringConfigurationParcel actual,
+                @NonNull TetheringConfigurationParcel expect) {
+            assertEquals(actual.subId, expect.subId);
+            assertArrayEquals(actual.tetherableUsbRegexs, expect.tetherableUsbRegexs);
+            assertArrayEquals(actual.tetherableWifiRegexs, expect.tetherableWifiRegexs);
+            assertArrayEquals(actual.tetherableBluetoothRegexs, expect.tetherableBluetoothRegexs);
+            assertEquals(actual.isDunRequired, expect.isDunRequired);
+            assertEquals(actual.chooseUpstreamAutomatically, expect.chooseUpstreamAutomatically);
+            assertArrayEquals(actual.preferredUpstreamIfaceTypes,
+                    expect.preferredUpstreamIfaceTypes);
+            assertArrayEquals(actual.legacyDhcpRanges, expect.legacyDhcpRanges);
+            assertArrayEquals(actual.defaultIPv4DNS, expect.defaultIPv4DNS);
+            assertEquals(actual.enableLegacyDhcpServer, expect.enableLegacyDhcpServer);
+            assertArrayEquals(actual.provisioningApp, expect.provisioningApp);
+            assertEquals(actual.provisioningAppNoUi, expect.provisioningAppNoUi);
+            assertEquals(actual.provisioningCheckPeriod, expect.provisioningCheckPeriod);
+        }
     }
 
     @Test
-    public void testRegisterTetheringEventCallback() throws Exception {
-        TestTetheringEventCallback callback1 = new TestTetheringEventCallback();
-        TestTetheringEventCallback callback2 = new TestTetheringEventCallback();
+    public void testRegisterTetherInternalCallback() throws Exception {
+        TestTetherInternalCallback callback = new TestTetherInternalCallback();
 
-        // 1. Register one callback and run usb tethering.
-        mTethering.registerTetheringEventCallback(callback1);
+        // 1. Register one callback before running any tethering.
+        mTethering.registerTetherInternalCallback(callback);
         mLooper.dispatchAll();
-        callback1.expectUpstreamChanged(new Network[] {null});
+        callback.expectUpstreamChanged(new Network[] {null});
+        callback.expectConfigurationChanged(
+                mTethering.getTetheringConfiguration().toStableParcelable());
+        TetherStatesParcel tetherState = callback.pollTetherStatesChanged();
+        assertEquals(tetherState, null);
+        // 2. Enable wifi tethering
         NetworkState upstreamState = buildMobileDualStackUpstreamState();
-        runUsbTethering(upstreamState);
-        callback1.expectUpstreamChanged(upstreamState.network);
-        // 2. Register second callback.
-        mTethering.registerTetheringEventCallback(callback2);
-        mLooper.dispatchAll();
-        callback2.expectUpstreamChanged(upstreamState.network);
-        // 3. Disable usb tethering.
-        mTethering.stopTethering(TETHERING_USB);
-        mLooper.dispatchAll();
-        sendUsbBroadcast(false, false, false);
-        mLooper.dispatchAll();
-        callback1.expectUpstreamChanged(new Network[] {null});
-        callback2.expectUpstreamChanged(new Network[] {null});
-        // 4. Unregister first callback and run hotspot.
-        mTethering.unregisterTetheringEventCallback(callback1);
-        mLooper.dispatchAll();
         when(mUpstreamNetworkMonitor.getCurrentPreferredUpstream()).thenReturn(upstreamState);
         when(mUpstreamNetworkMonitor.selectPreferredUpstreamType(any()))
                 .thenReturn(upstreamState);
         when(mWifiManager.startSoftAp(any(WifiConfiguration.class))).thenReturn(true);
-        mTethering.startTethering(TETHERING_WIFI, null, false);
-        mLooper.dispatchAll();
         mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
+        mLooper.dispatchAll();
+        tetherState = callback.pollTetherStatesChanged();
+        assertArrayEquals(tetherState.availableList, new String[] {TEST_WLAN_IFNAME});
+
+        mTethering.startTethering(TETHERING_WIFI, null, false);
         sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
         mLooper.dispatchAll();
-        callback1.assertNoCallback();
-        callback2.expectUpstreamChanged(upstreamState.network);
+        tetherState = callback.pollTetherStatesChanged();
+        assertArrayEquals(tetherState.tetheredList, new String[] {TEST_WLAN_IFNAME});
+        callback.expectUpstreamChanged(upstreamState.network);
+
+        // 3. Disable wifi tethering.
+        mTethering.stopTethering(TETHERING_WIFI);
+        sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLED);
+        mLooper.dispatchAll();
+        tetherState = callback.pollTetherStatesChanged();
+        assertArrayEquals(tetherState.availableList, new String[] {TEST_WLAN_IFNAME});
+        mLooper.dispatchAll();
+        callback.expectUpstreamChanged(new Network[] {null});
     }
 
     @Test
@@ -1091,7 +1206,7 @@
         verify(mUpstreamNetworkMonitor, times(1)).startObserveAllNetworks();
         // This will be called twice, one is on entering IpServer.STATE_AVAILABLE,
         // and another one is on IpServer.STATE_TETHERED/IpServer.STATE_LOCAL_ONLY.
-        assertEquals(2, mTetheringDependencies.isTetheringSupportedCalls);
+        assertEquals(2, mTetheringDependencies.mIsTetheringSupportedCalls);
 
         assertEquals(TETHER_ERROR_NO_ERROR, mTethering.getLastTetherError(TEST_P2P_IFNAME));
 
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 084a747..4e75e00 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -20,7 +20,6 @@
         ":vold_aidl",
         ":gsiservice_aidl",
         ":platform-compat-config",
-        ":tethering-servicescore-srcs",
         "java/com/android/server/EventLogTags.logtags",
         "java/com/android/server/am/EventLogTags.logtags",
         "java/com/android/server/policy/EventLogTags.logtags",
@@ -50,7 +49,6 @@
         "android.hardware.biometrics.face-V1.0-java",
         "android.hardware.biometrics.fingerprint-V2.1-java",
         "android.hardware.oemlock-V1.0-java",
-        "android.hardware.tetheroffload.control-V1.0-java",
         "android.hardware.configstore-V1.0-java",
         "android.hardware.contexthub-V1.0-java",
         "android.hidl.manager-V1.2-java",
@@ -82,11 +80,3 @@
     name: "gps_debug.conf",
     src: "java/com/android/server/location/gps_debug.conf",
 }
-
-// TODO: this should be removed after tethering migration done.
-filegroup {
-    name: "servicescore-tethering-src",
-    srcs: [
-        "java/com/android/server/connectivity/MockableSystemProperties.java",
-    ],
-}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index bb7406a..a3a6172 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -108,6 +108,7 @@
 import android.net.ProxyInfo;
 import android.net.RouteInfo;
 import android.net.SocketKeepalive;
+import android.net.TetheringManager;
 import android.net.UidRange;
 import android.net.Uri;
 import android.net.VpnService;
@@ -187,9 +188,7 @@
 import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
 import com.android.server.connectivity.PermissionMonitor;
 import com.android.server.connectivity.ProxyTracker;
-import com.android.server.connectivity.Tethering;
 import com.android.server.connectivity.Vpn;
-import com.android.server.connectivity.tethering.TetheringDependencies;
 import com.android.server.net.BaseNetdEventCallback;
 import com.android.server.net.BaseNetworkObserver;
 import com.android.server.net.LockdownVpnTracker;
@@ -233,7 +232,6 @@
 
     private static final String DIAG_ARG = "--diag";
     public static final String SHORT_ARG = "--short";
-    private static final String TETHERING_ARG = "tethering";
     private static final String NETWORK_ARG = "networks";
     private static final String REQUEST_ARG = "requests";
 
@@ -280,7 +278,7 @@
 
     private MockableSystemProperties mSystemProperties;
 
-    private Tethering mTethering;
+    private TetheringManager mTetheringManager;
 
     @VisibleForTesting
     protected final PermissionMonitor mPermissionMonitor;
@@ -869,15 +867,10 @@
         }
 
         /**
-         * @see Tethering
+         * Get a reference to the TetheringManager.
          */
-        public Tethering makeTethering(@NonNull Context context,
-                @NonNull INetworkManagementService nms,
-                @NonNull INetworkStatsService statsService,
-                @NonNull INetworkPolicyManager policyManager,
-                @NonNull TetheringDependencies tetheringDeps) {
-            return new Tethering(context, nms, statsService, policyManager,
-                    IoThread.get().getLooper(), getSystemProperties(), tetheringDeps);
+        public TetheringManager getTetheringManager() {
+            return TetheringManager.getInstance();
         }
 
         /**
@@ -932,6 +925,10 @@
             return IIpConnectivityMetrics.Stub.asInterface(
                     ServiceManager.getService(IpConnectivityLog.SERVICE_NAME));
         }
+
+        public IBatteryStats getBatteryStatsService() {
+            return BatteryStatsService.getService();
+        }
     }
 
     public ConnectivityService(Context context, INetworkManagementService netManager,
@@ -1075,8 +1072,7 @@
 
         mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
 
-        mTethering = deps.makeTethering(mContext, mNMS, mStatsService, mPolicyManager,
-                makeTetheringDependencies());
+        mTetheringManager = mDeps.getTetheringManager();
 
         mPermissionMonitor = new PermissionMonitor(mContext, mNetd);
 
@@ -1111,7 +1107,6 @@
                 mHandler);
 
         try {
-            mNMS.registerObserver(mTethering);
             mNMS.registerObserver(mDataActivityObserver);
         } catch (RemoteException e) {
             loge("Error registering observer :" + e);
@@ -1145,19 +1140,6 @@
         registerPrivateDnsSettingsCallbacks();
     }
 
-    private TetheringDependencies makeTetheringDependencies() {
-        return new TetheringDependencies() {
-            @Override
-            public boolean isTetheringSupported() {
-                return ConnectivityService.this.isTetheringSupported();
-            }
-            @Override
-            public NetworkRequest getDefaultNetworkRequest() {
-                return mDefaultRequest;
-            }
-        };
-    }
-
     private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) {
         final NetworkCapabilities netCap = new NetworkCapabilities();
         netCap.addCapability(NET_CAPABILITY_INTERNET);
@@ -1909,7 +1891,9 @@
             // TODO: relocate this specific callback in Tethering.
             if (restrictBackground) {
                 log("onRestrictBackgroundChanged(true): disabling tethering");
-                mTethering.untetherAll();
+                mTetheringManager.stopTethering(ConnectivityManager.TETHERING_WIFI);
+                mTetheringManager.stopTethering(ConnectivityManager.TETHERING_USB);
+                mTetheringManager.stopTethering(ConnectivityManager.TETHERING_BLUETOOTH);
             }
         }
     };
@@ -2164,7 +2148,7 @@
                     opts.setMaxManifestReceiverApiLevel(Build.VERSION_CODES.M);
                     options = opts.toBundle();
                 }
-                final IBatteryStats bs = BatteryStatsService.getService();
+                final IBatteryStats bs = mDeps.getBatteryStatsService();
                 try {
                     bs.noteConnectivityChanged(intent.getIntExtra(
                             ConnectivityManager.EXTRA_NETWORK_TYPE, ConnectivityManager.TYPE_NONE),
@@ -2193,7 +2177,6 @@
         mPermissionMonitor.startMonitoring();
         mProxyTracker.loadGlobalProxy();
         registerNetdEventCallback();
-        mTethering.systemReady();
 
         synchronized (this) {
             mSystemReady = true;
@@ -2405,9 +2388,6 @@
         if (ArrayUtils.contains(args, DIAG_ARG)) {
             dumpNetworkDiagnostics(pw);
             return;
-        } else if (ArrayUtils.contains(args, TETHERING_ARG)) {
-            mTethering.dump(fd, pw, args);
-            return;
         } else if (ArrayUtils.contains(args, NETWORK_ARG)) {
             dumpNetworks(pw);
             return;
@@ -2469,10 +2449,13 @@
         mLegacyTypeTracker.dump(pw);
 
         pw.println();
-        mTethering.dump(fd, pw, args);
+        mKeepaliveTracker.dump(pw);
 
         pw.println();
-        mKeepaliveTracker.dump(pw);
+        pw.println("TetheringManager logs:");
+        pw.increaseIndent();
+        TetheringManager.getInstance().dump(pw);
+        pw.decreaseIndent();
 
         pw.println();
         dumpAvoidBadWifiSettings(pw);
@@ -4004,7 +3987,7 @@
     public int tether(String iface, String callerPkg) {
         ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
         if (isTetheringSupported()) {
-            return mTethering.tether(iface);
+            return mTetheringManager.tether(iface);
         } else {
             return ConnectivityManager.TETHER_ERROR_UNSUPPORTED;
         }
@@ -4016,7 +3999,7 @@
         ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
 
         if (isTetheringSupported()) {
-            return mTethering.untether(iface);
+            return mTetheringManager.untether(iface);
         } else {
             return ConnectivityManager.TETHER_ERROR_UNSUPPORTED;
         }
@@ -4028,7 +4011,7 @@
         enforceTetherAccessPermission();
 
         if (isTetheringSupported()) {
-            return mTethering.getLastTetherError(iface);
+            return mTetheringManager.getLastTetherError(iface);
         } else {
             return ConnectivityManager.TETHER_ERROR_UNSUPPORTED;
         }
@@ -4039,7 +4022,7 @@
     public String[] getTetherableUsbRegexs() {
         enforceTetherAccessPermission();
         if (isTetheringSupported()) {
-            return mTethering.getTetherableUsbRegexs();
+            return mTetheringManager.getTetherableUsbRegexs();
         } else {
             return new String[0];
         }
@@ -4049,7 +4032,7 @@
     public String[] getTetherableWifiRegexs() {
         enforceTetherAccessPermission();
         if (isTetheringSupported()) {
-            return mTethering.getTetherableWifiRegexs();
+            return mTetheringManager.getTetherableWifiRegexs();
         } else {
             return new String[0];
         }
@@ -4059,7 +4042,7 @@
     public String[] getTetherableBluetoothRegexs() {
         enforceTetherAccessPermission();
         if (isTetheringSupported()) {
-            return mTethering.getTetherableBluetoothRegexs();
+            return mTetheringManager.getTetherableBluetoothRegexs();
         } else {
             return new String[0];
         }
@@ -4069,7 +4052,7 @@
     public int setUsbTethering(boolean enable, String callerPkg) {
         ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
         if (isTetheringSupported()) {
-            return mTethering.setUsbTethering(enable);
+            return mTetheringManager.setUsbTethering(enable);
         } else {
             return ConnectivityManager.TETHER_ERROR_UNSUPPORTED;
         }
@@ -4080,25 +4063,25 @@
     @Override
     public String[] getTetherableIfaces() {
         enforceTetherAccessPermission();
-        return mTethering.getTetherableIfaces();
+        return mTetheringManager.getTetherableIfaces();
     }
 
     @Override
     public String[] getTetheredIfaces() {
         enforceTetherAccessPermission();
-        return mTethering.getTetheredIfaces();
+        return mTetheringManager.getTetheredIfaces();
     }
 
     @Override
     public String[] getTetheringErroredIfaces() {
         enforceTetherAccessPermission();
-        return mTethering.getErroredIfaces();
+        return mTetheringManager.getTetheringErroredIfaces();
     }
 
     @Override
     public String[] getTetheredDhcpRanges() {
         enforceConnectivityInternalPermission();
-        return mTethering.getTetheredDhcpRanges();
+        return mTetheringManager.getTetheredDhcpRanges();
     }
 
     @Override
@@ -4126,7 +4109,8 @@
             Binder.restoreCallingIdentity(token);
         }
 
-        return tetherEnabledInSettings && adminUser && mTethering.hasTetherableConfiguration();
+        return tetherEnabledInSettings && adminUser
+                && mTetheringManager.hasTetherableConfiguration();
     }
 
     @Override
@@ -4137,13 +4121,13 @@
             receiver.send(ConnectivityManager.TETHER_ERROR_UNSUPPORTED, null);
             return;
         }
-        mTethering.startTethering(type, receiver, showProvisioningUi);
+        mTetheringManager.startTethering(type, receiver, showProvisioningUi);
     }
 
     @Override
     public void stopTethering(int type, String callerPkg) {
         ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
-        mTethering.stopTethering(type);
+        mTetheringManager.stopTethering(type);
     }
 
     /**
@@ -4157,7 +4141,8 @@
     public void getLatestTetheringEntitlementResult(int type, ResultReceiver receiver,
             boolean showEntitlementUi, String callerPkg) {
         ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
-        mTethering.getLatestTetheringEntitlementResult(type, receiver, showEntitlementUi);
+        mTetheringManager.requestLatestTetheringEntitlementResult(
+                type, receiver, showEntitlementUi);
     }
 
     /** Register tethering event callback. */
@@ -4165,7 +4150,7 @@
     public void registerTetheringEventCallback(ITetheringEventCallback callback,
             String callerPkg) {
         ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
-        mTethering.registerTetheringEventCallback(callback);
+        mTetheringManager.registerTetheringEventCallback(callback);
     }
 
     /** Unregister tethering event callback. */
@@ -4173,7 +4158,7 @@
     public void unregisterTetheringEventCallback(ITetheringEventCallback callback,
             String callerPkg) {
         ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
-        mTethering.unregisterTetheringEventCallback(callback);
+        mTetheringManager.unregisterTetheringEventCallback(callback);
     }
 
     // Called when we lose the default network and have no replacement yet.
@@ -5647,7 +5632,8 @@
         // are accurate.
         networkAgent.clatd.fixupLinkProperties(oldLp, newLp);
 
-        updateInterfaces(newLp, oldLp, netId, networkAgent.networkCapabilities);
+        updateInterfaces(newLp, oldLp, netId, networkAgent.networkCapabilities,
+                networkAgent.networkInfo.getType());
 
         // update filtering rules, need to happen after the interface update so netd knows about the
         // new interface (the interface name -> index map becomes initialized)
@@ -5726,21 +5712,26 @@
 
     }
 
-    private void updateInterfaces(LinkProperties newLp, LinkProperties oldLp, int netId,
-                                  NetworkCapabilities caps) {
-        CompareResult<String> interfaceDiff = new CompareResult<>(
+    private void updateInterfaces(final @Nullable LinkProperties newLp,
+            final @Nullable LinkProperties oldLp, final int netId,
+            final @Nullable NetworkCapabilities caps, final int legacyType) {
+        final CompareResult<String> interfaceDiff = new CompareResult<>(
                 oldLp != null ? oldLp.getAllInterfaceNames() : null,
                 newLp != null ? newLp.getAllInterfaceNames() : null);
-        for (String iface : interfaceDiff.added) {
-            try {
-                if (DBG) log("Adding iface " + iface + " to network " + netId);
-                mNMS.addInterfaceToNetwork(iface, netId);
-                wakeupModifyInterface(iface, caps, true);
-            } catch (Exception e) {
-                loge("Exception adding interface: " + e);
+        if (!interfaceDiff.added.isEmpty()) {
+            final IBatteryStats bs = mDeps.getBatteryStatsService();
+            for (final String iface : interfaceDiff.added) {
+                try {
+                    if (DBG) log("Adding iface " + iface + " to network " + netId);
+                    mNMS.addInterfaceToNetwork(iface, netId);
+                    wakeupModifyInterface(iface, caps, true);
+                    bs.noteNetworkInterfaceType(iface, legacyType);
+                } catch (Exception e) {
+                    loge("Exception adding interface: " + e);
+                }
             }
         }
-        for (String iface : interfaceDiff.removed) {
+        for (final String iface : interfaceDiff.removed) {
             try {
                 if (DBG) log("Removing iface " + iface + " from network " + netId);
                 wakeupModifyInterface(iface, caps, false);
@@ -6476,77 +6467,6 @@
         // do this after the default net is switched, but
         // before LegacyTypeTracker sends legacy broadcasts
         for (NetworkRequestInfo nri : addedRequests) notifyNetworkAvailable(newNetwork, nri);
-
-        // Linger any networks that are no longer needed. This should be done after sending the
-        // available callback for newNetwork.
-        for (NetworkAgentInfo nai : removedRequests) {
-            updateLingerState(nai, now);
-        }
-        // Possibly unlinger newNetwork. Unlingering a network does not send any callbacks so it
-        // does not need to be done in any particular order.
-        updateLingerState(newNetwork, now);
-
-        if (isNewDefault) {
-            // Maintain the illusion: since the legacy API only
-            // understands one network at a time, we must pretend
-            // that the current default network disconnected before
-            // the new one connected.
-            if (oldDefaultNetwork != null) {
-                mLegacyTypeTracker.remove(oldDefaultNetwork.networkInfo.getType(),
-                                          oldDefaultNetwork, true);
-            }
-            mDefaultInetConditionPublished = newNetwork.lastValidated ? 100 : 0;
-            mLegacyTypeTracker.add(newNetwork.networkInfo.getType(), newNetwork);
-            notifyLockdownVpn(newNetwork);
-        }
-
-        if (reassignedRequests.containsValue(newNetwork) || newNetwork.isVPN()) {
-            // Notify battery stats service about this network, both the normal
-            // interface and any stacked links.
-            // TODO: Avoid redoing this; this must only be done once when a network comes online.
-            try {
-                final IBatteryStats bs = BatteryStatsService.getService();
-                final int type = newNetwork.networkInfo.getType();
-
-                final String baseIface = newNetwork.linkProperties.getInterfaceName();
-                bs.noteNetworkInterfaceType(baseIface, type);
-                for (LinkProperties stacked : newNetwork.linkProperties.getStackedLinks()) {
-                    final String stackedIface = stacked.getInterfaceName();
-                    bs.noteNetworkInterfaceType(stackedIface, type);
-                }
-            } catch (RemoteException ignored) {
-            }
-
-            // This has to happen after the notifyNetworkCallbacks as that tickles each
-            // ConnectivityManager instance so that legacy requests correctly bind dns
-            // requests to this network.  The legacy users are listening for this broadcast
-            // and will generally do a dns request so they can ensureRouteToHost and if
-            // they do that before the callbacks happen they'll use the default network.
-            //
-            // TODO: Is there still a race here? We send the broadcast
-            // after sending the callback, but if the app can receive the
-            // broadcast before the callback, it might still break.
-            //
-            // This *does* introduce a race where if the user uses the new api
-            // (notification callbacks) and then uses the old api (getNetworkInfo(type))
-            // they may get old info.  Reverse this after the old startUsing api is removed.
-            // This is on top of the multiple intent sequencing referenced in the todo above.
-            for (int i = 0; i < newNetwork.numNetworkRequests(); i++) {
-                NetworkRequest nr = newNetwork.requestAt(i);
-                if (nr.legacyType != TYPE_NONE && nr.isRequest()) {
-                    // legacy type tracker filters out repeat adds
-                    mLegacyTypeTracker.add(nr.legacyType, newNetwork);
-                }
-            }
-
-            // A VPN generally won't get added to the legacy tracker in the "for (nri)" loop above,
-            // because usually there are no NetworkRequests it satisfies (e.g., mDefaultRequest
-            // wants the NOT_VPN capability, so it will never be satisfied by a VPN). So, add the
-            // newNetwork to the tracker explicitly (it's a no-op if it has already been added).
-            if (newNetwork.isVPN()) {
-                mLegacyTypeTracker.add(TYPE_VPN, newNetwork);
-            }
-        }
     }
 
     /**
@@ -6560,15 +6480,32 @@
         // requests. Once the code has switched to a request-major iteration style, this can
         // be optimized to only do the processing needed.
         final long now = SystemClock.elapsedRealtime();
+        final NetworkAgentInfo oldDefaultNetwork = getDefaultNetwork();
+
         final NetworkAgentInfo[] nais = mNetworkAgentInfos.values().toArray(
                 new NetworkAgentInfo[mNetworkAgentInfos.size()]);
         // Rematch higher scoring networks first to prevent requests first matching a lower
         // scoring network and then a higher scoring network, which could produce multiple
-        // callbacks and inadvertently unlinger networks.
+        // callbacks.
         Arrays.sort(nais);
-        for (NetworkAgentInfo nai : nais) {
+        for (final NetworkAgentInfo nai : nais) {
             rematchNetworkAndRequests(nai, now);
         }
+
+        final NetworkAgentInfo newDefaultNetwork = getDefaultNetwork();
+
+        for (final NetworkAgentInfo nai : nais) {
+            // Rematching may have altered the linger state of some networks, so update all linger
+            // timers. updateLingerState reads the state from the network agent and does nothing
+            // if the state has not changed : the source of truth is controlled with
+            // NetworkAgentInfo#lingerRequest and NetworkAgentInfo#unlingerRequest, which have been
+            // called while rematching the individual networks above.
+            updateLingerState(nai, now);
+        }
+
+        updateLegacyTypeTrackerAndVpnLockdownForRematch(oldDefaultNetwork, newDefaultNetwork, nais);
+
+        // Tear down all unneeded networks.
         for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
             if (unneeded(nai, UnneededFor.TEARDOWN)) {
                 if (nai.getLingerExpiry() > 0) {
@@ -6588,6 +6525,70 @@
         }
     }
 
+    private void updateLegacyTypeTrackerAndVpnLockdownForRematch(
+            @Nullable final NetworkAgentInfo oldDefaultNetwork,
+            @Nullable final NetworkAgentInfo newDefaultNetwork,
+            @NonNull final NetworkAgentInfo[] nais) {
+        if (oldDefaultNetwork != newDefaultNetwork) {
+            // Maintain the illusion : since the legacy API only understands one network at a time,
+            // if the default network changed, apps should see a disconnected broadcast for the
+            // old default network before they see a connected broadcast for the new one.
+            if (oldDefaultNetwork != null) {
+                mLegacyTypeTracker.remove(oldDefaultNetwork.networkInfo.getType(),
+                        oldDefaultNetwork, true);
+            }
+            if (newDefaultNetwork != null) {
+                // The new default network can be newly null if and only if the old default
+                // network doesn't satisfy the default request any more because it lost a
+                // capability.
+                mDefaultInetConditionPublished = newDefaultNetwork.lastValidated ? 100 : 0;
+                mLegacyTypeTracker.add(newDefaultNetwork.networkInfo.getType(), newDefaultNetwork);
+                // If the legacy VPN is connected, notifyLockdownVpn may end up sending a broadcast
+                // to reflect the NetworkInfo of this new network. This broadcast has to be sent
+                // after the disconnect broadcasts above, but before the broadcasts sent by the
+                // legacy type tracker below.
+                // TODO : refactor this, it's too complex
+                notifyLockdownVpn(newDefaultNetwork);
+            }
+        }
+
+        // Now that all the callbacks have been sent, send the legacy network broadcasts
+        // as needed. This is necessary so that legacy requests correctly bind dns
+        // requests to this network. The legacy users are listening for this broadcast
+        // and will generally do a dns request so they can ensureRouteToHost and if
+        // they do that before the callbacks happen they'll use the default network.
+        //
+        // TODO: Is there still a race here? The legacy broadcast will be sent after sending
+        // callbacks, but if apps can receive the broadcast before the callback, they still might
+        // have an inconsistent view of networking.
+        //
+        // This *does* introduce a race where if the user uses the new api
+        // (notification callbacks) and then uses the old api (getNetworkInfo(type))
+        // they may get old info. Reverse this after the old startUsing api is removed.
+        // This is on top of the multiple intent sequencing referenced in the todo above.
+        for (NetworkAgentInfo nai : nais) {
+            addNetworkToLegacyTypeTracker(nai);
+        }
+    }
+
+    private void addNetworkToLegacyTypeTracker(@NonNull final NetworkAgentInfo nai) {
+        for (int i = 0; i < nai.numNetworkRequests(); i++) {
+            NetworkRequest nr = nai.requestAt(i);
+            if (nr.legacyType != TYPE_NONE && nr.isRequest()) {
+                // legacy type tracker filters out repeat adds
+                mLegacyTypeTracker.add(nr.legacyType, nai);
+            }
+        }
+
+        // A VPN generally won't get added to the legacy tracker in the "for (nri)" loop above,
+        // because usually there are no NetworkRequests it satisfies (e.g., mDefaultRequest
+        // wants the NOT_VPN capability, so it will never be satisfied by a VPN). So, add the
+        // newNetwork to the tracker explicitly (it's a no-op if it has already been added).
+        if (nai.isVPN()) {
+            mLegacyTypeTracker.add(TYPE_VPN, nai);
+        }
+    }
+
     private void updateInetCondition(NetworkAgentInfo nai) {
         // Don't bother updating until we've graduated to validated at least once.
         if (!nai.everValidated) return;
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 24a5b7f..bb7f862 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -580,7 +580,7 @@
         // semantics of WakeupMessage guarantee that if cancel is called then the alarm will
         // never call its callback (handleLingerComplete), even if it has already fired.
         // WakeupMessage makes no such guarantees about rescheduling a message, so if mLingerMessage
-        // has already been dispatched, rescheduling to some time in the future it won't stop it
+        // has already been dispatched, rescheduling to some time in the future won't stop it
         // from calling its callback immediately.
         if (mLingerMessage != null) {
             mLingerMessage.cancel();
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java
deleted file mode 100644
index 4ad7ac4..0000000
--- a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2017 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.tethering;
-
-import android.content.Context;
-import android.net.NetworkRequest;
-import android.net.ip.IpServer;
-import android.net.util.SharedLog;
-import android.os.Handler;
-
-import com.android.internal.util.StateMachine;
-import com.android.server.connectivity.MockableSystemProperties;
-
-import java.util.ArrayList;
-
-
-/**
- * Capture tethering dependencies, for injection.
- *
- * @hide
- */
-public class TetheringDependencies {
-    /**
-     * Get a reference to the offload hardware interface to be used by tethering.
-     */
-    public OffloadHardwareInterface getOffloadHardwareInterface(Handler h, SharedLog log) {
-        return new OffloadHardwareInterface(h, log);
-    }
-
-    /**
-     * Get a reference to the UpstreamNetworkMonitor to be used by tethering.
-     */
-    public UpstreamNetworkMonitor getUpstreamNetworkMonitor(Context ctx, StateMachine target,
-            SharedLog log, int what) {
-        return new UpstreamNetworkMonitor(ctx, target, log, what);
-    }
-
-    /**
-     * Get a reference to the IPv6TetheringCoordinator to be used by tethering.
-     */
-    public IPv6TetheringCoordinator getIPv6TetheringCoordinator(
-            ArrayList<IpServer> notifyList, SharedLog log) {
-        return new IPv6TetheringCoordinator(notifyList, log);
-    }
-
-    /**
-     * Get dependencies to be used by IpServer.
-     */
-    public IpServer.Dependencies getIpServerDependencies() {
-        return new IpServer.Dependencies();
-    }
-
-    /**
-     * Indicates whether tethering is supported on the device.
-     */
-    public boolean isTetheringSupported() {
-        return true;
-    }
-
-    /**
-     * Get the NetworkRequest that should be fulfilled by the default network.
-     */
-    public NetworkRequest getDefaultNetworkRequest() {
-        return null;
-    }
-
-    /**
-     * Get a reference to the EntitlementManager to be used by tethering.
-     */
-    public EntitlementManager getEntitlementManager(Context ctx, StateMachine target,
-            SharedLog log, int what, MockableSystemProperties systemProperties) {
-        return new EntitlementManager(ctx, target, log, what, systemProperties);
-    }
-
-    /**
-     * Generate a new TetheringConfiguration according to input sub Id.
-     */
-    public TetheringConfiguration generateTetheringConfiguration(Context ctx, SharedLog log,
-            int subId) {
-        return new TetheringConfiguration(ctx, log, subId);
-    }
-}
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 08c9426..e473c96 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -138,7 +138,6 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
-import com.android.server.connectivity.Tethering;
 
 import java.io.File;
 import java.io.FileDescriptor;
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 7b3fbb9..2b1c07b 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -53,7 +53,6 @@
         "com_android_server_am_LowMemDetector.cpp",
         "onload.cpp",
         ":lib_networkStatsFactory_native",
-        ":tethering-jni-srcs",
     ],
 
     include_dirs: [
@@ -122,7 +121,6 @@
         "android.hardware.power@1.0",
         "android.hardware.power@1.1",
         "android.hardware.power.stats@1.0",
-        "android.hardware.tetheroffload.config@1.0",
         "android.hardware.thermal@1.0",
         "android.hardware.tv.cec@1.0",
         "android.hardware.tv.input@1.0",
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index efffa6c..692c9d2 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -40,7 +40,6 @@
 int register_android_server_VibratorService(JNIEnv* env);
 int register_android_server_location_GnssLocationProvider(JNIEnv* env);
 int register_android_server_connectivity_Vpn(JNIEnv* env);
-int register_android_server_connectivity_tethering_OffloadHardwareInterface(JNIEnv*);
 int register_android_server_TestNetworkService(JNIEnv* env);
 int register_android_server_devicepolicy_CryptoTestHelper(JNIEnv*);
 int register_android_server_hdmi_HdmiCecController(JNIEnv* env);
@@ -88,7 +87,6 @@
     register_android_server_SystemServer(env);
     register_android_server_location_GnssLocationProvider(env);
     register_android_server_connectivity_Vpn(env);
-    register_android_server_connectivity_tethering_OffloadHardwareInterface(env);
     register_android_server_TestNetworkService(env);
     register_android_server_devicepolicy_CryptoTestHelper(env);
     register_android_server_ConsumerIrService(env);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index def61bf..adf3218 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -40,6 +40,7 @@
 import android.hardware.display.DisplayManagerInternal;
 import android.net.ConnectivityModuleConnector;
 import android.net.NetworkStackClient;
+import android.net.TetheringManager;
 import android.os.BaseBundle;
 import android.os.Binder;
 import android.os.Build;
@@ -2175,6 +2176,15 @@
             }
             traceEnd();
 
+            traceBeginAndSlog("StartTethering");
+            try {
+                // Tethering must start after ConnectivityService and NetworkStack.
+                TetheringManager.getInstance().start();
+            } catch (Throwable e) {
+                reportWtf("starting Tethering", e);
+            }
+            traceEnd();
+
             traceBeginAndSlog("MakeLocationServiceReady");
             try {
                 if (locationF != null) {
diff --git a/services/net/Android.bp b/services/net/Android.bp
index 45430ea..2dabdb7 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -2,8 +2,8 @@
     name: "services.net",
     srcs: [
         ":net-module-utils-srcs",
-        ":tethering-servicesnet-srcs",
         "java/**/*.java",
+        ":tethering-manager",
     ],
     static_libs: [
         "dnsresolver_aidl_interface-V2-java",
diff --git a/tests/net/Android.bp b/tests/net/Android.bp
index 10f27e2..b2f384a 100644
--- a/tests/net/Android.bp
+++ b/tests/net/Android.bp
@@ -45,7 +45,6 @@
     name: "FrameworksNetTests",
     defaults: ["FrameworksNetTests-jni-defaults"],
     srcs: [
-        ":tethering-tests-src",
         "java/**/*.java",
         "java/**/*.kt",
     ],
diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
index 334b26d..25028fb 100644
--- a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
+++ b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -32,6 +32,7 @@
 import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
 import android.net.NetworkRequest
 import android.net.TestNetworkStackClient
+import android.net.TetheringManager
 import android.net.metrics.IpConnectivityLog
 import android.os.ConditionVariable
 import android.os.IBinder
@@ -48,7 +49,6 @@
 import com.android.server.connectivity.IpConnectivityMetrics
 import com.android.server.connectivity.MockableSystemProperties
 import com.android.server.connectivity.ProxyTracker
-import com.android.server.connectivity.Tethering
 import com.android.server.net.NetworkPolicyManagerInternal
 import com.android.testutils.TestableNetworkCallback
 import org.junit.After
@@ -169,8 +169,7 @@
         val deps = spy(ConnectivityService.Dependencies())
         doReturn(networkStackClient).`when`(deps).networkStack
         doReturn(metricsLogger).`when`(deps).metricsLogger
-        doReturn(mock(Tethering::class.java)).`when`(deps).makeTethering(
-                any(), any(), any(), any(), any())
+        doReturn(mock(TetheringManager::class.java)).`when`(deps).getTetheringManager()
         doReturn(mock(ProxyTracker::class.java)).`when`(deps).makeProxyTracker(any(), any())
         doReturn(mock(MockableSystemProperties::class.java)).`when`(deps).systemProperties
         doReturn(TestNetIdManager()).`when`(deps).makeNetIdManager()
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 7ea9bcf..c4e353b 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -96,6 +96,7 @@
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.startsWith;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.atLeastOnce;
@@ -163,6 +164,7 @@
 import android.net.ResolverParamsParcel;
 import android.net.RouteInfo;
 import android.net.SocketKeepalive;
+import android.net.TetheringManager;
 import android.net.UidRange;
 import android.net.metrics.IpConnectivityLog;
 import android.net.shared.NetworkMonitorUtils;
@@ -197,6 +199,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.app.IBatteryStats;
 import com.android.internal.net.VpnConfig;
 import com.android.internal.net.VpnInfo;
 import com.android.internal.util.ArrayUtils;
@@ -210,7 +213,6 @@
 import com.android.server.connectivity.Nat464Xlat;
 import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
 import com.android.server.connectivity.ProxyTracker;
-import com.android.server.connectivity.Tethering;
 import com.android.server.connectivity.Vpn;
 import com.android.server.net.NetworkPinner;
 import com.android.server.net.NetworkPolicyManagerInternal;
@@ -305,6 +307,7 @@
     @Mock DefaultNetworkMetrics mDefaultNetworkMetrics;
     @Mock INetworkManagementService mNetworkManagementService;
     @Mock INetworkStatsService mStatsService;
+    @Mock IBatteryStats mBatteryStatsService;
     @Mock INetworkPolicyManager mNpm;
     @Mock IDnsResolver mMockDnsResolver;
     @Mock INetd mMockNetd;
@@ -1130,11 +1133,12 @@
         doReturn(new TestNetIdManager()).when(deps).makeNetIdManager();
         doReturn(mNetworkStack).when(deps).getNetworkStack();
         doReturn(systemProperties).when(deps).getSystemProperties();
-        doReturn(mock(Tethering.class)).when(deps).makeTethering(any(), any(), any(), any(), any());
+        doReturn(mock(TetheringManager.class)).when(deps).getTetheringManager();
         doReturn(mock(ProxyTracker.class)).when(deps).makeProxyTracker(any(), any());
         doReturn(mMetricsService).when(deps).getMetricsLogger();
         doReturn(true).when(deps).queryUserAccess(anyInt(), anyInt());
         doReturn(mIpConnectivityMetrics).when(deps).getIpConnectivityMetrics();
+        doReturn(mBatteryStatsService).when(deps).getBatteryStatsService();
         doReturn(true).when(deps).hasService(Context.ETHERNET_SERVICE);
         doAnswer(inv -> {
             mPolicyTracker = new WrappedMultinetworkPolicyTracker(
@@ -5640,6 +5644,36 @@
         mCm.unregisterNetworkCallback(defaultCallback);
     }
 
+    @Test
+    public final void testBatteryStatsNetworkType() throws Exception {
+        final LinkProperties cellLp = new LinkProperties();
+        cellLp.setInterfaceName("cell0");
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
+        mCellNetworkAgent.connect(true);
+        waitForIdle();
+        verify(mBatteryStatsService).noteNetworkInterfaceType(cellLp.getInterfaceName(),
+                TYPE_MOBILE);
+        reset(mBatteryStatsService);
+
+        final LinkProperties wifiLp = new LinkProperties();
+        wifiLp.setInterfaceName("wifi0");
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
+        mWiFiNetworkAgent.connect(true);
+        waitForIdle();
+        verify(mBatteryStatsService).noteNetworkInterfaceType(wifiLp.getInterfaceName(),
+                TYPE_WIFI);
+        reset(mBatteryStatsService);
+
+        mCellNetworkAgent.disconnect();
+
+        cellLp.setInterfaceName("wifi0");
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
+        mCellNetworkAgent.connect(true);
+        waitForIdle();
+        verify(mBatteryStatsService).noteNetworkInterfaceType(cellLp.getInterfaceName(),
+                TYPE_MOBILE);
+    }
+
     /**
      * Make simulated InterfaceConfig for Nat464Xlat to query clat lower layer info.
      */
@@ -5680,25 +5714,28 @@
         mCm.registerNetworkCallback(networkRequest, networkCallback);
 
         // Prepare ipv6 only link properties.
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        final int cellNetId = mCellNetworkAgent.getNetwork().netId;
         final LinkProperties cellLp = new LinkProperties();
         cellLp.setInterfaceName(MOBILE_IFNAME);
         cellLp.addLinkAddress(myIpv6);
         cellLp.addRoute(new RouteInfo((IpPrefix) null, myIpv6.getAddress(), MOBILE_IFNAME));
         cellLp.addRoute(new RouteInfo(myIpv6, null, MOBILE_IFNAME));
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
         reset(mNetworkManagementService);
         reset(mMockDnsResolver);
         reset(mMockNetd);
+        reset(mBatteryStatsService);
         when(mNetworkManagementService.getInterfaceConfig(CLAT_PREFIX + MOBILE_IFNAME))
                 .thenReturn(getClatInterfaceConfig(myIpv4));
 
         // Connect with ipv6 link properties. Expect prefix discovery to be started.
-        mCellNetworkAgent.sendLinkProperties(cellLp);
         mCellNetworkAgent.connect(true);
+        final int cellNetId = mCellNetworkAgent.getNetwork().netId;
+        waitForIdle();
 
         verify(mMockNetd, times(1)).networkCreatePhysical(eq(cellNetId), anyInt());
         verify(mMockDnsResolver, times(1)).createNetworkCache(eq(cellNetId));
+        verify(mBatteryStatsService).noteNetworkInterfaceType(cellLp.getInterfaceName(),
+                TYPE_MOBILE);
 
         networkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId);
@@ -5714,6 +5751,11 @@
         verify(mMockDnsResolver, times(1)).stopPrefix64Discovery(cellNetId);
         verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration(any());
 
+        // Make sure BatteryStats was not told about any v4- interfaces, as none should have
+        // come online yet.
+        waitForIdle();
+        verify(mBatteryStatsService, never()).noteNetworkInterfaceType(startsWith("v4-"), anyInt());
+
         verifyNoMoreInteractions(mMockNetd);
         verifyNoMoreInteractions(mMockDnsResolver);
         reset(mMockNetd);
@@ -5760,6 +5802,11 @@
         assertEquals(1, resolvrParams.servers.length);
         assertTrue(ArrayUtils.contains(resolvrParams.servers, "8.8.8.8"));
 
+        for (final LinkProperties stackedLp : stackedLpsAfterChange) {
+            verify(mBatteryStatsService).noteNetworkInterfaceType(stackedLp.getInterfaceName(),
+                    TYPE_MOBILE);
+        }
+
         // Add ipv4 address, expect that clatd and prefix discovery are stopped and stacked
         // linkproperties are cleaned up.
         cellLp.addLinkAddress(myIpv4);