Merge "Refactor FakeIcing to use the real Icing lib's protos."
diff --git a/Android.bp b/Android.bp
index 303cff9..c1f6860 100644
--- a/Android.bp
+++ b/Android.bp
@@ -225,7 +225,6 @@
         ":framework-mca-filterfw-sources",
         ":framework-mca-filterpacks-sources",
         ":framework-media-sources",
-        ":framework-mime-sources",
         ":framework-mms-sources",
         ":framework-opengl-sources",
         ":framework-rs-sources",
@@ -267,10 +266,19 @@
 }
 
 filegroup {
+    name: "framework-updatable-sources",
+    srcs: [
+        ":framework-sdkext-sources",
+        ":updatable-media-srcs",
+    ]
+}
+
+filegroup {
     name: "framework-all-sources",
     srcs: [
+        ":framework-mime-sources",
         ":framework-non-updatable-sources",
-        ":updatable-media-srcs",
+        ":framework-updatable-sources",
     ],
 }
 
@@ -375,7 +383,6 @@
 
     static_libs: [
         "framework-internal-utils",
-        "mimemap",
     ],
 
     dxflags: [
@@ -438,6 +445,11 @@
         "media-provider-platform-compat-config",
         "services-devicepolicy-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",
 }
@@ -572,6 +584,12 @@
     ],
 }
 
+java_library {
+    name: "framework-annotations-lib",
+    srcs: [ ":framework-annotations" ],
+    sdk_version: "current",
+}
+
 filegroup {
     name: "framework-networkstack-shared-srcs",
     srcs: [
@@ -772,7 +790,7 @@
 filegroup {
     name: "incremental_aidl",
     srcs: [
-        "core/java/android/os/incremental/IIncrementalService.aidl",
+        "core/java/android/os/incremental/IIncrementalManager.aidl",
         "core/java/android/os/incremental/IIncrementalServiceProxy.aidl",
         "core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl",
         "core/java/android/os/incremental/IncrementalFileSystemControlParcel.aidl",
@@ -1015,14 +1033,15 @@
 stubs_defaults {
     name: "framework-doc-stubs-default",
     srcs: [
+        ":framework-mime-sources",
         ":framework-non-updatable-sources",
+        ":framework-updatable-sources",
         "core/java/**/*.logtags",
         "test-base/src/**/*.java",
         ":opt-telephony-srcs",
         ":opt-net-voip-srcs",
         ":core-current-stubs-source",
         ":core_public_api_files",
-        ":updatable-media-srcs",
         "test-mock/src/**/*.java",
         "test-runner/src/**/*.java",
     ],
@@ -1080,12 +1099,12 @@
     name: "metalava-api-stubs-default",
     srcs: [
         ":framework-non-updatable-sources",
+        ":framework-updatable-sources",
         "core/java/**/*.logtags",
         ":opt-telephony-srcs",
         ":opt-net-voip-srcs",
         ":core-current-stubs-source",
         ":core_public_api_files",
-        ":updatable-media-srcs",
         ":ike-api-srcs",
     ],
     libs: ["framework-internal-utils"],
@@ -1620,7 +1639,6 @@
         "core/java/com/android/internal/util/State.java",
         "core/java/com/android/internal/util/StateMachine.java",
         "core/java/com/android/internal/util/UserIcons.java",
-        "core/java/com/android/internal/util/XmlUtils.java",
     ],
 }
 
diff --git a/apex/sdkext/Android.bp b/apex/sdkext/Android.bp
new file mode 100644
index 0000000..b8dcb90
--- /dev/null
+++ b/apex/sdkext/Android.bp
@@ -0,0 +1,32 @@
+// 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",
+    java_libs: [ "framework-sdkext" ],
+    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..b17f0f8
--- /dev/null
+++ b/apex/sdkext/framework/Android.bp
@@ -0,0 +1,30 @@
+// 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",
+}
+
+java_library {
+    name: "framework-sdkext",
+    srcs: [ ":framework-sdkext-sources" ],
+    sdk_version: "system_current",
+    libs: [ "framework-annotations-lib" ],
+    permitted_packages: [ "android.os.ext" ],
+    installable: true,
+}
diff --git a/apex/sdkext/framework/java/android/os/ext/SdkExtensions.java b/apex/sdkext/framework/java/android/os/ext/SdkExtensions.java
new file mode 100644
index 0000000..c039a82
--- /dev/null
+++ b/apex/sdkext/framework/java/android/os/ext/SdkExtensions.java
@@ -0,0 +1,53 @@
+/*
+ * 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.os.ext;
+
+import android.annotation.IntDef;
+import android.os.Build.VERSION_CODES;
+import android.os.SystemProperties;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** @hide */
+public class SdkExtensions {
+
+    private static final int R_EXTENSION_INT;
+    static {
+        R_EXTENSION_INT = SystemProperties.getInt("persist.com.android.sdkext.sdk_info", 0);
+    }
+
+    /** Values suitable as parameters for {@link #getExtensionVersion(int)}. */
+    @IntDef(value = { VERSION_CODES.R })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SdkVersion {}
+
+    /**
+     * Return the version of the extension to the given SDK.
+     *
+     * @param sdk the SDK version to get the extension version of.
+     * @see SdkVersion
+     * @throws IllegalArgumentException if sdk is not an sdk version with extensions
+     */
+    public static int getExtensionVersion(@SdkVersion int sdk) {
+        if (sdk < VERSION_CODES.R) {
+            throw new IllegalArgumentException();
+        }
+        return R_EXTENSION_INT;
+    }
+
+}
diff --git a/apex/sdkext/framework/java/android/os/ext/package.html b/apex/sdkext/framework/java/android/os/ext/package.html
new file mode 100644
index 0000000..34c1697
--- /dev/null
+++ b/apex/sdkext/framework/java/android/os/ext/package.html
@@ -0,0 +1,5 @@
+<HTML>
+<BODY>
+Provides APIs to interface with the SDK extensions.
+</BODY>
+</HTML>
diff --git a/apex/sdkext/framework/tests/Android.bp b/apex/sdkext/framework/tests/Android.bp
new file mode 100644
index 0000000..3d5dbb3
--- /dev/null
+++ b/apex/sdkext/framework/tests/Android.bp
@@ -0,0 +1,10 @@
+android_test {
+    name: "framework-sdkext-tests",
+    srcs: ["src/**/*.java"],
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
+    static_libs: [ "framework-sdkext" ],
+    platform_apis: true,
+}
diff --git a/apex/sdkext/framework/tests/AndroidManifest.xml b/apex/sdkext/framework/tests/AndroidManifest.xml
new file mode 100644
index 0000000..831f132
--- /dev/null
+++ b/apex/sdkext/framework/tests/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.sdkext.tests">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.test.InstrumentationTestRunner"
+            android:targetPackage="com.android.sdkext.tests" />
+
+</manifest>
diff --git a/apex/sdkext/framework/tests/src/android/os/ext/SdkExtensionsTest.java b/apex/sdkext/framework/tests/src/android/os/ext/SdkExtensionsTest.java
new file mode 100644
index 0000000..6885110
--- /dev/null
+++ b/apex/sdkext/framework/tests/src/android/os/ext/SdkExtensionsTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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.os.ext;
+
+import android.os.Build;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import junit.framework.TestCase;
+
+public class SdkExtensionsTest extends TestCase {
+
+    @SmallTest
+    public void testBadArgument() throws Exception {
+        try {
+            SdkExtensions.getExtensionVersion(Build.VERSION_CODES.Q);
+            fail("expected IllegalArgumentException");
+        } catch (IllegalArgumentException expected) { }
+
+        try {
+            SdkExtensions.getExtensionVersion(999999);
+            fail("expected IllegalArgumentException");
+        } catch (IllegalArgumentException expected) { }
+    }
+
+    @SmallTest
+    public void testDefault() throws Exception {
+        int r = SdkExtensions.getExtensionVersion(Build.VERSION_CODES.R);
+        assertTrue(r >= 0);
+    }
+
+}
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 aefe2097..b1eac59 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -28928,6 +28928,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();
@@ -28938,6 +28939,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;
   }
@@ -30569,6 +30571,7 @@
     method @Nullable public android.net.wifi.p2p.WifiP2pWfdInfo getWfdInfo();
     method public boolean isGroupOwner();
     method public boolean isServiceDiscoveryCapable();
+    method public void update(@NonNull android.net.wifi.p2p.WifiP2pDevice);
     method public boolean wpsDisplaySupported();
     method public boolean wpsKeypadSupported();
     method public boolean wpsPbcSupported();
diff --git a/api/system-current.txt b/api/system-current.txt
index aeb89b2..7cd31df 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -828,6 +828,7 @@
     method @RequiresPermission(android.Manifest.permission.BACKUP) public void backupNow();
     method @RequiresPermission(android.Manifest.permission.BACKUP) public android.app.backup.RestoreSession beginRestoreSession();
     method @RequiresPermission(android.Manifest.permission.BACKUP) public void cancelBackups();
+    method @RequiresPermission(android.Manifest.permission.BACKUP) public void excludeKeysFromRestore(@NonNull String, @NonNull java.util.List<java.lang.String>);
     method @RequiresPermission(android.Manifest.permission.BACKUP) public long getAvailableRestoreToken(String);
     method @RequiresPermission(android.Manifest.permission.BACKUP) public android.content.Intent getConfigurationIntent(String);
     method @RequiresPermission(android.Manifest.permission.BACKUP) public String getCurrentTransport();
@@ -4378,6 +4379,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();
@@ -4386,6 +4388,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;
   }
 
@@ -4438,7 +4441,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();
@@ -4452,7 +4454,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);
@@ -5577,6 +5578,7 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public java.util.List<java.lang.Integer> getAvailableChannels(int);
     method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public boolean getScanResults();
     method @NonNull @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public java.util.List<android.net.wifi.ScanResult> getSingleScanResults();
+    method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void registerScanListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiScanner.ScanListener);
     method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setScanningEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void startBackgroundScan(android.net.wifi.WifiScanner.ScanSettings, android.net.wifi.WifiScanner.ScanListener);
     method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void startBackgroundScan(android.net.wifi.WifiScanner.ScanSettings, android.net.wifi.WifiScanner.ScanListener, android.os.WorkSource);
@@ -5588,6 +5590,7 @@
     method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void stopScan(android.net.wifi.WifiScanner.ScanListener);
     method @Deprecated public void stopTrackingBssids(android.net.wifi.WifiScanner.BssidListener);
     method @Deprecated public void stopTrackingWifiChange(android.net.wifi.WifiScanner.WifiChangeListener);
+    method public void unregisterScanListener(@NonNull android.net.wifi.WifiScanner.ScanListener);
     field public static final int MAX_SCAN_PERIOD_MS = 1024000; // 0xfa000
     field public static final int MIN_SCAN_PERIOD_MS = 1000; // 0x3e8
     field public static final int REASON_DUPLICATE_REQEUST = -5; // 0xfffffffb
@@ -5600,6 +5603,9 @@
     field public static final int REPORT_EVENT_AFTER_EACH_SCAN = 1; // 0x1
     field public static final int REPORT_EVENT_FULL_SCAN_RESULT = 2; // 0x2
     field public static final int REPORT_EVENT_NO_BATCH = 4; // 0x4
+    field public static final int SCAN_TYPE_HIGH_ACCURACY = 2; // 0x2
+    field public static final int SCAN_TYPE_LOW_LATENCY = 0; // 0x0
+    field public static final int SCAN_TYPE_LOW_POWER = 1; // 0x1
     field public static final int WIFI_BAND_24_GHZ = 1; // 0x1
     field public static final int WIFI_BAND_5_GHZ = 2; // 0x2
     field public static final int WIFI_BAND_5_GHZ_DFS_ONLY = 4; // 0x4
@@ -5668,6 +5674,7 @@
     ctor public WifiScanner.ScanSettings();
     field public int band;
     field public android.net.wifi.WifiScanner.ChannelSpec[] channels;
+    field @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public final java.util.List<android.net.wifi.WifiScanner.ScanSettings.HiddenNetwork> hiddenNetworks;
     field public boolean hideFromAppOps;
     field public boolean ignoreLocationSettings;
     field public int maxPeriodInMs;
@@ -5676,6 +5683,12 @@
     field public int periodInMs;
     field public int reportEvents;
     field public int stepCount;
+    field @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public int type;
+  }
+
+  public static class WifiScanner.ScanSettings.HiddenNetwork {
+    ctor public WifiScanner.ScanSettings.HiddenNetwork(@NonNull String);
+    field @NonNull public final String ssid;
   }
 
   @Deprecated public static interface WifiScanner.WifiChangeListener extends android.net.wifi.WifiScanner.ActionListener {
diff --git a/api/system-lint-baseline.txt b/api/system-lint-baseline.txt
index ab0f0f9..fcf5178 100644
--- a/api/system-lint-baseline.txt
+++ b/api/system-lint-baseline.txt
@@ -65,7 +65,7 @@
 MissingNullability: android.media.tv.TvRecordingClient.RecordingCallback#onEvent(String, String, android.os.Bundle) parameter #1:
     
 MissingNullability: android.media.tv.TvRecordingClient.RecordingCallback#onEvent(String, String, android.os.Bundle) parameter #2:
-    
+
 MissingNullability: android.net.wifi.rtt.RangingRequest.Builder#addResponder(android.net.wifi.rtt.ResponderConfig):
     
 MissingNullability: android.printservice.recommendation.RecommendationService#attachBaseContext(android.content.Context) parameter #0:
@@ -181,6 +181,8 @@
     Bare field saePasswordId must be marked final, or moved behind accessors if mutable
 MutableBareField: android.net.wifi.WifiConfiguration#shared:
     Bare field shared must be marked final, or moved behind accessors if mutable
+MutableBareField: android.net.wifi.WifiScanner.ScanSettings#type:
+    Bare field type must be marked final, or moved behind accessors if mutable
 
 
 NoClone: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #0:
diff --git a/api/test-current.txt b/api/test-current.txt
index 9445322..1b08139 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1466,7 +1466,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();
@@ -1480,7 +1479,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);
@@ -4274,6 +4272,7 @@
     field public static final String PERSIST_PREFIX = "persist.sys.fflag.override.";
     field public static final String SCREENRECORD_LONG_PRESS = "settings_screenrecord_long_press";
     field public static final String SEAMLESS_TRANSFER = "settings_seamless_transfer";
+    field public static final String SETTINGS_FUSE_FLAG = "settings_fuse";
     field public static final String SETTINGS_WIFITRACKER2 = "settings_wifitracker2";
   }
 
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/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index d9b3d3b..1829f74 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -34,6 +34,7 @@
 import android.app.slice.SliceManager;
 import android.app.timedetector.TimeDetector;
 import android.app.timezone.RulesManager;
+import android.app.timezonedetector.TimeZoneDetector;
 import android.app.trust.TrustManager;
 import android.app.usage.IStorageStatsManager;
 import android.app.usage.IUsageStatsManager;
@@ -1125,6 +1126,14 @@
                         return new TimeDetector();
                     }});
 
+        registerService(Context.TIME_ZONE_DETECTOR_SERVICE, TimeZoneDetector.class,
+                new CachedServiceFetcher<TimeZoneDetector>() {
+                    @Override
+                    public TimeZoneDetector createService(ContextImpl ctx)
+                            throws ServiceNotFoundException {
+                        return new TimeZoneDetector();
+                    }});
+
         registerService(Context.PERMISSION_SERVICE, PermissionManager.class,
                 new CachedServiceFetcher<PermissionManager>() {
                     @Override
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 73980a5..47fd87d 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -6363,6 +6363,9 @@
     /**
      * Sets the device owner information to be shown on the lock screen.
      * <p>
+     * Device owner information set using this method overrides any owner information manually set
+     * by the user and prevents the user from further changing it.
+     * <p>
      * If the device owner information is {@code null} or empty then the device owner info is
      * cleared and the user owner info is shown on the lock screen if it is set.
      * <p>
@@ -6372,6 +6375,8 @@
      * If the device owner information needs to be localized, it is the responsibility of the
      * {@link DeviceAdminReceiver} to listen to the {@link Intent#ACTION_LOCALE_CHANGED} broadcast
      * and set a new version of this string accordingly.
+     * <p>
+     * May be called by the device owner or the profile owner of an organization-owned device.
      *
      * @param admin The name of the admin component to check.
      * @param info Device owner information which will be displayed instead of the user owner info.
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index 25caaaa..93d1e71 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -34,6 +34,8 @@
 import android.util.Log;
 import android.util.Pair;
 
+import java.util.List;
+
 /**
  * The interface through which an application interacts with the Android backup service to
  * request backup and restore operations.
@@ -948,6 +950,29 @@
         return null;
     }
 
+    /**
+     * Excludes keys from KV restore for a given package. The corresponding data will be excluded
+     * from the data set available the backup agent during restore. However,  final list  of keys
+     * that have been excluded will be passed to the agent to make it aware of the exclusions.
+     *
+     * @param packageName The name of the package for which to exclude keys.
+     * @param keys The list of keys to exclude.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.BACKUP)
+    public void excludeKeysFromRestore(@NonNull String packageName, @NonNull List<String> keys) {
+        checkServiceBinder();
+        if (sService != null) {
+            try {
+                sService.excludeKeysFromRestore(packageName, keys);
+            } catch (RemoteException e) {
+                Log.e(TAG, "excludeKeysFromRestore() couldn't connect");
+            }
+        }
+    }
+
     /*
      * We wrap incoming binder calls with a private class implementation that
      * redirects them into main-thread actions.  This serializes the backup
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index 2dfaad7..099272d 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -710,4 +710,10 @@
      */
     void setAncestralSerialNumber(in long ancestralSerialNumber);
 
+    /**
+     * Excludes keys from KV restore for a given package. The corresponding data will be excluded
+     * from the data set available the backup agent during restore. However,  final list  of keys
+     * that have been excluded will be passed to the agent to make it aware of the exclusions.
+     */
+    void excludeKeysFromRestore(String packageName, in List<String> keys);
 }
diff --git a/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl b/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl
new file mode 100644
index 0000000..260c7df
--- /dev/null
+++ b/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl
@@ -0,0 +1,36 @@
+/*
+ * 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.app.timezonedetector;
+
+import android.app.timezonedetector.PhoneTimeZoneSuggestion;
+
+/**
+ * System private API to communicate with time zone detector service.
+ *
+ * <p>Used to provide information to the Time Zone Detector Service from other parts of the Android
+ * system that have access to time zone-related signals, e.g. telephony.
+ *
+ * <p>Use the {@link android.app.timezonedetector.TimeZoneDetector} class rather than going through
+ * this Binder interface directly. See {@link android.app.timezonedetector.TimeZoneDetectorService}
+ * for more complete documentation.
+ *
+ *
+ * {@hide}
+ */
+interface ITimeZoneDetectorService {
+  void suggestPhoneTimeZone(in PhoneTimeZoneSuggestion timeZoneSuggestion);
+}
diff --git a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.aidl b/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.aidl
new file mode 100644
index 0000000..3ad903b
--- /dev/null
+++ b/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.app.timezonedetector;
+
+parcelable PhoneTimeZoneSuggestion;
diff --git a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java b/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java
new file mode 100644
index 0000000..e8162488
--- /dev/null
+++ b/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright 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.app.timezonedetector;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A suggested time zone from a Phone-based signal, e.g. from MCC and NITZ information.
+ *
+ * @hide
+ */
+public final class PhoneTimeZoneSuggestion implements Parcelable {
+
+    @NonNull
+    public static final Creator<PhoneTimeZoneSuggestion> CREATOR =
+            new Creator<PhoneTimeZoneSuggestion>() {
+                public PhoneTimeZoneSuggestion createFromParcel(Parcel in) {
+                    return PhoneTimeZoneSuggestion.createFromParcel(in);
+                }
+
+                public PhoneTimeZoneSuggestion[] newArray(int size) {
+                    return new PhoneTimeZoneSuggestion[size];
+                }
+            };
+
+    /**
+     * Creates an empty time zone suggestion, i.e. one that will cancel previous suggestions with
+     * the same {@code phoneId}.
+     */
+    @NonNull
+    public static PhoneTimeZoneSuggestion createEmptySuggestion(
+            int phoneId, @NonNull String debugInfo) {
+        return new Builder(phoneId).addDebugInfo(debugInfo).build();
+    }
+
+    @IntDef({ MATCH_TYPE_NA, MATCH_TYPE_NETWORK_COUNTRY_ONLY, MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET,
+            MATCH_TYPE_EMULATOR_ZONE_ID, MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface MatchType {}
+
+    /** Used when match type is not applicable. */
+    public static final int MATCH_TYPE_NA = 0;
+
+    /**
+     * Only the network country is known.
+     */
+    public static final int MATCH_TYPE_NETWORK_COUNTRY_ONLY = 2;
+
+    /**
+     * Both the network county and offset were known.
+     */
+    public static final int MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET = 3;
+
+    /**
+     * The device is running in an emulator and an NITZ signal was simulated containing an
+     * Android extension with an explicit Olson ID.
+     */
+    public static final int MATCH_TYPE_EMULATOR_ZONE_ID = 4;
+
+    /**
+     * The phone is most likely running in a test network not associated with a country (this is
+     * distinct from the country just not being known yet).
+     * Historically, Android has just picked an arbitrary time zone with the correct offset when
+     * on a test network.
+     */
+    public static final int MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY = 5;
+
+    @IntDef({ QUALITY_NA, QUALITY_SINGLE_ZONE, QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET,
+            QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Quality {}
+
+    /** Used when quality is not applicable. */
+    public static final int QUALITY_NA = 0;
+
+    /** There is only one answer */
+    public static final int QUALITY_SINGLE_ZONE = 1;
+
+    /**
+     * There are multiple answers, but they all shared the same offset / DST state at the time
+     * the suggestion was created. i.e. it might be the wrong zone but the user won't notice
+     * immediately if it is wrong.
+     */
+    public static final int QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET = 2;
+
+    /**
+     * There are multiple answers with different offsets. The one given is just one possible.
+     */
+    public static final int QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS = 3;
+
+    /**
+     * The ID of the phone this suggestion is associated with. For multiple-sim devices this
+     * helps to establish origin so filtering / stickiness can be implemented.
+     */
+    private final int mPhoneId;
+
+    /**
+     * The suggestion. {@code null} means there is no current suggestion and any previous suggestion
+     * should be forgotten.
+     */
+    private final String mZoneId;
+
+    /**
+     * The type of "match" used to establish the time zone.
+     */
+    @MatchType
+    private final int mMatchType;
+
+    /**
+     * A measure of the quality of the time zone suggestion, i.e. how confident one could be in
+     * it.
+     */
+    @Quality
+    private final int mQuality;
+
+    /**
+     * Free-form debug information about how the signal was derived. Used for debug only,
+     * intentionally not used in equals(), etc.
+     */
+    private List<String> mDebugInfo;
+
+    private PhoneTimeZoneSuggestion(Builder builder) {
+        mPhoneId = builder.mPhoneId;
+        mZoneId = builder.mZoneId;
+        mMatchType = builder.mMatchType;
+        mQuality = builder.mQuality;
+        mDebugInfo = builder.mDebugInfo != null ? new ArrayList<>(builder.mDebugInfo) : null;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static PhoneTimeZoneSuggestion createFromParcel(Parcel in) {
+        // Use the Builder so we get validation during build().
+        int phoneId = in.readInt();
+        PhoneTimeZoneSuggestion suggestion = new Builder(phoneId)
+                .setZoneId(in.readString())
+                .setMatchType(in.readInt())
+                .setQuality(in.readInt())
+                .build();
+        List<String> debugInfo = in.readArrayList(PhoneTimeZoneSuggestion.class.getClassLoader());
+        if (debugInfo != null) {
+            suggestion.addDebugInfo(debugInfo);
+        }
+        return suggestion;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mPhoneId);
+        dest.writeString(mZoneId);
+        dest.writeInt(mMatchType);
+        dest.writeInt(mQuality);
+        dest.writeList(mDebugInfo);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public int getPhoneId() {
+        return mPhoneId;
+    }
+
+    @Nullable
+    public String getZoneId() {
+        return mZoneId;
+    }
+
+    @MatchType
+    public int getMatchType() {
+        return mMatchType;
+    }
+
+    @Quality
+    public int getQuality() {
+        return mQuality;
+    }
+
+    @NonNull
+    public List<String> getDebugInfo() {
+        return mDebugInfo == null
+                ? Collections.emptyList() : Collections.unmodifiableList(mDebugInfo);
+    }
+
+    /**
+     * Associates information with the instance that can be useful for debugging / logging. The
+     * information is present in {@link #toString()} but is not considered for
+     * {@link #equals(Object)} and {@link #hashCode()}.
+     */
+    public void addDebugInfo(@NonNull String debugInfo) {
+        if (mDebugInfo == null) {
+            mDebugInfo = new ArrayList<>();
+        }
+        mDebugInfo.add(debugInfo);
+    }
+
+    /**
+     * Associates information with the instance that can be useful for debugging / logging. The
+     * information is present in {@link #toString()} but is not considered for
+     * {@link #equals(Object)} and {@link #hashCode()}.
+     */
+    public void addDebugInfo(@NonNull List<String> debugInfo) {
+        if (mDebugInfo == null) {
+            mDebugInfo = new ArrayList<>(debugInfo.size());
+        }
+        mDebugInfo.addAll(debugInfo);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        PhoneTimeZoneSuggestion that = (PhoneTimeZoneSuggestion) o;
+        return mPhoneId == that.mPhoneId
+                && mMatchType == that.mMatchType
+                && mQuality == that.mQuality
+                && Objects.equals(mZoneId, that.mZoneId);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mPhoneId, mZoneId, mMatchType, mQuality);
+    }
+
+    @Override
+    public String toString() {
+        return "PhoneTimeZoneSuggestion{"
+                + "mPhoneId=" + mPhoneId
+                + ", mZoneId='" + mZoneId + '\''
+                + ", mMatchType=" + mMatchType
+                + ", mQuality=" + mQuality
+                + ", mDebugInfo=" + mDebugInfo
+                + '}';
+    }
+
+    /**
+     * Builds {@link PhoneTimeZoneSuggestion} instances.
+     *
+     * @hide
+     */
+    public static class Builder {
+        private final int mPhoneId;
+        private String mZoneId;
+        @MatchType private int mMatchType;
+        @Quality private int mQuality;
+        private List<String> mDebugInfo;
+
+        public Builder(int phoneId) {
+            mPhoneId = phoneId;
+        }
+
+        /** Returns the builder for call chaining. */
+        public Builder setZoneId(String zoneId) {
+            mZoneId = zoneId;
+            return this;
+        }
+
+        /** Returns the builder for call chaining. */
+        public Builder setMatchType(@MatchType int matchType) {
+            mMatchType = matchType;
+            return this;
+        }
+
+        /** Returns the builder for call chaining. */
+        public Builder setQuality(@Quality int quality) {
+            mQuality = quality;
+            return this;
+        }
+
+        /** Returns the builder for call chaining. */
+        public Builder addDebugInfo(@NonNull String debugInfo) {
+            if (mDebugInfo == null) {
+                mDebugInfo = new ArrayList<>();
+            }
+            mDebugInfo.add(debugInfo);
+            return this;
+        }
+
+        /**
+         * Performs basic structural validation of this instance. e.g. Are all the fields populated
+         * that must be? Are the enum ints set to valid values?
+         */
+        void validate() {
+            int quality = mQuality;
+            int matchType = mMatchType;
+            if (mZoneId == null) {
+                if (quality != QUALITY_NA || matchType != MATCH_TYPE_NA) {
+                    throw new RuntimeException("Invalid quality or match type for null zone ID."
+                            + " quality=" + quality + ", matchType=" + matchType);
+                }
+            } else {
+                boolean qualityValid = (quality == QUALITY_SINGLE_ZONE
+                        || quality == QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET
+                        || quality == QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS);
+                boolean matchTypeValid = (matchType == MATCH_TYPE_NETWORK_COUNTRY_ONLY
+                        || matchType == MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET
+                        || matchType == MATCH_TYPE_EMULATOR_ZONE_ID
+                        || matchType == MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY);
+                if (!qualityValid || !matchTypeValid) {
+                    throw new RuntimeException("Invalid quality or match type with zone ID."
+                            + " quality=" + quality + ", matchType=" + matchType);
+                }
+            }
+        }
+
+        /** Returns the {@link PhoneTimeZoneSuggestion}. */
+        public PhoneTimeZoneSuggestion build() {
+            validate();
+            return new PhoneTimeZoneSuggestion(this);
+        }
+    }
+}
diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java
new file mode 100644
index 0000000..909cbc2
--- /dev/null
+++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java
@@ -0,0 +1,59 @@
+/*
+ * 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.app.timezonedetector;
+
+import android.annotation.NonNull;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceManager.ServiceNotFoundException;
+import android.util.Log;
+
+/**
+ * The interface through which system components can send signals to the TimeZoneDetectorService.
+ * @hide
+ */
+@SystemService(Context.TIME_ZONE_DETECTOR_SERVICE)
+public final class TimeZoneDetector {
+    private static final String TAG = "timezonedetector.TimeZoneDetector";
+    private static final boolean DEBUG = false;
+
+    private final ITimeZoneDetectorService mITimeZoneDetectorService;
+
+    public TimeZoneDetector() throws ServiceNotFoundException {
+        mITimeZoneDetectorService = ITimeZoneDetectorService.Stub.asInterface(
+                ServiceManager.getServiceOrThrow(Context.TIME_ZONE_DETECTOR_SERVICE));
+    }
+
+    /**
+     * Suggests the current time zone to the detector. The detector may ignore the signal if better
+     * signals are available such as those that come from more reliable sources or were
+     * determined more recently.
+     */
+    public void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion timeZoneSuggestion) {
+        if (DEBUG) {
+            Log.d(TAG, "suggestPhoneTimeZone called: " + timeZoneSuggestion);
+        }
+        try {
+            mITimeZoneDetectorService.suggestPhoneTimeZone(timeZoneSuggestion);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index fc584c4..341b520 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3425,7 +3425,9 @@
             CROSS_PROFILE_APPS_SERVICE,
             //@hide: SYSTEM_UPDATE_SERVICE,
             //@hide: TIME_DETECTOR_SERVICE,
+            //@hide: TIME_ZONE_DETECTOR_SERVICE,
             PERMISSION_SERVICE,
+            INCREMENTAL_SERVICE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ServiceName {}
@@ -4877,7 +4879,7 @@
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve an
-     * {@link android.app.timedetector.ITimeDetectorService}.
+     * {@link android.app.timedetector.TimeDetector}.
      * @hide
      *
      * @see #getSystemService(String)
@@ -4885,6 +4887,15 @@
     public static final String TIME_DETECTOR_SERVICE = "time_detector";
 
     /**
+     * Use with {@link #getSystemService(String)} to retrieve an
+     * {@link android.app.timezonedetector.TimeZoneDetector}.
+     * @hide
+     *
+     * @see #getSystemService(String)
+     */
+    public static final String TIME_ZONE_DETECTOR_SERVICE = "time_zone_detector";
+
+    /**
      * Binder service name for {@link AppBindingService}.
      * @hide
      */
@@ -4959,6 +4970,13 @@
     public static final String APP_INTEGRITY_SERVICE = "app_integrity";
 
     /**
+     * Use with {@link #getSystemService(String)} to retrieve an
+     * {@link android.os.incremental.IncrementalManager}.
+     * @hide
+     */
+    public static final String INCREMENTAL_SERVICE = "incremental";
+
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
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 ec39199..15ff69e 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -505,6 +505,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 89ccef6..6eaea99 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -1084,7 +1084,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");
@@ -1094,7 +1094,7 @@
                 TelephonyProperties.baseband_version(), "");
 
         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/java/android/os/incremental/IIncrementalService.aidl b/core/java/android/os/incremental/IIncrementalManager.aidl
similarity index 77%
rename from core/java/android/os/incremental/IIncrementalService.aidl
rename to core/java/android/os/incremental/IIncrementalManager.aidl
index 1c832ca..d6446d4 100644
--- a/core/java/android/os/incremental/IIncrementalService.aidl
+++ b/core/java/android/os/incremental/IIncrementalManager.aidl
@@ -19,7 +19,7 @@
 import android.os.incremental.IncrementalDataLoaderParamsParcel;
 
 /** @hide */
-interface IIncrementalService {
+interface IIncrementalManager {
     /**
      * A set of flags for the |createMode| parameters when creating a new Incremental storage.
      */
@@ -53,6 +53,12 @@
     int makeDirectory(int storageId, in @utf8InCpp String pathUnderStorage);
 
     /**
+     * Recursively creates a directory under a storage. The target directory is specified by its relative path under the storage.
+     * All the parent directories of the target directory will be created if they do not exist already.
+     */
+    int makeDirectories(int storageId, in @utf8InCpp String pathUnderStorage);
+
+    /**
      * Creates a file under a storage, specifying its name, size and metadata.
      */
     int makeFile(int storageId, in @utf8InCpp String pathUnderStorage, long size, in byte[] metadata);
@@ -64,10 +70,12 @@
     int makeFileFromRange(int storageId, in @utf8InCpp String targetPathUnderStorage, in @utf8InCpp String sourcePathUnderStorage, long start, long end);
 
     /**
-     * Creates a hard link between two files in a storage.
-     * Both source and destination are specified by relative paths under storage.
+     * Creates a hard link between two files in two storage instances.
+     * Source and dest specified by parent storage IDs and their relative paths under the storage.
+     * The source and dest storage instances should be in the same fs mount.
+     * Note: destStorageId can be the same as sourceStorageId.
      */
-    int makeLink(int storageId, in @utf8InCpp String sourcePathUnderStorage, in @utf8InCpp String destPathUnderStorage);
+    int makeLink(int sourceStorageId, in @utf8InCpp String sourcePathUnderStorage, int destStorageId, in @utf8InCpp String destPathUnderStorage);
 
     /**
      * Deletes a hard link in a storage, specified by the relative path of the link target under storage.
@@ -85,12 +93,12 @@
     byte[] getFileMetadata(int storageId, in @utf8InCpp String pathUnderStorage);
 
     /**
-     * Returns the list of file paths under a storage.
-     */
-    @utf8InCpp String[] getFileList(int storageId);
-
-    /**
      * Starts loading data for a storage.
      */
     boolean startLoading(int storageId);
+
+    /**
+     * Deletes a storage given its ID. Deletes its bind mounts and unmount it. Stop its data loader.
+     */
+    void deleteStorage(int storageId);
 }
diff --git a/core/java/android/os/incremental/IncrementalDataLoaderParams.java b/core/java/android/os/incremental/IncrementalDataLoaderParams.java
new file mode 100644
index 0000000..701f1cc
--- /dev/null
+++ b/core/java/android/os/incremental/IncrementalDataLoaderParams.java
@@ -0,0 +1,84 @@
+/*
+ * 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.os.incremental;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.ParcelFileDescriptor;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * This class represents the parameters used to configure an Incremental Data Loader.
+ * Hide for now.
+ * @hide
+ */
+public class IncrementalDataLoaderParams {
+    @NonNull private final IncrementalDataLoaderParamsParcel mData;
+
+    public IncrementalDataLoaderParams(@NonNull String url, @NonNull String packageName,
+            @Nullable Map<String, ParcelFileDescriptor> namedFds) {
+        IncrementalDataLoaderParamsParcel data = new IncrementalDataLoaderParamsParcel();
+        data.staticArgs = url;
+        data.packageName = packageName;
+        if (namedFds == null || namedFds.isEmpty()) {
+            data.dynamicArgs = new NamedParcelFileDescriptor[0];
+        } else {
+            data.dynamicArgs = new NamedParcelFileDescriptor[namedFds.size()];
+            int i = 0;
+            for (Map.Entry<String, ParcelFileDescriptor> namedFd : namedFds.entrySet()) {
+                data.dynamicArgs[i] = new NamedParcelFileDescriptor();
+                data.dynamicArgs[i].name = namedFd.getKey();
+                data.dynamicArgs[i].fd = namedFd.getValue();
+                i += 1;
+            }
+        }
+        mData = data;
+    }
+
+    public IncrementalDataLoaderParams(@NonNull IncrementalDataLoaderParamsParcel data) {
+        mData = data;
+    }
+
+    /**
+     * @return static server's URL
+     */
+    public final @NonNull String getStaticArgs() {
+        return mData.staticArgs;
+    }
+
+    /**
+     * @return data loader's package name
+     */
+    public final @NonNull String getPackageName() {
+        return mData.packageName;
+    }
+
+    public final @NonNull IncrementalDataLoaderParamsParcel getData() {
+        return mData;
+    }
+
+    /**
+     * @return data loader's dynamic arguments such as file descriptors
+     */
+    public final @NonNull Map<String, ParcelFileDescriptor> getDynamicArgs() {
+        return Arrays.stream(mData.dynamicArgs).collect(
+            Collectors.toMap(p->p.name, p->p.fd));
+    }
+}
diff --git a/core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl b/core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl
index 50c28f0..cd988dc 100644
--- a/core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl
+++ b/core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl
@@ -23,7 +23,7 @@
  * @hide
  */
 parcelable IncrementalDataLoaderParamsParcel {
-    @utf8InCpp String staticUri;
     @utf8InCpp String packageName;
+    @utf8InCpp String staticArgs;
     NamedParcelFileDescriptor[] dynamicArgs;
 }
diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java
new file mode 100644
index 0000000..5aabf86
--- /dev/null
+++ b/core/java/android/os/incremental/IncrementalManager.java
@@ -0,0 +1,327 @@
+/*
+ * 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.os.incremental;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.RemoteException;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+/**
+ * Provides operations to open or create an IncrementalStorage, using IIncrementalManager service.
+ * Example Usage:
+ *
+ * <blockquote><pre>
+ * IncrementalManager manager = (IncrementalManager) getSystemService(Context.INCREMENTAL_MANAGER);
+ * IncrementalStorage storage = manager.openStorage("/path/to/incremental/dir");
+ * </pre></blockquote>
+ *
+ * @hide
+ */
+@SystemService(Context.INCREMENTAL_SERVICE)
+public class IncrementalManager {
+    private static final String TAG = "IncrementalManager";
+    private final IIncrementalManager mService;
+    @GuardedBy("mStorages")
+    private final SparseArray<IncrementalStorage> mStorages = new SparseArray<>();
+
+    public static final int CREATE_MODE_TEMPORARY_BIND =
+            IIncrementalManager.CREATE_MODE_TEMPORARY_BIND;
+    public static final int CREATE_MODE_PERMANENT_BIND =
+            IIncrementalManager.CREATE_MODE_PERMANENT_BIND;
+    public static final int CREATE_MODE_CREATE =
+            IIncrementalManager.CREATE_MODE_CREATE;
+    public static final int CREATE_MODE_OPEN_EXISTING =
+            IIncrementalManager.CREATE_MODE_OPEN_EXISTING;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"CREATE_MODE_"}, value = {
+            CREATE_MODE_TEMPORARY_BIND,
+            CREATE_MODE_PERMANENT_BIND,
+            CREATE_MODE_CREATE,
+            CREATE_MODE_OPEN_EXISTING,
+    })
+    public @interface CreateMode {
+    }
+
+    public IncrementalManager(@NonNull IIncrementalManager is) {
+        mService = is;
+    }
+
+    /**
+     * Returns a storage object given a storage ID.
+     *
+     * @param storageId The storage ID to identify the storage object.
+     * @return IncrementalStorage object corresponding to storage ID.
+     */
+    @Nullable
+    public IncrementalStorage getStorage(int storageId) {
+        synchronized (mStorages) {
+            return mStorages.get(storageId);
+        }
+    }
+
+    /**
+     * Opens or create an Incremental File System mounted directory and returns an
+     * IncrementalStorage object.
+     *
+     * @param path                Absolute path to mount Incremental File System on.
+     * @param params              IncrementalDataLoaderParams object to configure data loading.
+     * @param createMode          Mode for opening an old Incremental File System mount or
+     *                            creating a new mount.
+     * @param autoStartDataLoader Set true to immediately start data loader after creating storage.
+     * @return IncrementalStorage object corresponding to the mounted directory.
+     */
+    @Nullable
+    public IncrementalStorage createStorage(@NonNull String path,
+            @NonNull IncrementalDataLoaderParams params, @CreateMode int createMode,
+            boolean autoStartDataLoader) {
+        try {
+            final int id = mService.createStorage(path, params.getData(), createMode);
+            if (id < 0) {
+                return null;
+            }
+            final IncrementalStorage storage = new IncrementalStorage(mService, id);
+            synchronized (mStorages) {
+                mStorages.put(id, storage);
+            }
+            if (autoStartDataLoader) {
+                storage.startLoading();
+            }
+            return storage;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Opens an existing Incremental File System mounted directory and returns an
+     * IncrementalStorage object.
+     *
+     * @param path Absolute target path that Incremental File System has been mounted on.
+     * @return IncrementalStorage object corresponding to the mounted directory.
+     */
+    @Nullable
+    public IncrementalStorage openStorage(@NonNull String path) {
+        try {
+            final int id = mService.openStorage(path);
+            if (id < 0) {
+                return null;
+            }
+            final IncrementalStorage storage = new IncrementalStorage(mService, id);
+            synchronized (mStorages) {
+                mStorages.put(id, storage);
+            }
+            return storage;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Opens or creates an IncrementalStorage that is linked to another IncrementalStorage.
+     *
+     * @return IncrementalStorage object corresponding to the linked storage.
+     */
+    @Nullable
+    public IncrementalStorage createStorage(@NonNull String path,
+            @NonNull IncrementalStorage linkedStorage, @CreateMode int createMode) {
+        try {
+            final int id = mService.createLinkedStorage(path, linkedStorage.getId(), createMode);
+            if (id < 0) {
+                return null;
+            }
+            final IncrementalStorage storage = new IncrementalStorage(mService, id);
+            synchronized (mStorages) {
+                mStorages.put(id, storage);
+            }
+            return storage;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Iterates through path parents to find the base dir of an Incremental Storage.
+     *
+     * @param file Target file to search storage for.
+     * @return Absolute path which is a bind-mount point of Incremental File System.
+     */
+    private Path getStoragePathForFile(File file) {
+        File currentPath = new File(file.getParent());
+        while (currentPath.getParent() != null) {
+            IncrementalStorage storage = openStorage(currentPath.getAbsolutePath());
+            if (storage != null) {
+                return currentPath.toPath();
+            }
+            currentPath = new File(currentPath.getParent());
+        }
+        return null;
+    }
+
+    /**
+     * Renames an Incremental path to a new path. If source path is a file, make a link from the old
+     * Incremental file to the new one. If source path is a dir, unbind old dir from Incremental
+     * Storage and bind the new one.
+     * <ol>
+     *     <li> For renaming a dir, dest dir will be created if not exists, and does not need to
+     *          be on the same Incremental storage as the source. </li>
+     *     <li> For renaming a file, dest file must be on the same Incremental storage as source.
+     *     </li>
+     * </ol>
+     *
+     * @param sourcePath   Absolute path to the source. Should be the same type as the destPath
+     *                     (file or dir). Expected to already exist and is an Incremental path.
+     * @param destPath     Absolute path to the destination.
+     * @throws IllegalArgumentException when 1) source does not exist,
+     *                     or 2) source and dest type mismatch (one is file and the other is dir),
+     *                     or 3) source path is not on Incremental File System,
+     * @throws IOException when 1) cannot find the root path of the Incremental storage of source,
+     *                     or 2) cannot retrieve the Incremental storage instance of the source,
+     *                     or 3) renaming a file, but dest is not on the same Incremental Storage,
+     *                     or 4) renaming a dir, dest dir does not exist but fails to be created.
+     *
+     * TODO(b/136132412): add unit tests
+     */
+    public void rename(@NonNull String sourcePath, @NonNull String destPath) throws IOException {
+        final File source = new File(sourcePath);
+        final File dest = new File(destPath);
+        if (!source.exists()) {
+            throw new IllegalArgumentException("Path not exist: " + sourcePath);
+        }
+        if (dest.exists()) {
+            throw new IllegalArgumentException("Target path already exists: " + destPath);
+        }
+        if (source.isDirectory() && dest.exists() && dest.isFile()) {
+            throw new IllegalArgumentException(
+                    "Trying to rename a dir but destination is a file: " + destPath);
+        }
+        if (source.isFile() && dest.exists() && dest.isDirectory()) {
+            throw new IllegalArgumentException(
+                    "Trying to rename a file but destination is a dir: " + destPath);
+        }
+        if (!isIncrementalPath(sourcePath)) {
+            throw new IllegalArgumentException("Not an Incremental path: " + sourcePath);
+        }
+
+        Path storagePath = Paths.get(sourcePath);
+        if (source.isFile()) {
+            storagePath = getStoragePathForFile(source);
+        }
+        if (storagePath == null || storagePath.toAbsolutePath() == null) {
+            throw new IOException("Invalid source storage path for: " + sourcePath);
+        }
+        final IncrementalStorage storage = openStorage(storagePath.toAbsolutePath().toString());
+        if (storage == null) {
+            throw new IOException("Failed to retrieve storage from Incremental Service.");
+        }
+        if (source.isFile()) {
+            renameFile(storage, storagePath, source, dest);
+        } else {
+            renameDir(storage, storagePath, source, dest);
+        }
+    }
+
+    private void renameFile(IncrementalStorage storage, Path storagePath,
+            File source, File dest) throws IOException {
+        Path sourcePath = source.toPath();
+        Path destPath = dest.toPath();
+        if (!sourcePath.startsWith(storagePath)) {
+            throw new IOException("Path: " + source.getAbsolutePath() + " is not on storage at: "
+                    + storagePath.toString());
+        }
+        if (!destPath.startsWith(storagePath)) {
+            throw new IOException("Path: " + dest.getAbsolutePath() + " is not on storage at: "
+                    + storagePath.toString());
+        }
+        final Path sourceRelativePath = storagePath.relativize(sourcePath);
+        final Path destRelativePath = storagePath.relativize(destPath);
+        storage.moveFile(sourceRelativePath.toString(), destRelativePath.toString());
+
+    }
+
+    private void renameDir(IncrementalStorage storage, Path storagePath,
+            File source, File dest) throws IOException {
+        Path destPath = dest.toPath();
+        boolean usedMkdir = false;
+        try {
+            Os.mkdir(dest.getAbsolutePath(), 0755);
+            usedMkdir = true;
+        } catch (ErrnoException e) {
+            // Traditional mkdir fails but maybe we can create it on Incremental File System if
+            // the dest path is on the same Incremental storage as the source.
+            if (destPath.startsWith(storagePath)) {
+                storage.makeDirectories(storagePath.relativize(destPath).toString());
+            } else {
+                throw new IOException("Failed to create directory: " + dest.getAbsolutePath(), e);
+            }
+        }
+        try {
+            storage.moveDir(source.getAbsolutePath(), dest.getAbsolutePath());
+        } catch (Exception ex) {
+            if (usedMkdir) {
+                try {
+                    Os.remove(dest.getAbsolutePath());
+                } catch (ErrnoException ignored) {
+                }
+            }
+            throw new IOException(
+                    "Failed to move " + source.getAbsolutePath() + " to " + dest.getAbsolutePath());
+        }
+    }
+
+    /**
+     * Closes a storage specified by the absolute path. If the path is not Incremental, do nothing.
+     * Unbinds the target dir and deletes the corresponding storage instance.
+     */
+    public void closeStorage(@NonNull String path) {
+        try {
+            final int id = mService.openStorage(path);
+            if (id < 0) {
+                return;
+            }
+            mService.deleteStorage(id);
+            synchronized (mStorages) {
+                mStorages.remove(id);
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Checks if path is mounted on Incremental File System.
+     */
+    public static boolean isIncrementalPath(@NonNull String path) {
+        // TODO(b/136132412): implement native method
+        return false;
+    }
+}
diff --git a/core/java/android/os/incremental/IncrementalStorage.java b/core/java/android/os/incremental/IncrementalStorage.java
new file mode 100644
index 0000000..2bf89ed
--- /dev/null
+++ b/core/java/android/os/incremental/IncrementalStorage.java
@@ -0,0 +1,346 @@
+/*
+ * 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.os.incremental;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.RemoteException;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Provides operations on an Incremental File System directory, using IncrementalService. Example
+ * usage:
+ *
+ * <blockquote><pre>
+ * IncrementalManager manager = (IncrementalManager) getSystemService(Context.INCREMENTAL_MANAGER);
+ * IncrementalStorage storage = manager.openStorage("/path/to/incremental/dir");
+ * storage.makeDirectory("subdir");
+ * </pre></blockquote>
+ *
+ * @hide
+ */
+public final class IncrementalStorage {
+    private static final String TAG = "IncrementalStorage";
+    private final int mId;
+    private final IIncrementalManager mService;
+
+
+    public IncrementalStorage(@NonNull IIncrementalManager is, int id) {
+        mService = is;
+        mId = id;
+    }
+
+    public int getId() {
+        return mId;
+    }
+
+    /**
+     * Temporarily bind-mounts the current storage directory to a target directory. The bind-mount
+     * will NOT be preserved between device reboots.
+     *
+     * @param targetPath Absolute path to the target directory.
+     */
+    public void bind(@NonNull String targetPath) throws IOException {
+        bind("", targetPath);
+    }
+
+    /**
+     * Temporarily bind-mounts a subdir under the current storage directory to a target directory.
+     * The bind-mount will NOT be preserved between device reboots.
+     *
+     * @param sourcePathUnderStorage Source path as a relative path under current storage
+     *                               directory.
+     * @param targetPath             Absolute path to the target directory.
+     */
+    public void bind(@NonNull String sourcePathUnderStorage, @NonNull String targetPath)
+            throws IOException {
+        try {
+            int res = mService.makeBindMount(mId, sourcePathUnderStorage, targetPath,
+                    IIncrementalManager.BIND_TEMPORARY);
+            if (res < 0) {
+                throw new IOException("bind() failed with errno " + -res);
+            }
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+
+    /**
+     * Permanently bind-mounts the current storage directory to a target directory. The bind-mount
+     * WILL be preserved between device reboots.
+     *
+     * @param targetPath Absolute path to the target directory.
+     */
+    public void bindPermanent(@NonNull String targetPath) throws IOException {
+        bindPermanent("", targetPath);
+    }
+
+    /**
+     * Permanently bind-mounts a subdir under the current storage directory to a target directory.
+     * The bind-mount WILL be preserved between device reboots.
+     *
+     * @param sourcePathUnderStorage Relative path under the current storage directory.
+     * @param targetPath             Absolute path to the target directory.
+     */
+    public void bindPermanent(@NonNull String sourcePathUnderStorage, @NonNull String targetPath)
+            throws IOException {
+        try {
+            int res = mService.makeBindMount(mId, sourcePathUnderStorage, targetPath,
+                    IIncrementalManager.BIND_PERMANENT);
+            if (res < 0) {
+                throw new IOException("bind() permanent failed with errno " + -res);
+            }
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Unbinds a bind mount.
+     *
+     * @param targetPath Absolute path to the target directory.
+     */
+    public void unBind(@NonNull String targetPath) throws IOException {
+        try {
+            int res = mService.deleteBindMount(mId, targetPath);
+            if (res < 0) {
+                throw new IOException("unbind() failed with errno " + -res);
+            }
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Creates a sub-directory under the current storage directory.
+     *
+     * @param pathUnderStorage Relative path of the sub-directory, e.g., "subdir"
+     */
+    public void makeDirectory(@NonNull String pathUnderStorage) throws IOException {
+        try {
+            int res = mService.makeDirectory(mId, pathUnderStorage);
+            if (res < 0) {
+                throw new IOException("makeDirectory() failed with errno " + -res);
+            }
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Creates a sub-directory under the current storage directory. If its parent dirs do not exist,
+     * create the parent dirs as well.
+     *
+     * @param pathUnderStorage Relative path of the sub-directory, e.g., "subdir/subsubdir"
+     */
+    public void makeDirectories(@NonNull String pathUnderStorage) throws IOException {
+        try {
+            int res = mService.makeDirectories(mId, pathUnderStorage);
+            if (res < 0) {
+                throw new IOException("makeDirectory() failed with errno " + -res);
+            }
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Creates a file under the current storage directory.
+     *
+     * @param pathUnderStorage Relative path of the new file.
+     * @param size             Size of the new file in bytes.
+     * @param metadata         Metadata bytes.
+     */
+    public void makeFile(@NonNull String pathUnderStorage, long size,
+            @Nullable byte[] metadata) throws IOException {
+        try {
+            int res = mService.makeFile(mId, pathUnderStorage, size, metadata);
+            if (res < 0) {
+                throw new IOException("makeFile() failed with errno " + -res);
+            }
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Creates a file in Incremental storage. The content of the file is mapped from a range inside
+     * a source file in the same storage.
+     *
+     * @param destRelativePath   Target relative path under storage.
+     * @param sourceRelativePath Source relative path under storage.
+     * @param rangeStart         Starting offset (in bytes) in the source file.
+     * @param rangeEnd           Ending offset (in bytes) in the source file.
+     */
+    public void makeFileFromRange(@NonNull String destRelativePath,
+            @NonNull String sourceRelativePath, long rangeStart, long rangeEnd) throws IOException {
+        try {
+            int res = mService.makeFileFromRange(mId, destRelativePath, sourceRelativePath,
+                    rangeStart, rangeEnd);
+            if (res < 0) {
+                throw new IOException("makeFileFromRange() failed, errno " + -res);
+            }
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Creates a hard-link between two paths, which can be under different storages but in the same
+     * Incremental File System.
+     *
+     * @param sourcePathUnderStorage The relative path of the source.
+     * @param destStorage            The target storage of the link target.
+     * @param destPathUnderStorage   The relative path of the target.
+     */
+    public void makeLink(@NonNull String sourcePathUnderStorage, IncrementalStorage destStorage,
+            @NonNull String destPathUnderStorage) throws IOException {
+        try {
+            int res = mService.makeLink(mId, sourcePathUnderStorage, destStorage.getId(),
+                    destPathUnderStorage);
+            if (res < 0) {
+                throw new IOException("makeLink() failed with errno " + -res);
+            }
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Deletes a hard-link under the current storage directory.
+     *
+     * @param pathUnderStorage The relative path of the target.
+     */
+    public void unlink(@NonNull String pathUnderStorage) throws IOException {
+        try {
+            int res = mService.unlink(mId, pathUnderStorage);
+            if (res < 0) {
+                throw new IOException("unlink() failed with errno " + -res);
+            }
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Rename an old file name to a new file name under the current storage directory.
+     *
+     * @param sourcePathUnderStorage Old file path as a relative path to the storage directory.
+     * @param destPathUnderStorage   New file path as a relative path to the storage directory.
+     */
+    public void moveFile(@NonNull String sourcePathUnderStorage,
+            @NonNull String destPathUnderStorage) throws IOException {
+        try {
+            int res = mService.makeLink(mId, sourcePathUnderStorage, mId, destPathUnderStorage);
+            if (res < 0) {
+                throw new IOException("moveFile() failed at makeLink(), errno " + -res);
+            }
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        try {
+            mService.unlink(mId, sourcePathUnderStorage);
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Move a directory, which is bind-mounted to a given storage, to a new location. The bind mount
+     * will be persistent between reboots.
+     *
+     * @param sourcePath The old path of the directory as an absolute path.
+     * @param destPath   The new path of the directory as an absolute path, expected to already
+     *                   exist.
+     */
+    public void moveDir(@NonNull String sourcePath, @NonNull String destPath) throws IOException {
+        if (!new File(destPath).exists()) {
+            throw new IOException("moveDir() requires that destination dir already exists.");
+        }
+        try {
+            int res = mService.makeBindMount(mId, "", destPath, IIncrementalManager.BIND_PERMANENT);
+            if (res < 0) {
+                throw new IOException("moveDir() failed at making bind mount, errno " + -res);
+            }
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        try {
+            mService.deleteBindMount(mId, sourcePath);
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Checks whether a file under the current storage directory is fully loaded.
+     *
+     * @param pathUnderStorage The relative path of the file.
+     * @return True if the file is fully loaded.
+     */
+    public boolean isFileFullyLoaded(@NonNull String pathUnderStorage) {
+        return isFileRangeLoaded(pathUnderStorage, 0, -1);
+    }
+
+    /**
+     * Checks whether a range in a file if loaded.
+     *
+     * @param pathUnderStorage The relative path of the file.
+     * @param start            The starting offset of the range.
+     * @param end              The ending offset of the range.
+     * @return True if the file is fully loaded.
+     */
+    public boolean isFileRangeLoaded(@NonNull String pathUnderStorage, long start, long end) {
+        try {
+            return mService.isFileRangeLoaded(mId, pathUnderStorage, start, end);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+            return false;
+        }
+    }
+
+    /**
+     * Returns the metadata object of an IncFs File.
+     *
+     * @param pathUnderStorage The relative path of the file.
+     * @return Byte array that contains metadata bytes.
+     */
+    @Nullable
+    public byte[] getFileMetadata(@NonNull String pathUnderStorage) {
+        try {
+            return mService.getFileMetadata(mId, pathUnderStorage);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+            return null;
+        }
+    }
+
+    /**
+     * Informs the data loader service associated with the current storage to start data loader
+     *
+     * @return True if data loader is successfully started.
+     */
+    public boolean startLoading() {
+        try {
+            return mService.startLoading(mId);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+            return false;
+        }
+    }
+}
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 7e09740..236e5ae 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -41,6 +41,7 @@
     public static final String SCREENRECORD_LONG_PRESS = "settings_screenrecord_long_press";
     public static final String DYNAMIC_SYSTEM = "settings_dynamic_system";
     public static final String SETTINGS_WIFITRACKER2 = "settings_wifitracker2";
+    public static final String SETTINGS_FUSE_FLAG = "settings_fuse";
 
     private static final Map<String, String> DEFAULT_FLAGS;
 
@@ -48,6 +49,7 @@
         DEFAULT_FLAGS = new HashMap<>();
         DEFAULT_FLAGS.put("settings_audio_switcher", "true");
         DEFAULT_FLAGS.put("settings_systemui_theme", "true");
+        DEFAULT_FLAGS.put(SETTINGS_FUSE_FLAG, "false");
         DEFAULT_FLAGS.put(DYNAMIC_SYSTEM, "false");
         DEFAULT_FLAGS.put(SEAMLESS_TRANSFER, "false");
         DEFAULT_FLAGS.put(HEARING_AID_SETTINGS, "false");
diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java
index fee8345..0847fbd 100644
--- a/core/java/com/android/internal/content/NativeLibraryHelper.java
+++ b/core/java/com/android/internal/content/NativeLibraryHelper.java
@@ -33,7 +33,6 @@
 import android.content.pm.PackageParser.PackageParserException;
 import android.os.Build;
 import android.os.SELinux;
-import android.os.SystemProperties;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.util.Slog;
@@ -444,6 +443,24 @@
         return sum;
     }
 
+     /**
+     * Configure the native library files managed by Incremental Service. Makes sure Incremental
+     * Service will create native library directories and set up native library binary files in the
+     * same structure as they are in non-incremental installations.
+     *
+     * @param pkg The package to be installed, including all the APK files.
+     * @param handle The pointer to an zip archive.
+     * @param libraryRoot The root directory of the native library files, e.g., lib/
+     * @param abiList The list of ABIs that are supported by the current device.
+     * @param useIsaSubdir Whether or not to set up a sub dir for the ISA.
+     * @return ABI code if installation succeeds or error code if installation fails.
+     */
+    public static int configureNativeBinariesForSupportedAbi(Package pkg, Handle handle,
+            File libraryRoot, String[] abiList, boolean useIsaSubdir) {
+        // TODO(b/136132412): Implement this.
+        return -1;
+    }
+
     // We don't care about the other return values for now.
     private static final int BITCODE_PRESENT = 1;
 
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 75a5804..f1e299d 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -1870,6 +1870,7 @@
             mCount = computeCurrentCountLocked();
             mUnpluggedReportedTotalTime = mCurrentReportedTotalTime = 0;
             mUnpluggedReportedCount = mCurrentReportedCount = 0;
+            mTrackingReportedValues = false;
         }
 
         public void setUpdateVersion(int version) {
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 2232393..0992beb 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;
 };
 
 // ----------------------------------------------------------------------------
@@ -965,6 +978,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();
@@ -1041,6 +1060,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/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index d181436..ab97fdd 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -693,6 +693,12 @@
     // CATEGORY: SETTINGS
     // OS: R
     ACTION_CONTROLLER_UPDATE_STATE = 1728;
+
+    // Custom tag to evaluate the consuming time from onAttach to
+    // DashboardFragment.updatePreferenceStates.
+    // CATEGORY: SETTINGS
+    // OS: R
+    ACTION_DASHBOARD_VISIBLE_TIME = 1729;
 }
 
 /**
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 99dfffe..936099f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -635,6 +635,11 @@
 
     <protected-broadcast android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY" />
 
+    <!-- NETWORK_SET_TIME / NETWORK_SET_TIMEZONE moved from com.android.phone to system server.
+         They should ultimately be removed. -->
+    <protected-broadcast android:name="android.intent.action.NETWORK_SET_TIME" />
+    <protected-broadcast android:name="android.intent.action.NETWORK_SET_TIMEZONE" />
+
     <!-- For tether entitlement recheck-->
     <protected-broadcast
         android:name="com.android.server.connectivity.tethering.PROVISIONING_RECHECK_ALARM" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 6741fea..76d9561 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2363,9 +2363,11 @@
          1 (0b001) - enforce (only install system packages if they are whitelisted)
          2 (0b010) - log (log when a non-whitelisted package is run)
          4 (0b100) - treat any package not mentioned in the whitelist file as implicitly whitelisted
+         8 (0b1000) - ignore OTAs (don't install system packages during OTAs)
         Note: This list must be kept current with PACKAGE_WHITELIST_MODE_PROP in
         frameworks/base/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java -->
-    <integer name="config_userTypePackageWhitelistMode">5</integer> <!-- 0b101 -->
+    <integer name="config_userTypePackageWhitelistMode">13</integer> <!-- 0b1101 -->
+    <!-- TODO(b/143200798): Change to value 5, i.e. 0b0101, when b/143200798 is resolved. -->
 
     <!-- Whether UI for multi user should be shown -->
     <bool name="config_enableMultiUserUI">false</bool>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 2d31f49..7310daf 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5336,5 +5336,5 @@
     <string name="accessibility_system_action_screenshot_label">Screenshot</string>
 
     <!-- Accessibility description of caption view -->
-    <string name="accessibility_freeform_caption"><xliff:g id="app_name">%1$s</xliff:g> app in Pop-up window.</string>
+    <string name="accessibility_freeform_caption">Caption bar of <xliff:g id="app_name">%1$s</xliff:g>.</string>
 </resources>
diff --git a/core/tests/coretests/src/android/app/timezonedetector/PhoneTimeZoneSuggestionTest.java b/core/tests/coretests/src/android/app/timezonedetector/PhoneTimeZoneSuggestionTest.java
new file mode 100644
index 0000000..ae91edc
--- /dev/null
+++ b/core/tests/coretests/src/android/app/timezonedetector/PhoneTimeZoneSuggestionTest.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright 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.app.timezonedetector;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import org.junit.Test;
+
+public class PhoneTimeZoneSuggestionTest {
+    private static final int PHONE_ID = 99999;
+
+    @Test
+    public void testEquals() {
+        PhoneTimeZoneSuggestion.Builder builder1 = new PhoneTimeZoneSuggestion.Builder(PHONE_ID);
+        {
+            PhoneTimeZoneSuggestion one = builder1.build();
+            assertEquals(one, one);
+        }
+
+        PhoneTimeZoneSuggestion.Builder builder2 = new PhoneTimeZoneSuggestion.Builder(PHONE_ID);
+        {
+            PhoneTimeZoneSuggestion one = builder1.build();
+            PhoneTimeZoneSuggestion two = builder2.build();
+            assertEquals(one, two);
+            assertEquals(two, one);
+        }
+
+        PhoneTimeZoneSuggestion.Builder builder3 =
+                new PhoneTimeZoneSuggestion.Builder(PHONE_ID + 1);
+        {
+            PhoneTimeZoneSuggestion one = builder1.build();
+            PhoneTimeZoneSuggestion three = builder3.build();
+            assertNotEquals(one, three);
+            assertNotEquals(three, one);
+        }
+
+        builder1.setZoneId("Europe/London");
+        builder1.setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY);
+        builder1.setQuality(PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE);
+        {
+            PhoneTimeZoneSuggestion one = builder1.build();
+            PhoneTimeZoneSuggestion two = builder2.build();
+            assertNotEquals(one, two);
+        }
+
+        builder2.setZoneId("Europe/Paris");
+        builder2.setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY);
+        builder2.setQuality(PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE);
+        {
+            PhoneTimeZoneSuggestion one = builder1.build();
+            PhoneTimeZoneSuggestion two = builder2.build();
+            assertNotEquals(one, two);
+        }
+
+        builder1.setZoneId("Europe/Paris");
+        {
+            PhoneTimeZoneSuggestion one = builder1.build();
+            PhoneTimeZoneSuggestion two = builder2.build();
+            assertEquals(one, two);
+        }
+
+        builder1.setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID);
+        builder2.setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY);
+        {
+            PhoneTimeZoneSuggestion one = builder1.build();
+            PhoneTimeZoneSuggestion two = builder2.build();
+            assertNotEquals(one, two);
+        }
+
+        builder1.setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY);
+        {
+            PhoneTimeZoneSuggestion one = builder1.build();
+            PhoneTimeZoneSuggestion two = builder2.build();
+            assertEquals(one, two);
+        }
+
+        builder1.setQuality(PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE);
+        builder2.setQuality(PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS);
+        {
+            PhoneTimeZoneSuggestion one = builder1.build();
+            PhoneTimeZoneSuggestion two = builder2.build();
+            assertNotEquals(one, two);
+        }
+
+        builder1.setQuality(PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS);
+        {
+            PhoneTimeZoneSuggestion one = builder1.build();
+            PhoneTimeZoneSuggestion two = builder2.build();
+            assertEquals(one, two);
+        }
+
+        // DebugInfo must not be considered in equals().
+        {
+            PhoneTimeZoneSuggestion one = builder1.build();
+            PhoneTimeZoneSuggestion two = builder2.build();
+            one.addDebugInfo("Debug info 1");
+            two.addDebugInfo("Debug info 2");
+            assertEquals(one, two);
+        }
+    }
+
+    @Test(expected = RuntimeException.class)
+    public void testBuilderValidates_emptyZone_badMatchType() {
+        PhoneTimeZoneSuggestion.Builder builder = new PhoneTimeZoneSuggestion.Builder(PHONE_ID);
+        // No zone ID, so match type should be left unset.
+        builder.setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET);
+        builder.build();
+    }
+
+    @Test(expected = RuntimeException.class)
+    public void testBuilderValidates_zoneSet_badMatchType() {
+        PhoneTimeZoneSuggestion.Builder builder = new PhoneTimeZoneSuggestion.Builder(PHONE_ID);
+        builder.setZoneId("Europe/London");
+        builder.setQuality(PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE);
+        builder.build();
+    }
+
+    @Test
+    public void testParcelable() {
+        PhoneTimeZoneSuggestion.Builder builder = new PhoneTimeZoneSuggestion.Builder(PHONE_ID);
+        assertRoundTripParcelable(builder.build());
+
+        builder.setZoneId("Europe/London");
+        builder.setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID);
+        builder.setQuality(PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE);
+        PhoneTimeZoneSuggestion suggestion1 = builder.build();
+        assertRoundTripParcelable(suggestion1);
+
+        // DebugInfo should also be stored (but is not checked by equals()
+        String debugString = "This is debug info";
+        suggestion1.addDebugInfo(debugString);
+        PhoneTimeZoneSuggestion suggestion1_2 = roundTripParcelable(suggestion1);
+        assertEquals(suggestion1, suggestion1_2);
+        assertTrue(suggestion1_2.getDebugInfo().contains(debugString));
+    }
+
+    private static void assertRoundTripParcelable(PhoneTimeZoneSuggestion instance) {
+        assertEquals(instance, roundTripParcelable(instance));
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <T extends Parcelable> T roundTripParcelable(T one) {
+        Parcel parcel = Parcel.obtain();
+        parcel.writeTypedObject(one, 0);
+        parcel.setDataPosition(0);
+
+        T toReturn = (T) parcel.readTypedObject(PhoneTimeZoneSuggestion.CREATOR);
+        parcel.recycle();
+        return toReturn;
+    }
+}
diff --git a/media/java/android/media/IMediaRoute2ProviderClient.aidl b/media/java/android/media/IMediaRoute2ProviderClient.aidl
index 6f44d45..f4fb7f4 100644
--- a/media/java/android/media/IMediaRoute2ProviderClient.aidl
+++ b/media/java/android/media/IMediaRoute2ProviderClient.aidl
@@ -17,10 +17,13 @@
 package android.media;
 
 import android.media.MediaRoute2ProviderInfo;
+import android.media.MediaRoute2Info;
+import android.os.Bundle;
 
 /**
  * @hide
  */
 oneway interface IMediaRoute2ProviderClient {
     void updateProviderInfo(in MediaRoute2ProviderInfo info);
+    void notifyRouteSelected(String packageName, String routeId, in Bundle controlHints, int seq);
 }
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index 386d2dc..1b6183e 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -18,14 +18,19 @@
 
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.Service;
 import android.content.Intent;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.util.Log;
 
+import java.util.Objects;
+
 /**
  * @hide
  */
@@ -44,7 +49,7 @@
     }
 
     @Override
-    public IBinder onBind(Intent intent) {
+    public IBinder onBind(@NonNull Intent intent) {
         //TODO: Allow binding from media router service only?
         if (SERVICE_INTERFACE.equals(intent.getAction())) {
             if (mStub == null) {
@@ -57,11 +62,17 @@
 
     /**
      * Called when selectRoute is called on a route of the provider.
+     * Once the route is ready to be used , call {@link #notifyRouteSelected(SelectToken, Bundle)}
+     * to notify that.
      *
      * @param packageName the package name of the application that selected the route
      * @param routeId the id of the route being selected
+     * @param token token that contains select info
+     *
+     * @see #notifyRouteSelected
      */
-    public abstract void onSelectRoute(String packageName, String routeId);
+    public abstract void onSelectRoute(@NonNull String packageName, @NonNull String routeId,
+            @NonNull SelectToken token);
 
     /**
      * Called when unselectRoute is called on a route of the provider.
@@ -69,7 +80,7 @@
      * @param packageName the package name of the application that has selected the route.
      * @param routeId the id of the route being unselected
      */
-    public abstract void onUnselectRoute(String packageName, String routeId);
+    public abstract void onUnselectRoute(@NonNull String packageName, @NonNull String routeId);
 
     /**
      * Called when sendControlRequest is called on a route of the provider
@@ -78,21 +89,21 @@
      * @param request the media control request intent
      */
     //TODO: Discuss what to use for request (e.g., Intent? Request class?)
-    public abstract void onControlRequest(String routeId, Intent request);
+    public abstract void onControlRequest(@NonNull String routeId, @NonNull Intent request);
 
     /**
      * Called when requestSetVolume is called on a route of the provider
      * @param routeId the id of the route
      * @param volume the target volume
      */
-    public abstract void onSetVolume(String routeId, int volume);
+    public abstract void onSetVolume(@NonNull String routeId, int volume);
 
     /**
      * Called when requestUpdateVolume is called on a route of the provider
      * @param routeId id of the route
      * @param delta the delta to add to the current volume
      */
-    public abstract void onUpdateVolume(String routeId, int delta);
+    public abstract void onUpdateVolume(@NonNull String routeId, int delta);
 
     /**
      * Updates provider info and publishes routes
@@ -102,6 +113,29 @@
         publishState();
     }
 
+    /**
+     * Notifies the client of that the selected route is ready for use. If the selected route can be
+     * controlled, pass a {@link Bundle} that contains how to control it.
+     *
+     * @param token token passed in {@link #onSelectRoute}
+     * @param controlHints a {@link Bundle} that contains how to control the given route.
+     * Pass {@code null} if the route is not available.
+     */
+    public final void notifyRouteSelected(@NonNull SelectToken token,
+            @Nullable Bundle controlHints) {
+        Objects.requireNonNull(token, "token must not be null");
+
+        if (mClient == null) {
+            return;
+        }
+        try {
+            mClient.notifyRouteSelected(token.mPackageName, token.mRouteId,
+                    controlHints, token.mSeq);
+        } catch (RemoteException ex) {
+            Log.w(TAG, "Failed to notify route selected");
+        }
+    }
+
     void setClient(IMediaRoute2ProviderClient client) {
         mClient = client;
         publishState();
@@ -118,6 +152,23 @@
         }
     }
 
+    /**
+     * Route selection information.
+     *
+     * @see #notifyRouteSelected
+     */
+    public final class SelectToken {
+        final String mPackageName;
+        final String mRouteId;
+        final int mSeq;
+
+        SelectToken(String packageName, String routeId, int seq) {
+            mPackageName = packageName;
+            mRouteId = routeId;
+            mSeq = seq;
+        }
+    }
+
     final class ProviderStub extends IMediaRoute2Provider.Stub {
         ProviderStub() { }
 
@@ -129,10 +180,10 @@
 
         @Override
         public void requestSelectRoute(String packageName, String id, int seq) {
-            // TODO: When introducing MediaRoute2ProviderService#sendConnectionHints(),
-            // use the sequence number here properly.
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSelectRoute,
-                    MediaRoute2ProviderService.this, packageName, id));
+                    MediaRoute2ProviderService.this, packageName, id,
+                    new SelectToken(packageName, id, seq)));
+
         }
 
         @Override
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index 9cb7869..7b15d95 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -1318,6 +1318,7 @@
         sStatic.rebindAsUser(userId);
     }
 
+    //TODO: remove this and Client1Record in MediaRouter2ServiceImpl.
     /**
      * Sets the control categories of the application.
      * Routes that support at least one of the given control categories only exists and are handled
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 94ac77a..35cb066 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -57,7 +57,8 @@
     @IntDef(value = {
             SELECT_REASON_UNKNOWN,
             SELECT_REASON_USER_SELECTED,
-            SELECT_REASON_FALLBACK})
+            SELECT_REASON_FALLBACK,
+            SELECT_REASON_SYSTEM_SELECTED})
     public @interface SelectReason {}
 
     /**
@@ -80,6 +81,13 @@
      */
     public static final int SELECT_REASON_FALLBACK = 2;
 
+    /**
+     * This is passed from {@link com.android.server.media.MediaRouterService} when the route
+     * is selected in response to a request from other apps (e.g. System UI).
+     * @hide
+     */
+    public static final int SELECT_REASON_SYSTEM_SELECTED = 3;
+
     private static final String TAG = "MR2";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     private static final Object sLock = new Object();
@@ -485,6 +493,9 @@
             }
             mSelectingRoute = null;
         }
+        if (reason == SELECT_REASON_SYSTEM_SELECTED) {
+            reason = SELECT_REASON_USER_SELECTED;
+        }
         mSelectedRoute = route;
         notifyRouteSelected(route, reason, controlHints);
     }
diff --git a/media/jni/android_media_MediaDataSource.h b/media/jni/android_media_MediaDataSource.h
index 378baf4..b65039d3 100644
--- a/media/jni/android_media_MediaDataSource.h
+++ b/media/jni/android_media_MediaDataSource.h
@@ -19,7 +19,7 @@
 
 #include "jni.h"
 
-#include <media/IDataSource.h>
+#include <android/IDataSource.h>
 #include <media/stagefright/foundation/ABase.h>
 #include <utils/Errors.h>
 #include <utils/Mutex.h>
diff --git a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
index f4f8d0b..6650f96 100644
--- a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
+++ b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
@@ -20,6 +20,7 @@
 import android.media.MediaRoute2Info;
 import android.media.MediaRoute2ProviderInfo;
 import android.media.MediaRoute2ProviderService;
+import android.os.Bundle;
 import android.os.IBinder;
 
 import java.util.HashMap;
@@ -95,7 +96,7 @@
     }
 
     @Override
-    public void onSelectRoute(String packageName, String routeId) {
+    public void onSelectRoute(String packageName, String routeId, SelectToken token) {
         MediaRoute2Info route = mRoutes.get(routeId);
         if (route == null) {
             return;
@@ -104,6 +105,7 @@
                 .setClientPackageName(packageName)
                 .build());
         publishRoutes();
+        notifyRouteSelected(token, Bundle.EMPTY);
     }
 
     @Override
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
index d0f7c78..c70ad8d 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
@@ -23,23 +23,19 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
 
 import android.content.Context;
 import android.content.Intent;
 import android.media.MediaRoute2Info;
 import android.media.MediaRouter2;
 import android.media.MediaRouter2Manager;
+import android.os.Bundle;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.text.TextUtils;
 
-import org.junit.Assert;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -95,9 +91,14 @@
     private Executor mExecutor;
     private String mPackageName;
 
+    private final List<MediaRouter2Manager.Callback> mManagerCallbacks = new ArrayList<>();
+    private final List<MediaRouter2.Callback> mRouterCallbacks = new ArrayList<>();
+    private Map<String, MediaRoute2Info> mRoutes;
+
     private static final List<String> CATEGORIES_ALL = new ArrayList();
     private static final List<String> CATEGORIES_SPECIAL = new ArrayList();
     private static final List<String> CATEGORIES_LIVE_AUDIO = new ArrayList<>();
+
     static {
         CATEGORIES_ALL.add(CATEGORY_SAMPLE);
         CATEGORIES_ALL.add(CATEGORY_SPECIAL);
@@ -108,6 +109,7 @@
         CATEGORIES_LIVE_AUDIO.add(CATEGORY_LIVE_AUDIO);
     }
 
+
     @Before
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getTargetContext();
@@ -116,6 +118,16 @@
         //TODO: If we need to support thread pool executors, change this to thread pool executor.
         mExecutor = Executors.newSingleThreadExecutor();
         mPackageName = mContext.getPackageName();
+
+        // ensure media router 2 client
+        addRouterCallback(new MediaRouter2.Callback());
+        mRoutes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+    }
+
+    @After
+    public void tearDown() {
+        // unregister callbacks
+        clearCallbacks();
     }
 
     //TODO: Move to a separate file
@@ -132,10 +144,13 @@
         assertNotEquals(routeInfo1, routeInfo3);
     }
 
+    /**
+     * Tests if routes are added correctly when a new callback is registered.
+     */
     @Test
     public void testOnRoutesAdded() throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
-        MediaRouter2Manager.Callback callback = new MediaRouter2Manager.Callback() {
+        addManagerCallback(new MediaRouter2Manager.Callback() {
             @Override
             public void onRoutesAdded(List<MediaRoute2Info> routes) {
                 assertTrue(routes.size() > 0);
@@ -145,27 +160,15 @@
                     }
                 }
             }
-        };
-        mManager.registerCallback(mExecutor, callback);
+        });
 
         assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        mManager.unregisterCallback(callback);
     }
 
     @Test
     public void testOnRoutesRemoved() throws Exception {
-        MediaRouter2Manager.Callback mockCallback = mock(MediaRouter2Manager.Callback.class);
-        mManager.registerCallback(mExecutor, mockCallback);
-
-        MediaRouter2.Callback routerCallback = new MediaRouter2.Callback();
-        mRouter2.registerCallback(mExecutor, routerCallback);
-
-        Map<String, MediaRoute2Info> routes =
-                waitAndGetRoutesWithManager(CATEGORIES_ALL);
-
         CountDownLatch latch = new CountDownLatch(1);
-        MediaRouter2Manager.Callback callback = new MediaRouter2Manager.Callback() {
+        addManagerCallback(new MediaRouter2Manager.Callback() {
             @Override
             public void onRoutesRemoved(List<MediaRoute2Info> routes) {
                 assertTrue(routes.size() > 0);
@@ -175,16 +178,12 @@
                     }
                 }
             }
-        };
-        mManager.registerCallback(mExecutor, callback);
+        });
 
         //TODO: Figure out a more proper way to test.
         // (Control requests shouldn't be used in this way.)
-        mRouter2.sendControlRequest(routes.get(ROUTE_ID2), new Intent(ACTION_REMOVE_ROUTE));
+        mRouter2.sendControlRequest(mRoutes.get(ROUTE_ID2), new Intent(ACTION_REMOVE_ROUTE));
         assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        mRouter2.unregisterCallback(routerCallback);
-        mManager.unregisterCallback(mockCallback);
     }
 
     /**
@@ -192,16 +191,10 @@
      */
     @Test
     public void testControlCategory() throws Exception {
-        MediaRouter2Manager.Callback mockCallback = mock(MediaRouter2Manager.Callback.class);
-        mManager.registerCallback(mExecutor, mockCallback);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_SPECIAL);
 
-        Map<String, MediaRoute2Info> routes =
-                waitAndGetRoutesWithManager(CATEGORIES_SPECIAL);
-
-        Assert.assertEquals(1, routes.size());
-        Assert.assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY));
-
-        mManager.unregisterCallback(mockCallback);
+        assertEquals(1, routes.size());
+        assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY));
     }
 
     /**
@@ -209,37 +202,60 @@
      */
     @Test
     public void testGetRoutes() throws Exception {
-        MediaRouter2.Callback mockCallback = mock(MediaRouter2.Callback.class);
-        mRouter2.registerCallback(mExecutor, mockCallback);
-
         Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CATEGORIES_SPECIAL);
 
-        Assert.assertEquals(1, routes.size());
-        Assert.assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY));
-
-        mRouter2.unregisterCallback(mockCallback);
+        assertEquals(1, routes.size());
+        assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY));
     }
 
+    /**
+     * Tests if MR2.Callback.onRouteSelected is called when a route is selected from MR2Manager.
+     */
     @Test
-    public void testOnRouteSelected() throws Exception {
-        MediaRouter2.Callback routerCallback = new MediaRouter2.Callback();
-        MediaRouter2Manager.Callback managerCallback = mock(MediaRouter2Manager.Callback.class);
+    public void testRouterOnRouteSelected() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
 
-        mManager.registerCallback(mExecutor, managerCallback);
-        mRouter2.registerCallback(mExecutor, routerCallback);
+        addRouterCallback(new MediaRouter2.Callback() {
+            @Override
+            public void onRouteSelected(MediaRoute2Info route, int reason, Bundle controlHints) {
+                if (route != null && TextUtils.equals(route.getId(), ROUTE_ID1)) {
+                    latch.countDown();
+                }
+            }
+        });
 
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
-
-        MediaRoute2Info routeToSelect = routes.get(ROUTE_ID1);
+        MediaRoute2Info routeToSelect = mRoutes.get(ROUTE_ID1);
         assertNotNull(routeToSelect);
 
         mManager.selectRoute(mPackageName, routeToSelect);
-        verify(managerCallback, timeout(TIMEOUT_MS))
-                .onRouteSelected(eq(mPackageName),
-                        argThat(route -> route != null && route.equals(routeToSelect)));
 
-        mRouter2.unregisterCallback(routerCallback);
-        mManager.unregisterCallback(managerCallback);
+        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    }
+
+    /**
+     * Tests if MR2Manager.Callback.onRouteSelected is called
+     * when a route is selected by MR2Manager.
+     */
+    @Test
+    public void testManagerOnRouteSelected() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+
+        addManagerCallback(new MediaRouter2Manager.Callback() {
+            @Override
+            public void onRouteSelected(String packageName, MediaRoute2Info route) {
+                if (TextUtils.equals(mPackageName, packageName)
+                        && route != null && TextUtils.equals(route.getId(), ROUTE_ID1)) {
+                    latch.countDown();
+                }
+            }
+        });
+
+        MediaRoute2Info routeToSelect = mRoutes.get(ROUTE_ID1);
+        assertNotNull(routeToSelect);
+
+        mManager.selectRoute(mPackageName, routeToSelect);
+
+        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
     }
 
     /**
@@ -247,19 +263,13 @@
      */
     @Test
     public void testSingleProviderSelect() throws Exception {
-        MediaRouter2.Callback routerCallback = mock(MediaRouter2.Callback.class);
-
-        mRouter2.registerCallback(mExecutor, routerCallback);
-
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
-
         awaitOnRouteChangedManager(
-                () -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID1)),
+                () -> mManager.selectRoute(mPackageName, mRoutes.get(ROUTE_ID1)),
                 ROUTE_ID1,
                 route -> TextUtils.equals(route.getClientPackageName(), mPackageName));
 
         awaitOnRouteChangedManager(
-                () -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID2)),
+                () -> mManager.selectRoute(mPackageName, mRoutes.get(ROUTE_ID2)),
                 ROUTE_ID2,
                 route -> TextUtils.equals(route.getClientPackageName(), mPackageName));
 
@@ -267,8 +277,6 @@
                 () -> mManager.unselectRoute(mPackageName),
                 ROUTE_ID2,
                 route -> TextUtils.equals(route.getClientPackageName(), null));
-
-        mRouter2.unregisterCallback(routerCallback);
     }
 
     @Test
@@ -292,12 +300,7 @@
 
     @Test
     public void testControlVolumeWithManager() throws Exception {
-        MediaRouter2.Callback mockCallback = mock(MediaRouter2.Callback.class);
-
-        mRouter2.registerCallback(mExecutor, mockCallback);
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
-
-        MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
+        MediaRoute2Info volRoute = mRoutes.get(ROUTE_ID_VARIABLE_VOLUME);
         int originalVolume = volRoute.getVolume();
         int deltaVolume = (originalVolume == volRoute.getVolumeMax() ? -1 : 1);
 
@@ -310,24 +313,16 @@
                 () -> mManager.requestSetVolume(volRoute, originalVolume),
                 ROUTE_ID_VARIABLE_VOLUME,
                 (route -> route.getVolume() == originalVolume));
-
-        mRouter2.unregisterCallback(mockCallback);
     }
 
     @Test
     public void testVolumeHandling() throws Exception {
-        MediaRouter2.Callback mockCallback = mock(MediaRouter2.Callback.class);
-        mRouter2.registerCallback(mExecutor, mockCallback);
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CATEGORIES_ALL);
-
-        MediaRoute2Info fixedVolumeRoute = routes.get(ROUTE_ID_FIXED_VOLUME);
-        MediaRoute2Info variableVolumeRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
+        MediaRoute2Info fixedVolumeRoute = mRoutes.get(ROUTE_ID_FIXED_VOLUME);
+        MediaRoute2Info variableVolumeRoute = mRoutes.get(ROUTE_ID_VARIABLE_VOLUME);
 
         assertEquals(PLAYBACK_VOLUME_FIXED, fixedVolumeRoute.getVolumeHandling());
         assertEquals(PLAYBACK_VOLUME_VARIABLE, variableVolumeRoute.getVolumeHandling());
         assertEquals(VOLUME_MAX, variableVolumeRoute.getVolumeMax());
-
-        mRouter2.unregisterCallback(mockCallback);
     }
 
     @Test
@@ -368,6 +363,7 @@
                     latch.countDown();
                 }
             }
+
             @Override
             public void onControlCategoriesChanged(String packageName) {
                 if (TextUtils.equals(mPackageName, packageName)) {
@@ -401,7 +397,7 @@
         };
         mRouter2.registerCallback(mExecutor, callback);
         try {
-            new Thread(task).start();
+            task.run();
             assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
         } finally {
             mRouter2.unregisterCallback(callback);
@@ -422,7 +418,7 @@
         };
         mManager.registerCallback(mExecutor, callback);
         try {
-            new Thread(task).start();
+            task.run();
             assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
         } finally {
             mManager.unregisterCallback(callback);
@@ -433,9 +429,31 @@
     static Map<String, MediaRoute2Info> createRouteMap(List<MediaRoute2Info> routes) {
         Map<String, MediaRoute2Info> routeMap = new HashMap<>();
         for (MediaRoute2Info route : routes) {
-            // intentionally not route.getUniqueId() for convenience.
+            // intentionally not using route.getUniqueId() for convenience.
             routeMap.put(route.getId(), route);
         }
         return routeMap;
     }
+
+    private void addManagerCallback(MediaRouter2Manager.Callback callback) {
+        mManagerCallbacks.add(callback);
+        mManager.registerCallback(mExecutor, callback);
+    }
+
+    private void addRouterCallback(MediaRouter2.Callback callback) {
+        mRouterCallbacks.add(callback);
+        mRouter2.registerCallback(mExecutor, callback);
+    }
+
+    private void clearCallbacks() {
+        for (MediaRouter2Manager.Callback callback : mManagerCallbacks) {
+            mManager.unregisterCallback(callback);
+        }
+        mManagerCallbacks.clear();
+
+        for (MediaRouter2.Callback callback : mRouterCallbacks) {
+            mRouter2.unregisterCallback(callback);
+        }
+        mRouterCallbacks.clear();
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java
index 1c62879..0a1a122 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java
@@ -76,6 +76,19 @@
     }
 
     /**
+     * Logs the elapsed time from onAttach to calling {@link #writeElapsedTimeMetric(int, String)}.
+     * @param action : The value of the Action Enums.
+     * @param key : The value of special key string.
+     */
+    public void writeElapsedTimeMetric(int action, String key) {
+        if (mMetricsFeature == null || mMetricsCategory == METRICS_CATEGORY_UNKNOWN) {
+            return;
+        }
+        final int elapse = (int) (SystemClock.elapsedRealtime() - mTimestamp);
+        mMetricsFeature.action(METRICS_CATEGORY_UNKNOWN, action, mMetricsCategory, key, elapse);
+    }
+
+    /**
      * Sets source metrics category for this logger. Source is the caller that opened this UI.
      */
     public void setSourceMetricsCategory(Activity activity) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index f2af40e..b0bdf1d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -33,7 +33,6 @@
 import android.net.NetworkScoreManager;
 import android.net.NetworkScorerAppData;
 import android.net.ScoredNetwork;
-import android.net.wifi.IWifiManager;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiConfiguration.KeyMgmt;
@@ -47,7 +46,6 @@
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -1612,13 +1610,8 @@
         final ConnectivityManager cm = (ConnectivityManager)
                 context.getSystemService(Context.CONNECTIVITY_SERVICE);
         if (state == DetailedState.CONNECTED) {
-            IWifiManager wifiManager = IWifiManager.Stub.asInterface(
-                    ServiceManager.getService(Context.WIFI_SERVICE));
-            NetworkCapabilities nc = null;
-
-            try {
-                nc = cm.getNetworkCapabilities(wifiManager.getCurrentNetwork());
-            } catch (RemoteException e) {}
+            WifiManager wifiManager = context.getSystemService(WifiManager.class);
+            NetworkCapabilities nc = cm.getNetworkCapabilities(wifiManager.getCurrentNetwork());
 
             if (nc != null) {
                 if (nc.hasCapability(nc.NET_CAPABILITY_CAPTIVE_PORTAL)) {
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/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
index 1c2c640..8a4a1c6 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
@@ -153,6 +153,7 @@
                 Slog.v(TAG, "lockGen=" + lockGeneration + " : lockChanged=" + lockChanged);
                 Slog.v(TAG, "sysEligble=" + sysEligible);
                 Slog.v(TAG, "lockEligible=" + lockEligible);
+                Slog.v(TAG, "hasLockWallpaper=" + hasLockWallpaper);
             }
 
             // only back up the wallpapers if we've been told they're eligible
@@ -174,6 +175,17 @@
                 prefs.edit().putInt(SYSTEM_GENERATION, sysGeneration).apply();
             }
 
+            // If there's no lock wallpaper, then we have nothing to add to the backup.
+            if (lockGeneration == -1) {
+                if (lockChanged && lockImageStage.exists()) {
+                    if (DEBUG) Slog.v(TAG, "Removed lock wallpaper; deleting");
+                    lockImageStage.delete();
+                }
+                if (DEBUG) Slog.v(TAG, "No lock paper set, add nothing to backup");
+                prefs.edit().putInt(LOCK_GENERATION, lockGeneration).apply();
+                return;
+            }
+
             // Don't try to store the lock image if we overran our quota last time
             if (lockEligible && hasLockWallpaper && mLockWallpaperFile.exists() && !mQuotaExceeded) {
                 if (lockChanged || !lockImageStage.exists()) {
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/tests/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/tests/WallpaperBackupAgentTest.java
index 46a7dfe..255fdef 100644
--- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/tests/WallpaperBackupAgentTest.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/tests/WallpaperBackupAgentTest.java
@@ -19,8 +19,13 @@
 import static android.app.WallpaperManager.FLAG_LOCK;
 import static android.app.WallpaperManager.FLAG_SYSTEM;
 
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -40,6 +45,7 @@
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -53,78 +59,109 @@
     private static final String SYSTEM_GENERATION = "system_gen";
     private static final String LOCK_GENERATION = "lock_gen";
 
+    private static final int TEST_SYSTEM_WALLPAPER_ID = 1;
+    private static final int TEST_LOCK_WALLPAPER_ID = 2;
+
     @Mock private FullBackupDataOutput mOutput;
     @Mock private WallpaperManager mWallpaperManager;
     @Mock private SharedPreferences mSharedPreferences;
+    @Mock private SharedPreferences.Editor mSharedPreferenceEditor;
 
     @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
 
     private ContextWithServiceOverrides mContext;
+    private IsolatedWallpaperBackupAgent mWallpaperBackupAgent;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
+        when(mSharedPreferences.edit()).thenReturn(mSharedPreferenceEditor);
+        when(mSharedPreferenceEditor.putInt(anyString(), anyInt()))
+                .thenReturn(mSharedPreferenceEditor);
+        doNothing().when(mSharedPreferenceEditor).apply();
+
         mContext = new ContextWithServiceOverrides(ApplicationProvider.getApplicationContext());
         mContext.injectSystemService(WallpaperManager.class, mWallpaperManager);
         mContext.setSharedPreferencesOverride(mSharedPreferences);
+
+        mWallpaperBackupAgent = new IsolatedWallpaperBackupAgent(mTemporaryFolder.getRoot());
+        mWallpaperBackupAgent.attach(mContext);
+        mWallpaperBackupAgent.onCreate();
     }
 
     @Test
     public void testOnFullBackup_withNoChanges_onlyBacksUpEmptyFile() throws IOException {
-        WallpaperBackupAgent wallpaperBackupAgent = new WallpaperBackupAgent();
-        initialiseAgent(wallpaperBackupAgent);
+        mockBackedUpState();
+        mockCurrentWallpapers(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID);
 
-        when(mWallpaperManager.getWallpaperIdForUser(eq(FLAG_SYSTEM), eq(UserHandle.USER_SYSTEM)))
-                .thenReturn(1);
-        when(mWallpaperManager.getWallpaperIdForUser(eq(FLAG_LOCK), eq(UserHandle.USER_SYSTEM)))
-                .thenReturn(1);
-        when(mSharedPreferences.getInt(eq(SYSTEM_GENERATION), eq(-1))).thenReturn(1);
-        when(mSharedPreferences.getInt(eq(LOCK_GENERATION), eq(-1))).thenReturn(1);
+        mWallpaperBackupAgent.onFullBackup(mOutput);
 
-        wallpaperBackupAgent.onFullBackup(mOutput);
-
-        verify(mOutput); // Backup of empty file only
+        assertThat(mWallpaperBackupAgent.mBackedUpFiles.size()).isEqualTo(1);
+        assertThat(mWallpaperBackupAgent.mBackedUpFiles.get(0).getName()).isEqualTo("empty");
     }
 
     @Test
     public void testOnFullBackup_withOnlyChangedSystem_updatesTheSharedPreferences()
             throws IOException {
-        // Create a system wallpaper file
-        mTemporaryFolder.newFile("wallpaper_orig");
-        // Create stageing file to simulate he wallpaper being ready to back up
-        new File(mContext.getFilesDir(), "wallpaper-stage").createNewFile();
+        mockSystemWallpaperReadyToBackUp();
+        mockUnbackedUpState();
+        mockCurrentWallpapers(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID);
 
-        WallpaperBackupAgent wallpaperBackupAgent =
-                new IsolatedWallpaperBackupAgent(mTemporaryFolder.getRoot());
-        initialiseAgent(wallpaperBackupAgent);
+        mWallpaperBackupAgent.onFullBackup(mOutput);
 
-        SharedPreferences.Editor preferenceEditor = mock(SharedPreferences.Editor.class);
+        verify(mSharedPreferenceEditor).putInt(eq(SYSTEM_GENERATION), eq(TEST_SYSTEM_WALLPAPER_ID));
+    }
 
+    @Test
+    public void testOnFullBackup_withLockChangedToMatchSystem_updatesTheSharedPreferences()
+            throws IOException {
+        mockBackedUpState();
+        mockSystemWallpaperReadyToBackUp();
+        mockCurrentWallpapers(TEST_SYSTEM_WALLPAPER_ID, -1);
+
+        mWallpaperBackupAgent.onFullBackup(mOutput);
+
+        InOrder inOrder = inOrder(mSharedPreferenceEditor);
+        inOrder.verify(mSharedPreferenceEditor)
+                .putInt(eq(SYSTEM_GENERATION), eq(TEST_SYSTEM_WALLPAPER_ID));
+        inOrder.verify(mSharedPreferenceEditor).apply();
+        inOrder.verify(mSharedPreferenceEditor).putInt(eq(LOCK_GENERATION), eq(-1));
+        inOrder.verify(mSharedPreferenceEditor).apply();
+    }
+
+    private void mockUnbackedUpState() {
+        mockCurrentWallpapers(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID);
+        when(mSharedPreferences.getInt(eq(SYSTEM_GENERATION), eq(-1))).thenReturn(-1);
+        when(mSharedPreferences.getInt(eq(LOCK_GENERATION), eq(-1))).thenReturn(-1);
+    }
+
+    private void mockBackedUpState() {
+        when(mSharedPreferences.getInt(eq(SYSTEM_GENERATION), eq(-1)))
+                .thenReturn(TEST_SYSTEM_WALLPAPER_ID);
+        when(mSharedPreferences.getInt(eq(LOCK_GENERATION), eq(-1)))
+                .thenReturn(TEST_LOCK_WALLPAPER_ID);
+    }
+
+    private void mockCurrentWallpapers(int systemWallpaperId, int lockWallpaperId) {
         when(mWallpaperManager.getWallpaperIdForUser(eq(FLAG_SYSTEM), eq(UserHandle.USER_SYSTEM)))
-                .thenReturn(2);
+                .thenReturn(systemWallpaperId);
         when(mWallpaperManager.getWallpaperIdForUser(eq(FLAG_LOCK), eq(UserHandle.USER_SYSTEM)))
-                .thenReturn(1);
+                .thenReturn(lockWallpaperId);
         when(mWallpaperManager.isWallpaperBackupEligible(eq(FLAG_SYSTEM))).thenReturn(true);
         when(mWallpaperManager.isWallpaperBackupEligible(eq(FLAG_LOCK))).thenReturn(true);
-        when(mSharedPreferences.getInt(eq(SYSTEM_GENERATION), eq(-1))).thenReturn(1);
-        when(mSharedPreferences.getInt(eq(LOCK_GENERATION), eq(-1))).thenReturn(1);
-        when(mSharedPreferences.edit()).thenReturn(preferenceEditor);
-        when(preferenceEditor.putInt(eq(SYSTEM_GENERATION), eq(2))).thenReturn(preferenceEditor);
-
-        wallpaperBackupAgent.onFullBackup(mOutput);
-
-        verify(preferenceEditor).putInt(eq(SYSTEM_GENERATION), eq(2));
     }
 
-    private void initialiseAgent(WallpaperBackupAgent agent) {
-        agent.attach(mContext);
-        agent.onCreate();
+    private void mockSystemWallpaperReadyToBackUp() throws IOException {
+        // Create a system wallpaper file
+        mTemporaryFolder.newFile("wallpaper_orig");
+        // Create staging file to simulate he wallpaper being ready to back up
+        new File(mContext.getFilesDir(), "wallpaper-stage").createNewFile();
     }
 
-    private static class IsolatedWallpaperBackupAgent extends WallpaperBackupAgent {
+    private class IsolatedWallpaperBackupAgent extends WallpaperBackupAgent {
         File mWallpaperBaseDirectory;
-        List<File> mBackedUpFiles = new ArrayList();
+        List<File> mBackedUpFiles = new ArrayList<>();
 
         IsolatedWallpaperBackupAgent(File wallpaperBaseDirectory) {
             mWallpaperBaseDirectory = wallpaperBaseDirectory;
@@ -139,5 +176,10 @@
         protected void backupFile(File file, FullBackupDataOutput data) {
             mBackedUpFiles.add(file);
         }
+
+        @Override
+        public SharedPreferences getSharedPreferences(File file, int mode) {
+            return mSharedPreferences;
+        }
     }
 }
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 222a6f2..e8c5299 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -67,6 +67,7 @@
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -1510,6 +1511,26 @@
     }
 
     /**
+     * Excludes keys from KV restore for a given package. The corresponding data will be excluded
+     * from the data set available the backup agent during restore. However,  final list  of keys
+     * that have been excluded will be passed to the agent to make it aware of the exclusions.
+     */
+    public void excludeKeysFromRestore(String packageName, List<String> keys) {
+        int userId = Binder.getCallingUserHandle().getIdentifier();
+        if (!isUserReadyForBackup(userId)) {
+            Slog.w(TAG, "Returning from excludeKeysFromRestore as backup for user" + userId +
+                    " is not initialized yet");
+            return;
+        }
+        UserBackupManagerService userBackupManagerService =
+                getServiceForUserIfCallerHasPermission(userId, "excludeKeysFromRestore()");
+
+        if (userBackupManagerService != null) {
+            userBackupManagerService.excludeKeysFromRestore(packageName, keys);
+        }
+    }
+
+    /**
      * Returns the {@link UserBackupManagerService} instance for the specified user {@code userId}.
      * If the user is not registered with the service (either the user is locked or not eligible for
      * the backup service) then return {@code null}.
diff --git a/services/backup/java/com/android/server/backup/DataChangedJournal.java b/services/backup/java/com/android/server/backup/DataChangedJournal.java
index 498185c..e75eb73 100644
--- a/services/backup/java/com/android/server/backup/DataChangedJournal.java
+++ b/services/backup/java/com/android/server/backup/DataChangedJournal.java
@@ -17,6 +17,7 @@
 package com.android.server.backup;
 
 import android.annotation.Nullable;
+import android.util.Slog;
 
 import java.io.BufferedInputStream;
 import java.io.DataInputStream;
@@ -36,6 +37,7 @@
  * reboot.
  */
 public class DataChangedJournal {
+    private static final String TAG = "DataChangedJournal";
     private static final String FILE_NAME_PREFIX = "journal";
 
     /**
@@ -139,7 +141,12 @@
      */
     static ArrayList<DataChangedJournal> listJournals(File journalDirectory) {
         ArrayList<DataChangedJournal> journals = new ArrayList<>();
-        for (File file : journalDirectory.listFiles()) {
+        File[] journalFiles = journalDirectory.listFiles();
+        if (journalFiles == null) {
+            Slog.w(TAG, "Failed to read journal files");
+            return journals;
+        }
+        for (File file : journalFiles) {
             journals.add(new DataChangedJournal(file));
         }
         return journals;
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 77888db..56b345b 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -158,6 +158,7 @@
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.Queue;
 import java.util.Random;
 import java.util.Set;
@@ -332,6 +333,8 @@
     // locking around the pending-backup management
     private final Object mQueueLock = new Object();
 
+    private final UserBackupPreferences mBackupPreferences;
+
     // The thread performing the sequence of queued backups binds to each app's agent
     // in succession.  Bind notifications are asynchronously delivered through the
     // Activity Manager; use this lock object to signal when a requested binding has
@@ -632,6 +635,8 @@
         // the pending backup set
         mBackupHandler.postDelayed(this::parseLeftoverJournals, INITIALIZATION_DELAY_MILLIS);
 
+        mBackupPreferences = new UserBackupPreferences(mContext, mBaseStateDir);
+
         // Power management
         mWakelock = new BackupWakeLock(
                 mPowerManager.newWakeLock(
@@ -1097,6 +1102,14 @@
         }
     }
 
+    public Map<String, Set<String>> getExcludedRestoreKeys(String... packages) {
+        return mBackupPreferences.getExcludedRestoreKeysForPackages(packages);
+    }
+
+    public Map<String, Set<String>> getAllExcludedRestoreKeys() {
+        return mBackupPreferences.getAllExcludedRestoreKeys();
+    }
+
     /** Used for generating random salts or passwords. */
     public byte[] randomBytes(int bits) {
         byte[] array = new byte[bits / 8];
@@ -2746,6 +2759,14 @@
         }
     }
 
+    /**
+     * Excludes keys from KV restore for a given package. The keys won't be part of the data passed
+     * to the backup agent during restore.
+     */
+    public void excludeKeysFromRestore(String packageName, List<String> keys) {
+        mBackupPreferences.addExcludedKeys(packageName, keys);
+    }
+
     private boolean startConfirmationUi(int token, String action) {
         try {
             Intent confIntent = new Intent(action);
@@ -3341,7 +3362,8 @@
                                 restoreSet,
                                 packageName,
                                 token,
-                                listener);
+                                listener,
+                                getExcludedRestoreKeys(packageName));
                 mBackupHandler.sendMessage(msg);
             } catch (Exception e) {
                 // Calling into the transport broke; back off and proceed with the installation.
diff --git a/services/backup/java/com/android/server/backup/UserBackupPreferences.java b/services/backup/java/com/android/server/backup/UserBackupPreferences.java
new file mode 100644
index 0000000..41b9719
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/UserBackupPreferences.java
@@ -0,0 +1,63 @@
+/*
+ * 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.backup;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/** Manages the persisted backup preferences per user. */
+public class UserBackupPreferences {
+    private static final String PREFERENCES_FILE = "backup_preferences";
+
+    private final SharedPreferences mPreferences;
+    private final SharedPreferences.Editor mEditor;
+
+    UserBackupPreferences(Context conext, File storageDir) {
+        File excludedKeysFile = new File(storageDir, PREFERENCES_FILE);
+        mPreferences = conext.getSharedPreferences(excludedKeysFile, Context.MODE_PRIVATE);
+        mEditor = mPreferences.edit();
+    }
+
+    void addExcludedKeys(String packageName, List<String> keys) {
+        Set<String> existingKeys =
+                new HashSet<>(mPreferences.getStringSet(packageName, Collections.emptySet()));
+        existingKeys.addAll(keys);
+        mEditor.putStringSet(packageName, existingKeys);
+        mEditor.commit();
+    }
+
+    Map<String, Set<String>> getExcludedRestoreKeysForPackages(String... packages) {
+        Map<String, Set<String>> excludedKeys = new HashMap<>();
+        for (String packageName : packages) {
+            excludedKeys.put(packageName,
+                    mPreferences.getStringSet(packageName, Collections.emptySet()));
+        }
+        return excludedKeys;
+    }
+
+    Map<String, Set<String>> getAllExcludedRestoreKeys() {
+        return (Map<String, Set<String>>) mPreferences.getAll();
+    }
+}
diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
index 059b1b9..8c48b84 100644
--- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java
+++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
@@ -279,7 +279,8 @@
                                 params.pmToken,
                                 params.isSystemRestore,
                                 params.filterSet,
-                                params.listener);
+                                params.listener,
+                                params.excludedKeys);
 
                 synchronized (backupManagerService.getPendingRestores()) {
                     if (backupManagerService.isRestoreInProgress()) {
diff --git a/services/backup/java/com/android/server/backup/params/RestoreParams.java b/services/backup/java/com/android/server/backup/params/RestoreParams.java
index c9a6b60..09b7e35 100644
--- a/services/backup/java/com/android/server/backup/params/RestoreParams.java
+++ b/services/backup/java/com/android/server/backup/params/RestoreParams.java
@@ -24,6 +24,9 @@
 import com.android.server.backup.internal.OnTaskFinishedListener;
 import com.android.server.backup.transport.TransportClient;
 
+import java.util.Map;
+import java.util.Set;
+
 public class RestoreParams {
     public final TransportClient transportClient;
     public final IRestoreObserver observer;
@@ -34,6 +37,7 @@
     public final boolean isSystemRestore;
     @Nullable public final String[] filterSet;
     public final OnTaskFinishedListener listener;
+    public final Map<String, Set<String>> excludedKeys;
 
     /**
      * No kill after restore.
@@ -44,7 +48,8 @@
             IBackupManagerMonitor monitor,
             long token,
             PackageInfo packageInfo,
-            OnTaskFinishedListener listener) {
+            OnTaskFinishedListener listener,
+            Map<String, Set<String>> excludedKeys) {
         return new RestoreParams(
                 transportClient,
                 observer,
@@ -54,7 +59,8 @@
                 /* pmToken */ 0,
                 /* isSystemRestore */ false,
                 /* filterSet */ null,
-                listener);
+                listener,
+                excludedKeys);
     }
 
     /**
@@ -67,7 +73,8 @@
             long token,
             String packageName,
             int pmToken,
-            OnTaskFinishedListener listener) {
+            OnTaskFinishedListener listener,
+            Map<String, Set<String>> excludedKeys) {
         String[] filterSet = {packageName};
         return new RestoreParams(
                 transportClient,
@@ -78,7 +85,8 @@
                 pmToken,
                 /* isSystemRestore */ false,
                 filterSet,
-                listener);
+                listener,
+                excludedKeys);
     }
 
     /**
@@ -89,7 +97,8 @@
             IRestoreObserver observer,
             IBackupManagerMonitor monitor,
             long token,
-            OnTaskFinishedListener listener) {
+            OnTaskFinishedListener listener,
+            Map<String, Set<String>> excludedKeys) {
         return new RestoreParams(
                 transportClient,
                 observer,
@@ -99,7 +108,8 @@
                 /* pmToken */ 0,
                 /* isSystemRestore */ true,
                 /* filterSet */ null,
-                listener);
+                listener,
+                excludedKeys);
     }
 
     /**
@@ -112,7 +122,8 @@
             long token,
             String[] filterSet,
             boolean isSystemRestore,
-            OnTaskFinishedListener listener) {
+            OnTaskFinishedListener listener,
+            Map<String, Set<String>> excludedKeys) {
         return new RestoreParams(
                 transportClient,
                 observer,
@@ -122,7 +133,8 @@
                 /* pmToken */ 0,
                 isSystemRestore,
                 filterSet,
-                listener);
+                listener,
+                excludedKeys);
     }
 
     private RestoreParams(
@@ -134,7 +146,8 @@
             int pmToken,
             boolean isSystemRestore,
             @Nullable String[] filterSet,
-            OnTaskFinishedListener listener) {
+            OnTaskFinishedListener listener,
+            Map<String, Set<String>> excludedKeys) {
         this.transportClient = transportClient;
         this.observer = observer;
         this.monitor = monitor;
@@ -144,5 +157,6 @@
         this.isSystemRestore = isSystemRestore;
         this.filterSet = filterSet;
         this.listener = listener;
+        this.excludedKeys = excludedKeys;
     }
 }
diff --git a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
index 5a57cdc..c0f76c3 100644
--- a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
+++ b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
@@ -178,7 +178,8 @@
                                                 observer,
                                                 monitor,
                                                 token,
-                                                listener),
+                                                listener,
+                                                mBackupManagerService.getAllExcludedRestoreKeys()),
                                 "RestoreSession.restoreAll()");
                     } finally {
                         Binder.restoreCallingIdentity(oldId);
@@ -271,7 +272,8 @@
                                                 token,
                                                 packages,
                                                 /* isSystemRestore */ packages.length > 1,
-                                                listener),
+                                                listener,
+                                                mBackupManagerService.getExcludedRestoreKeys(packages)),
                                 "RestoreSession.restorePackages(" + packages.length + " packages)");
                     } finally {
                         Binder.restoreCallingIdentity(oldId);
@@ -363,7 +365,8 @@
                                     monitor,
                                     token,
                                     app,
-                                    listener),
+                                    listener,
+                                    mBackupManagerService.getExcludedRestoreKeys(app.packageName)),
                     "RestoreSession.restorePackage(" + packageName + ")");
         } finally {
             Binder.restoreCallingIdentity(oldId);
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index 675a6eb..be597d7 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -52,6 +52,7 @@
 import android.util.EventLog;
 import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.backup.IBackupTransport;
 import com.android.internal.util.Preconditions;
 import com.android.server.AppWidgetBackupBridge;
@@ -77,6 +78,8 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 public class PerformUnifiedRestoreTask implements BackupRestoreTask {
 
@@ -151,6 +154,8 @@
     // When finished call listener
     private final OnTaskFinishedListener mListener;
 
+    private final Map<String, Set<String>> mExcludedKeys;
+
     // Key/value: bookkeeping about staged data and files for agent access
     private File mBackupDataName;
     private File mStageName;
@@ -161,6 +166,17 @@
     private final int mEphemeralOpToken;
     private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
 
+    @VisibleForTesting
+    PerformUnifiedRestoreTask(Map<String, Set<String>> excludedKeys) {
+        mExcludedKeys = excludedKeys;
+        mListener = null;
+        mAgentTimeoutParameters = null;
+        mTransportClient = null;
+        mTransportManager = null;
+        mEphemeralOpToken = 0;
+        mUserId = 0;
+    }
+
     // This task can assume that the wakelock is properly held for it and doesn't have to worry
     // about releasing it.
     public PerformUnifiedRestoreTask(
@@ -173,7 +189,8 @@
             int pmToken,
             boolean isFullSystemRestore,
             @Nullable String[] filterSet,
-            OnTaskFinishedListener listener) {
+            OnTaskFinishedListener listener,
+            Map<String, Set<String>> excludedKeys) {
         this.backupManagerService = backupManagerService;
         mUserId = backupManagerService.getUserId();
         mTransportManager = backupManagerService.getTransportManager();
@@ -195,6 +212,8 @@
                 backupManagerService.getAgentTimeoutParameters(),
                 "Timeout parameters cannot be null");
 
+        mExcludedKeys = excludedKeys;
+
         if (targetPackage != null) {
             // Single package restore
             mAcceptSet = new ArrayList<>();
@@ -724,27 +743,7 @@
 
                 BackupDataInput in = new BackupDataInput(stage.getFileDescriptor());
                 BackupDataOutput out = new BackupDataOutput(mBackupData.getFileDescriptor());
-                byte[] buffer = new byte[8192]; // will grow when needed
-                while (in.readNextHeader()) {
-                    final String key = in.getKey();
-                    final int size = in.getDataSize();
-
-                    // is this a special key?
-                    if (key.equals(KEY_WIDGET_STATE)) {
-                        if (DEBUG) {
-                            Slog.i(TAG, "Restoring widget state for " + packageName);
-                        }
-                        mWidgetData = new byte[size];
-                        in.readEntityData(mWidgetData, 0, size);
-                    } else {
-                        if (size > buffer.length) {
-                            buffer = new byte[size];
-                        }
-                        in.readEntityData(buffer, 0, size);
-                        out.writeEntityHeader(key, size);
-                        out.writeEntityData(buffer, size);
-                    }
-                }
+                filterExcludedKeys(packageName, in, out);
 
                 mBackupData.close();
             }
@@ -783,6 +782,39 @@
         }
     }
 
+    @VisibleForTesting
+    void filterExcludedKeys(String packageName, BackupDataInput in, BackupDataOutput out)
+            throws Exception {
+        Set<String> excludedKeysForPackage = mExcludedKeys.get(packageName);
+
+        byte[] buffer = new byte[8192]; // will grow when needed
+        while (in.readNextHeader()) {
+            final String key = in.getKey();
+            final int size = in.getDataSize();
+
+            if (excludedKeysForPackage != null && excludedKeysForPackage.contains(key)) {
+                in.skipEntityData();
+                continue;
+            }
+
+            // is this a special key?
+            if (key.equals(KEY_WIDGET_STATE)) {
+                if (DEBUG) {
+                    Slog.i(TAG, "Restoring widget state for " + packageName);
+                }
+                mWidgetData = new byte[size];
+                in.readEntityData(mWidgetData, 0, size);
+            } else {
+                if (size > buffer.length) {
+                    buffer = new byte[size];
+                }
+                in.readEntityData(buffer, 0, size);
+                out.writeEntityHeader(key, size);
+                out.writeEntityData(buffer, size);
+            }
+        }
+    }
+
     // state RESTORE_FULL : restore one package via streaming engine
     private void restoreFull() {
         // None of this can run on the work looper here, so we spin asynchronous
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 9291adc..2eb2f33 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -83,7 +83,6 @@
         ":storaged_aidl",
         ":vold_aidl",
         ":platform-compat-config",
-        ":tethering-servicescore-srcs",
         "java/com/android/server/EventLogTags.logtags",
         "java/com/android/server/am/EventLogTags.logtags",
         "java/com/android/server/wm/EventLogTags.logtags",
@@ -115,7 +114,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",
@@ -164,11 +162,3 @@
     name: "protolog.conf.json.gz",
     src: ":services.core.json.gz",
 }
-
-// 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/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 413a79d..de6cca5 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -768,9 +768,10 @@
      * @param userId user to uninstall apex package for. Must be
      *               {@link android.os.UserHandle#USER_ALL}, otherwise failure will be reported.
      * @param intentSender a {@link IntentSender} to send result of an uninstall to.
+     * @param flags flags about the uninstall.
      */
     public abstract void uninstallApex(String packageName, long versionCode, int userId,
-            IntentSender intentSender);
+            IntentSender intentSender, int flags);
 
     /**
      * Get fingerprint of build that updated the runtime permissions for a user.
diff --git a/services/core/java/android/os/UserManagerInternal.java b/services/core/java/android/os/UserManagerInternal.java
index 9a7cb3f..a2e9341 100644
--- a/services/core/java/android/os/UserManagerInternal.java
+++ b/services/core/java/android/os/UserManagerInternal.java
@@ -15,6 +15,7 @@
  */
 package android.os;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -22,13 +23,24 @@
 import android.content.pm.UserInfo;
 import android.graphics.Bitmap;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * @hide Only for use within the system server.
  */
 public abstract class UserManagerInternal {
-    public static final int CAMERA_NOT_DISABLED = 0;
-    public static final int CAMERA_DISABLED_LOCALLY = 1;
-    public static final int CAMERA_DISABLED_GLOBALLY = 2;
+
+    public static final int OWNER_TYPE_DEVICE_OWNER = 0;
+    public static final int OWNER_TYPE_PROFILE_OWNER = 1;
+    public static final int OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE = 2;
+    public static final int OWNER_TYPE_NO_OWNER = 3;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {OWNER_TYPE_DEVICE_OWNER, OWNER_TYPE_PROFILE_OWNER,
+            OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE, OWNER_TYPE_NO_OWNER})
+    public @interface OwnerType {
+    }
 
     public interface UserRestrictionsListener {
         /**
@@ -47,13 +59,19 @@
      *
      * @param userId target user id for the local restrictions.
      * @param restrictions a bundle of user restrictions.
-     * @param isDeviceOwner whether {@code userId} corresponds to device owner user id.
-     * @param cameraRestrictionScope is camera disabled and if so what is the scope of restriction.
-     *        Should be one of {@link #CAMERA_NOT_DISABLED}, {@link #CAMERA_DISABLED_LOCALLY} or
-     *                               {@link #CAMERA_DISABLED_GLOBALLY}
+     * @param restrictionOwnerType determines which admin {@code userId} corresponds to.
+     *             The admin can be either
+     *             {@link UserManagerInternal#OWNER_TYPE_DEVICE_OWNER},
+     *             {@link UserManagerInternal#OWNER_TYPE_PROFILE_OWNER},
+     *             {@link UserManagerInternal#OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE}
+     *             or {@link UserManagerInternal#OWNER_TYPE_NO_OWNER}.
+     *             If the admin is a DEVICE_OWNER or a PROFILE_OWNER_ORG_OWNED_DEVICE then
+     *             a restriction may be applied globally depending on which restriction it is,
+     *             otherwise it will be applied just on the current user.
+     * @see OwnerType
      */
     public abstract void setDevicePolicyUserRestrictions(int userId, @Nullable Bundle restrictions,
-            boolean isDeviceOwner, int cameraRestrictionScope);
+            @OwnerType int restrictionOwnerType);
 
     /**
      * Returns the "base" user restrictions.
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/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index bc50956..e2fe76e 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -247,7 +247,14 @@
         List<MonitoredPackage> packages = new ArrayList<>();
         for (int i = 0; i < packageNames.size(); i++) {
             // Health checks not available yet so health check state will start INACTIVE
-            packages.add(new MonitoredPackage(packageNames.get(i), durationMs, false));
+            MonitoredPackage pkg = newMonitoredPackage(packageNames.get(i), durationMs, false);
+            if (pkg != null) {
+                packages.add(pkg);
+            }
+        }
+
+        if (packages.isEmpty()) {
+            return;
         }
 
         // Sync before we add the new packages to the observers. This will #pruneObservers,
@@ -634,16 +641,8 @@
                 if (registeredObserver != null) {
                     Iterator<MonitoredPackage> it = failedPackages.iterator();
                     while (it.hasNext()) {
-                        String failedPackage = it.next().getName();
-                        Slog.i(TAG, "Explicit health check failed for package " + failedPackage);
-                        VersionedPackage versionedPkg = getVersionedPackage(failedPackage);
-                        if (versionedPkg == null) {
-                            Slog.w(TAG, "Explicit health check failed but could not find package "
-                                    + failedPackage);
-                            // TODO(b/120598832): Skip. We only continue to pass tests for now since
-                            // the tests don't install any packages
-                            versionedPkg = new VersionedPackage(failedPackage, 0L);
-                        }
+                        VersionedPackage versionedPkg = it.next().mPackage;
+                        Slog.i(TAG, "Explicit health check failed for package " + versionedPkg);
                         registeredObserver.execute(versionedPkg);
                     }
                 }
@@ -654,7 +653,7 @@
     @Nullable
     private VersionedPackage getVersionedPackage(String packageName) {
         final PackageManager pm = mContext.getPackageManager();
-        if (pm == null) {
+        if (pm == null || TextUtils.isEmpty(packageName)) {
             return null;
         }
         try {
@@ -848,7 +847,7 @@
         public void updatePackagesLocked(List<MonitoredPackage> packages) {
             for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
                 MonitoredPackage p = packages.get(pIndex);
-                this.packages.put(p.mName, p);
+                this.packages.put(p.getName(), p);
             }
         }
 
@@ -872,7 +871,7 @@
                 int newState = p.handleElapsedTimeLocked(elapsedMs);
                 if (oldState != HealthCheckState.FAILED
                         && newState == HealthCheckState.FAILED) {
-                    Slog.i(TAG, "Package " + p.mName + " failed health check");
+                    Slog.i(TAG, "Package " + p.getName() + " failed health check");
                     failedPackages.add(p);
                 }
                 if (p.isExpiredLocked()) {
@@ -925,9 +924,10 @@
                                             ATTR_EXPLICIT_HEALTH_CHECK_DURATION));
                             boolean hasPassedHealthCheck = Boolean.parseBoolean(
                                     parser.getAttributeValue(null, ATTR_PASSED_HEALTH_CHECK));
-                            if (!TextUtils.isEmpty(packageName)) {
-                                packages.add(watchdog.new MonitoredPackage(packageName, duration,
-                                        healthCheckDuration, hasPassedHealthCheck));
+                            MonitoredPackage pkg = watchdog.newMonitoredPackage(packageName,
+                                    duration, healthCheckDuration, hasPassedHealthCheck);
+                            if (pkg != null) {
+                                packages.add(pkg);
                             }
                         } catch (NumberFormatException e) {
                             Slog.wtf(TAG, "Skipping package for observer " + observerName, e);
@@ -963,6 +963,20 @@
         int FAILED = 3;
     }
 
+    MonitoredPackage newMonitoredPackage(
+            String name, long durationMs, boolean hasPassedHealthCheck) {
+        return newMonitoredPackage(name, durationMs, Long.MAX_VALUE, hasPassedHealthCheck);
+    }
+
+    MonitoredPackage newMonitoredPackage(String name, long durationMs, long healthCheckDurationMs,
+            boolean hasPassedHealthCheck) {
+        VersionedPackage pkg = getVersionedPackage(name);
+        if (pkg == null) {
+            return null;
+        }
+        return new MonitoredPackage(pkg, durationMs, healthCheckDurationMs, hasPassedHealthCheck);
+    }
+
     /**
      * Represents a package and its health check state along with the time
      * it should be monitored for.
@@ -971,8 +985,7 @@
      * instances of this class.
      */
     class MonitoredPackage {
-        //TODO(b/120598832): VersionedPackage?
-        private final String mName;
+        private final VersionedPackage mPackage;
         // Times when package failures happen sorted in ascending order
         @GuardedBy("mLock")
         private final LongArrayQueue mFailureHistory = new LongArrayQueue();
@@ -996,13 +1009,9 @@
         @GuardedBy("mLock")
         private long mHealthCheckDurationMs = Long.MAX_VALUE;
 
-        MonitoredPackage(String name, long durationMs, boolean hasPassedHealthCheck) {
-            this(name, durationMs, Long.MAX_VALUE, hasPassedHealthCheck);
-        }
-
-        MonitoredPackage(String name, long durationMs, long healthCheckDurationMs,
-                boolean hasPassedHealthCheck) {
-            mName = name;
+        private MonitoredPackage(VersionedPackage pkg, long durationMs,
+                long healthCheckDurationMs, boolean hasPassedHealthCheck) {
+            mPackage = pkg;
             mDurationMs = durationMs;
             mHealthCheckDurationMs = healthCheckDurationMs;
             mHasPassedHealthCheck = hasPassedHealthCheck;
@@ -1013,7 +1022,7 @@
         @GuardedBy("mLock")
         public void writeLocked(XmlSerializer out) throws IOException {
             out.startTag(null, TAG_PACKAGE);
-            out.attribute(null, ATTR_NAME, mName);
+            out.attribute(null, ATTR_NAME, getName());
             out.attribute(null, ATTR_DURATION, String.valueOf(mDurationMs));
             out.attribute(null, ATTR_EXPLICIT_HEALTH_CHECK_DURATION,
                     String.valueOf(mHealthCheckDurationMs));
@@ -1053,7 +1062,7 @@
         public int setHealthCheckActiveLocked(long initialHealthCheckDurationMs) {
             if (initialHealthCheckDurationMs <= 0) {
                 Slog.wtf(TAG, "Cannot set non-positive health check duration "
-                        + initialHealthCheckDurationMs + "ms for package " + mName
+                        + initialHealthCheckDurationMs + "ms for package " + getName()
                         + ". Using total duration " + mDurationMs + "ms instead");
                 initialHealthCheckDurationMs = mDurationMs;
             }
@@ -1072,7 +1081,7 @@
         @GuardedBy("mLock")
         public int handleElapsedTimeLocked(long elapsedMs) {
             if (elapsedMs <= 0) {
-                Slog.w(TAG, "Cannot handle non-positive elapsed time for package " + mName);
+                Slog.w(TAG, "Cannot handle non-positive elapsed time for package " + getName());
                 return mHealthCheckState;
             }
             // Transitions to FAILED if now <= 0 and health check not passed
@@ -1105,7 +1114,7 @@
 
         /** Returns the monitored package name. */
         private String getName() {
-            return mName;
+            return mPackage.getPackageName();
         }
 
         /**
@@ -1170,7 +1179,7 @@
             } else {
                 mHealthCheckState = HealthCheckState.ACTIVE;
             }
-            Slog.i(TAG, "Updated health check state for package " + mName + ": "
+            Slog.i(TAG, "Updated health check state for package " + getName() + ": "
                     + toString(oldState) + " -> " + toString(mHealthCheckState));
             return mHealthCheckState;
         }
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index a4bc3bd..9d1eb6c 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -118,6 +118,7 @@
 import android.util.ArrayMap;
 import android.util.AtomicFile;
 import android.util.DataUnit;
+import android.util.FeatureFlagUtils;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
@@ -819,7 +820,6 @@
                     refreshFuseSettings();
                 });
         refreshIsolatedStorageSettings();
-        refreshFuseSettings();
     }
 
     /**
@@ -883,16 +883,25 @@
         SystemProperties.set(StorageManager.PROP_ISOLATED_STORAGE, Boolean.toString(res));
     }
 
+    /**
+     * The most recent flag change takes precedence. Change fuse Settings flag if Device Config is
+     * changed. Settings flag change will in turn change fuse system property (persist.sys.fuse)
+     * whenever the user reboots.
+     */
     private void refreshFuseSettings() {
         int isFuseEnabled = DeviceConfig.getInt(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
                 FUSE_ENABLED, 0);
         if (isFuseEnabled == 1) {
-            SystemProperties.set(StorageManager.PROP_FUSE, "true");
+            Slog.d(TAG, "Device Config flag for FUSE is enabled, turn Settings fuse flag on");
+            SystemProperties.set(FeatureFlagUtils.PERSIST_PREFIX
+                    + FeatureFlagUtils.SETTINGS_FUSE_FLAG, "true");
         } else if (isFuseEnabled == -1) {
-            SystemProperties.set(StorageManager.PROP_FUSE, "false");
+            Slog.d(TAG, "Device Config flag for FUSE is disabled, turn Settings fuse flag off");
+            SystemProperties.set(FeatureFlagUtils.PERSIST_PREFIX
+                    + FeatureFlagUtils.SETTINGS_FUSE_FLAG, "false");
         }
         // else, keep the build config.
-        // This can be overridden be direct adjustment of persist.sys.prop
+        // This can be overridden by direct adjustment of persist.sys.fflag.override.settings_fuse
     }
 
     /**
@@ -1548,6 +1557,8 @@
     public StorageManagerService(Context context) {
         sSelf = this;
 
+        updateFusePropFromSettings();
+
         // Snapshot feature flag used for this boot
         SystemProperties.set(StorageManager.PROP_ISOLATED_STORAGE_SNAPSHOT, Boolean.toString(
                 SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, true)));
@@ -1609,6 +1620,23 @@
         }
     }
 
+    /**
+     *  Checks if user changed the persistent settings_fuse flag from Settings UI
+     *  and updates PROP_FUSE (reboots if changed).
+     */
+    private void updateFusePropFromSettings() {
+        Boolean settingsFuseFlag = SystemProperties.getBoolean((FeatureFlagUtils.PERSIST_PREFIX
+                + FeatureFlagUtils.SETTINGS_FUSE_FLAG), false);
+        Slog.d(TAG, "The value of Settings Fuse Flag is " + settingsFuseFlag);
+        if (SystemProperties.getBoolean(StorageManager.PROP_FUSE, false) != settingsFuseFlag) {
+            Slog.d(TAG, "Set persist.sys.fuse to " + settingsFuseFlag);
+            SystemProperties.set(StorageManager.PROP_FUSE, Boolean.toString(settingsFuseFlag));
+            // Perform hard reboot to kick policy into place
+            mContext.getSystemService(PowerManager.class).reboot("Reboot device for FUSE system"
+                    + "property change to take effect");
+        }
+    }
+
     private void start() {
         connectStoraged();
         connectVold();
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/display/WifiDisplayController.java b/services/core/java/com/android/server/display/WifiDisplayController.java
index 283a78b..2992f1e 100644
--- a/services/core/java/com/android/server/display/WifiDisplayController.java
+++ b/services/core/java/com/android/server/display/WifiDisplayController.java
@@ -508,8 +508,7 @@
                 Slog.d(TAG, "updateDesiredDevice: new information "
                         + describeWifiP2pDevice(device));
             }
-            mDesiredDevice.updateSupplicantDetails(device);
-            mDesiredDevice.status = device.status;
+            mDesiredDevice.update(device);
             if (mAdvertisedDisplay != null
                     && mAdvertisedDisplay.getDeviceAddress().equals(address)) {
                 readvertiseDisplay(createWifiDisplay(mDesiredDevice));
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 67a23dd..16b7d99 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -171,7 +171,6 @@
     private final ArrayList<InputDevice>
             mTempFullKeyboards = new ArrayList<InputDevice>(); // handler thread only
     private boolean mKeyboardLayoutNotificationShown;
-    private PendingIntent mKeyboardLayoutIntent;
     private Toast mSwitchedKeyboardLayoutToast;
 
     // State for vibrator tokens.
@@ -205,6 +204,7 @@
     private static native void nativeUnregisterInputChannel(long ptr, InputChannel inputChannel);
     private static native void nativePilferPointers(long ptr, IBinder token);
     private static native void nativeSetInputFilterEnabled(long ptr, boolean enable);
+    private static native void nativeSetInTouchMode(long ptr, boolean inTouchMode);
     private static native int nativeInjectInputEvent(long ptr, InputEvent event,
             int injectorPid, int injectorUid, int syncMode, int timeoutMillis,
             int policyFlags);
@@ -603,6 +603,25 @@
         }
     }
 
+    /**
+     * Set the state of the touch mode.
+     *
+     * WindowManager remains the source of truth of the touch mode state.
+     * However, we need to keep a copy of this state in input.
+     *
+     * The apps determine the touch mode state. Therefore, a single app will
+     * affect the global state. That state change needs to be propagated to
+     * other apps, when they become focused.
+     *
+     * When input dispatches focus to the apps, the touch mode state
+     * will be sent together with the focus change.
+     *
+     * @param inTouchMode true if the device is in touch mode.
+     */
+    public void setInTouchMode(boolean inTouchMode) {
+        nativeSetInTouchMode(mPtr, inTouchMode);
+    }
+
     @Override // Binder call
     public boolean injectInputEvent(InputEvent event, int mode) {
         return injectInputEventInternal(event, mode);
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
index ace15bd..7ecd624 100644
--- a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
+++ b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
@@ -20,17 +20,19 @@
 
 import java.io.OutputStream;
 import java.util.List;
+import java.util.Optional;
 
 /** A helper class to serialize rules from the {@link Rule} model to Xml representation. */
 public class RuleBinarySerializer implements RuleSerializer {
 
     @Override
-    public void serialize(List<Rule> rules, OutputStream outputStream) {
+    public void serialize(
+            List<Rule> rules, Optional<Integer> formatVersion, OutputStream outputStream) {
         // TODO: Implement stream serializer.
     }
 
     @Override
-    public String serialize(List<Rule> rules) {
+    public String serialize(List<Rule> rules, Optional<Integer> formatVersion) {
         // TODO: Implement text serializer.
         return null;
     }
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleSerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleSerializer.java
index c45f6be..1125f78 100644
--- a/services/core/java/com/android/server/integrity/serializer/RuleSerializer.java
+++ b/services/core/java/com/android/server/integrity/serializer/RuleSerializer.java
@@ -20,13 +20,16 @@
 
 import java.io.OutputStream;
 import java.util.List;
+import java.util.Optional;
 
 /** A helper class to serialize rules from the {@link Rule} model. */
 public interface RuleSerializer {
 
     /** Serialize rules to an output stream */
-    void serialize(List<Rule> rules, OutputStream outputStream) throws RuleSerializeException;
+    void serialize(List<Rule> rules, Optional<Integer> formatVersion, OutputStream outputStream)
+            throws RuleSerializeException;
 
     /** Serialize rules to a string. */
-    String serialize(List<Rule> rule) throws RuleSerializeException;
+    String serialize(List<Rule> rule, Optional<Integer> formatVersion)
+            throws RuleSerializeException;
 }
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java
index 5dd7891..254baba 100644
--- a/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java
+++ b/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java
@@ -29,6 +29,7 @@
 import java.io.StringWriter;
 import java.nio.charset.StandardCharsets;
 import java.util.List;
+import java.util.Optional;
 
 /** A helper class to serialize rules from the {@link Rule} model to Xml representation. */
 public class RuleXmlSerializer implements RuleSerializer {
@@ -48,7 +49,8 @@
     private static final String IS_HASHED_VALUE_ATTRIBUTE = "H";
 
     @Override
-    public void serialize(List<Rule> rules, OutputStream outputStream)
+    public void serialize(
+            List<Rule> rules, Optional<Integer> formatVersion, OutputStream outputStream)
             throws RuleSerializeException {
         try {
             XmlSerializer xmlSerializer = Xml.newSerializer();
@@ -60,7 +62,8 @@
     }
 
     @Override
-    public String serialize(List<Rule> rules) throws RuleSerializeException {
+    public String serialize(List<Rule> rules, Optional<Integer> formatVersion)
+            throws RuleSerializeException {
         try {
             XmlSerializer xmlSerializer = Xml.newSerializer();
             StringWriter writer = new StringWriter();
@@ -108,8 +111,8 @@
             return;
         }
         xmlSerializer.startTag(NAMESPACE, OPEN_FORMULA_TAG);
-        serializeAttributeValue(CONNECTOR_ATTRIBUTE, String.valueOf(compoundFormula.getConnector()),
-                xmlSerializer);
+        serializeAttributeValue(
+                CONNECTOR_ATTRIBUTE, String.valueOf(compoundFormula.getConnector()), xmlSerializer);
         for (Formula formula : compoundFormula.getFormulas()) {
             serializeFormula(formula, xmlSerializer);
         }
@@ -122,24 +125,30 @@
             return;
         }
         xmlSerializer.startTag(NAMESPACE, ATOMIC_FORMULA_TAG);
-        serializeAttributeValue(KEY_ATTRIBUTE, String.valueOf(atomicFormula.getKey()),
-                xmlSerializer);
+        serializeAttributeValue(
+                KEY_ATTRIBUTE, String.valueOf(atomicFormula.getKey()), xmlSerializer);
         if (atomicFormula instanceof AtomicFormula.StringAtomicFormula) {
-            serializeAttributeValue(VALUE_ATTRIBUTE,
-                    ((AtomicFormula.StringAtomicFormula) atomicFormula).getValue(), xmlSerializer);
-            serializeAttributeValue(IS_HASHED_VALUE_ATTRIBUTE,
+            serializeAttributeValue(
+                    VALUE_ATTRIBUTE,
+                    ((AtomicFormula.StringAtomicFormula) atomicFormula).getValue(),
+                    xmlSerializer);
+            serializeAttributeValue(
+                    IS_HASHED_VALUE_ATTRIBUTE,
                     String.valueOf(
                             ((AtomicFormula.StringAtomicFormula) atomicFormula).getIsHashedValue()),
                     xmlSerializer);
         } else if (atomicFormula instanceof AtomicFormula.IntAtomicFormula) {
-            serializeAttributeValue(OPERATOR_ATTRIBUTE,
+            serializeAttributeValue(
+                    OPERATOR_ATTRIBUTE,
                     String.valueOf(((AtomicFormula.IntAtomicFormula) atomicFormula).getOperator()),
                     xmlSerializer);
-            serializeAttributeValue(VALUE_ATTRIBUTE,
+            serializeAttributeValue(
+                    VALUE_ATTRIBUTE,
                     String.valueOf(((AtomicFormula.IntAtomicFormula) atomicFormula).getValue()),
                     xmlSerializer);
         } else if (atomicFormula instanceof AtomicFormula.BooleanAtomicFormula) {
-            serializeAttributeValue(VALUE_ATTRIBUTE,
+            serializeAttributeValue(
+                    VALUE_ATTRIBUTE,
                     String.valueOf(((AtomicFormula.BooleanAtomicFormula) atomicFormula).getValue()),
                     xmlSerializer);
         } else {
@@ -149,9 +158,8 @@
         xmlSerializer.endTag(NAMESPACE, ATOMIC_FORMULA_TAG);
     }
 
-    private void serializeAttributeValue(String attribute, String value,
-            XmlSerializer xmlSerializer)
-            throws IOException {
+    private void serializeAttributeValue(
+            String attribute, String value, XmlSerializer xmlSerializer) throws IOException {
         if (value == null) {
             return;
         }
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index a4e7ac4..83b6215 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -181,6 +181,7 @@
     private static final String SEPARATE_PROFILE_CHALLENGE_KEY = "lockscreen.profilechallenge";
     private static final String PREV_SYNTHETIC_PASSWORD_HANDLE_KEY = "prev-sp-handle";
     private static final String SYNTHETIC_PASSWORD_UPDATE_TIME_KEY = "sp-handle-ts";
+    private static final String USER_SERIAL_NUMBER_KEY = "serial-number";
 
     // No challenge provided
     private static final int CHALLENGE_NONE = 0;
@@ -661,6 +662,34 @@
     }
 
     /**
+     * Clean up states associated with the given user, in case the userId is reused but LSS didn't
+     * get a chance to do cleanup previously during ACTION_USER_REMOVED.
+     *
+     * Internally, LSS stores serial number for each user and check it against the current user's
+     * serial number to determine if the userId is reused and invoke cleanup code.
+     */
+    private void cleanupDataForReusedUserIdIfNecessary(int userId) {
+        if (userId == UserHandle.USER_SYSTEM) {
+            // Short circuit as we never clean up user 0.
+            return;
+        }
+        // Serial number is never reusued, so we can use it as a distinguisher for user Id reuse.
+        int serialNumber = mUserManager.getUserSerialNumber(userId);
+
+        int storedSerialNumber = getIntUnchecked(USER_SERIAL_NUMBER_KEY, -1, userId);
+        if (storedSerialNumber != serialNumber) {
+            // If LockSettingsStorage does not have a copy of the serial number, it could be either
+            // this is a user created before the serial number recording logic is introduced, or
+            // the user does not exist or was removed and cleaned up properly. In either case, don't
+            // invoke removeUser().
+            if (storedSerialNumber != -1) {
+                removeUser(userId, /* unknownUser */ true);
+            }
+            setIntUnchecked(USER_SERIAL_NUMBER_KEY, serialNumber, userId);
+        }
+    }
+
+    /**
      * Check if profile got unlocked but the keystore is still locked. This happens on full disk
      * encryption devices since the profile may not yet be running when we consider unlocking it
      * during the normal flow. In this case unlock the keystore for the profile.
@@ -684,6 +713,7 @@
         mHandler.post(new Runnable() {
             @Override
             public void run() {
+                cleanupDataForReusedUserIdIfNecessary(userId);
                 ensureProfileKeystoreUnlocked(userId);
                 // Hide notification first, as tie managed profile lock takes time
                 hideEncryptionNotification(new UserHandle(userId));
@@ -729,9 +759,6 @@
             if (Intent.ACTION_USER_ADDED.equals(intent.getAction())) {
                 // Notify keystore that a new user was added.
                 final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
-                if (userHandle > UserHandle.USER_SYSTEM) {
-                    removeUser(userHandle, /* unknownUser= */ true);
-                }
                 final KeyStore ks = KeyStore.getInstance();
                 final UserInfo parentInfo = mUserManager.getProfileParent(userHandle);
                 final int parentHandle = parentInfo != null ? parentInfo.id : -1;
@@ -1066,6 +1093,10 @@
         setStringUnchecked(key, userId, Long.toString(value));
     }
 
+    private void setIntUnchecked(String key, int value, int userId) {
+        setStringUnchecked(key, userId, Integer.toString(value));
+    }
+
     @Override
     public void setString(String key, String value, int userId) {
         checkWritePermission(userId);
@@ -1104,6 +1135,11 @@
         return TextUtils.isEmpty(value) ? defaultValue : Long.parseLong(value);
     }
 
+    private int getIntUnchecked(String key, int defaultValue, int userId) {
+        String value = getStringUnchecked(key, null, userId);
+        return TextUtils.isEmpty(value) ? defaultValue : Integer.parseInt(value);
+    }
+
     @Override
     public String getString(String key, String defaultValue, int userId) {
         checkReadPermission(key, userId);
@@ -2171,8 +2207,8 @@
     }
 
     private void removeUser(int userId, boolean unknownUser) {
+        Slog.i(TAG, "RemoveUser: " + userId);
         mSpManager.removeUser(userId);
-        mStorage.removeUser(userId);
         mStrongAuth.removeUser(userId);
         tryRemoveUserFromSpCacheLater(userId);
 
@@ -2183,6 +2219,9 @@
         if (unknownUser || mUserManager.getUserInfo(userId).isManagedProfile()) {
             removeKeystoreProfileKey(userId);
         }
+        // Clean up storage last, this is to ensure that cleanupDataForReusedUserIdIfNecessary()
+        // can make the assumption that no USER_SERIAL_NUMBER_KEY means user is fully removed.
+        mStorage.removeUser(userId);
     }
 
     private void removeKeystoreProfileKey(int targetUserId) {
diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java
index 91c9253..9a49c16 100644
--- a/services/core/java/com/android/server/media/MediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java
@@ -22,6 +22,7 @@
 import android.content.Intent;
 import android.media.MediaRoute2Info;
 import android.media.MediaRoute2ProviderInfo;
+import android.os.Bundle;
 
 import java.util.Objects;
 
@@ -29,7 +30,7 @@
     final ComponentName mComponentName;
     final String mUniqueId;
 
-    private Callback mCallback;
+    Callback mCallback;
     private MediaRoute2ProviderInfo mProviderInfo;
 
     MediaRoute2Provider(@NonNull ComponentName componentName) {
@@ -77,6 +78,9 @@
     }
 
     public interface Callback {
-        void onProviderStateChanged(MediaRoute2Provider provider);
+        void onProviderStateChanged(@Nullable MediaRoute2Provider provider);
+        void onRouteSelected(@NonNull MediaRoute2ProviderProxy provider,
+                @NonNull String clientPackageName, @NonNull MediaRoute2Info route,
+                @Nullable Bundle controlHints, int seq);
     }
 }
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
index 3b6580a..a5abb18 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
@@ -26,6 +26,7 @@
 import android.media.MediaRoute2Info;
 import android.media.MediaRoute2ProviderInfo;
 import android.media.MediaRoute2ProviderService;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.IBinder.DeathRecipient;
@@ -253,6 +254,20 @@
         setAndNotifyProviderInfo(info);
     }
 
+    private void onRouteSelected(Connection connection,
+            String packageName, String routeId, Bundle controlHints, int seq) {
+        if (mActiveConnection != connection) {
+            return;
+        }
+        MediaRoute2ProviderInfo providerInfo = getProviderInfo();
+        MediaRoute2Info route = (providerInfo == null) ? null : providerInfo.getRoute(routeId);
+        if (route == null) {
+            Slog.w(TAG, this + ": Unknown route " + routeId + " is selected from remove provider");
+            return;
+        }
+        mCallback.onRouteSelected(this, packageName, route, controlHints, seq);
+    }
+
     private void disconnect() {
         if (mActiveConnection != null) {
             mConnectionReady = false;
@@ -341,6 +356,11 @@
         void postProviderInfoUpdated(MediaRoute2ProviderInfo info) {
             mHandler.post(() -> onProviderInfoUpdated(Connection.this, info));
         }
+
+        void postRouteSelected(String packageName, String routeId, Bundle controlHints, int seq) {
+            mHandler.post(() -> onRouteSelected(Connection.this,
+                    packageName, routeId, controlHints, seq));
+        }
     }
 
     private static final class ProviderClient extends IMediaRoute2ProviderClient.Stub  {
@@ -361,5 +381,15 @@
                 connection.postProviderInfoUpdated(info);
             }
         }
+
+        @Override
+        public void notifyRouteSelected(String packageName, String routeId,
+                Bundle controlHints, int seq) {
+            Connection connection = mConnectionRef.get();
+            if (connection != null) {
+                connection.postRouteSelected(packageName, routeId, controlHints, seq);
+            }
+        }
+
     }
 }
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 2cf920d..2c478df 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -77,7 +77,7 @@
     @GuardedBy("mLock")
     private int mCurrentUserId = -1;
     @GuardedBy("mLock")
-    private int mSelectRouteRequestSequenceNumber = 0;
+    private int mSelectRouteRequestSequenceNumber = 1;
 
     MediaRouter2ServiceImpl(Context context) {
         mContext = context;
@@ -218,7 +218,7 @@
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
-                requestSelectRoute2Locked(mAllClientRecords.get(client.asBinder()), route);
+                requestSelectRoute2Locked(mAllClientRecords.get(client.asBinder()), false, route);
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -399,10 +399,12 @@
         }
     }
 
-    private void requestSelectRoute2Locked(ClientRecord clientRecord, MediaRoute2Info route) {
+    private void requestSelectRoute2Locked(ClientRecord clientRecord, boolean selectedByManager,
+            MediaRoute2Info route) {
         if (clientRecord != null) {
             MediaRoute2Info oldRoute = clientRecord.mSelectedRoute;
             clientRecord.mSelectingRoute = route;
+            clientRecord.mIsManagerSelecting = selectedByManager;
 
             UserHandler handler = clientRecord.mUserRecord.mHandler;
             //TODO: Handle transfer instead of unselect and select
@@ -417,7 +419,6 @@
                 handler.sendMessage(obtainMessage(
                         UserHandler::requestSelectRoute, handler, clientRecord.mPackageName,
                         route, seq));
-
                 // Remove all previous timeout messages
                 for (int previousSeq : clientRecord.mSelectRouteSequenceNumbers) {
                     clientRecord.mUserRecord.mHandler.removeMessages(previousSeq);
@@ -543,7 +544,7 @@
                 Slog.w(TAG, "Ignoring route selection for unknown client.");
             }
             if (clientRecord != null && managerRecord.mTrusted) {
-                requestSelectRoute2Locked(clientRecord, route);
+                requestSelectRoute2Locked(clientRecord, true, route);
             }
         }
     }
@@ -656,7 +657,9 @@
         public final UserRecord mUserRecord;
         public final String mPackageName;
         public final List<Integer> mSelectRouteSequenceNumbers;
+
         public List<String> mControlCategories;
+        public boolean mIsManagerSelecting;
         public MediaRoute2Info mSelectingRoute;
         public MediaRoute2Info mSelectedRoute;
 
@@ -802,9 +805,8 @@
             sendMessage(PooledLambda.obtainMessage(UserHandler::updateProvider, this, provider));
         }
 
-        // TODO: When introducing MediaRoute2ProviderService#sendControlHints(),
-        // Make this method to be called.
-        public void onRouteSelectionRequestHandled(@NonNull MediaRoute2ProviderProxy provider,
+        @Override
+        public void onRouteSelected(@NonNull MediaRoute2ProviderProxy provider,
                 String clientPackageName, MediaRoute2Info route, Bundle controlHints, int seq) {
             sendMessage(PooledLambda.obtainMessage(
                     UserHandler::updateSelectedRoute, this, provider, clientPackageName, route,
@@ -917,6 +919,8 @@
                 return;
             }
 
+            //TODO: handle a case such that controlHints is null. (How should we notify MR2?)
+
             if (clientRecord.mSelectingRoute == null || !TextUtils.equals(
                     clientRecord.mSelectingRoute.getUniqueId(), selectedRoute.getUniqueId())) {
                 Log.w(TAG, "Ignoring invalid updateSelectedRoute call. selectingRoute="
@@ -929,7 +933,9 @@
 
             notifyRouteSelectedToClient(((Client2Record) clientRecord).mClient,
                     selectedRoute,
-                    MediaRouter2.SELECT_REASON_USER_SELECTED,
+                    clientRecord.mIsManagerSelecting
+                            ? MediaRouter2.SELECT_REASON_SYSTEM_SELECTED :
+                            MediaRouter2.SELECT_REASON_USER_SELECTED,
                     controlHints);
             updateClientUsage(clientRecord);
 
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/java/com/android/server/pm/PackageAbiHelperImpl.java b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
index 259200b..7e47800 100644
--- a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
+++ b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
@@ -19,6 +19,7 @@
 import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
 import static android.content.pm.PackageParser.isApkFile;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+import static android.os.incremental.IncrementalManager.isIncrementalPath;
 
 import static com.android.internal.content.NativeLibraryHelper.LIB64_DIR_NAME;
 import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME;
@@ -309,6 +310,7 @@
 
         final String nativeLibraryRootStr = initialLibraryPaths.nativeLibraryRootDir;
         final boolean useIsaSpecificSubdirs = initialLibraryPaths.nativeLibraryRootRequiresIsa;
+        final boolean onIncremental = isIncrementalPath(pkg.codePath);
 
         String primaryCpuAbi = null;
         String secondaryCpuAbi = null;
@@ -341,10 +343,18 @@
                 int abi64 = PackageManager.NO_NATIVE_LIBRARIES;
                 if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
                     if (extractLibs) {
-                        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyNativeBinaries");
-                        abi32 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
-                                nativeLibraryRoot, Build.SUPPORTED_32_BIT_ABIS,
-                                useIsaSpecificSubdirs);
+                        if (onIncremental) {
+                            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER,
+                                    "incrementalNativeBinaries");
+                            abi32 = NativeLibraryHelper.configureNativeBinariesForSupportedAbi(pkg,
+                                    handle, nativeLibraryRoot, Build.SUPPORTED_32_BIT_ABIS,
+                                    useIsaSpecificSubdirs);
+                        } else {
+                            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyNativeBinaries");
+                            abi32 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
+                                    nativeLibraryRoot, Build.SUPPORTED_32_BIT_ABIS,
+                                    useIsaSpecificSubdirs);
+                        }
                     } else {
                         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "findSupportedAbi");
                         abi32 = NativeLibraryHelper.findSupportedAbi(
@@ -364,10 +374,18 @@
 
                 if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
                     if (extractLibs) {
-                        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyNativeBinaries");
-                        abi64 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
-                                nativeLibraryRoot, Build.SUPPORTED_64_BIT_ABIS,
-                                useIsaSpecificSubdirs);
+                        if (onIncremental) {
+                            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER,
+                                    "incrementalNativeBinaries");
+                            abi64 = NativeLibraryHelper.configureNativeBinariesForSupportedAbi(pkg,
+                                    handle, nativeLibraryRoot, Build.SUPPORTED_64_BIT_ABIS,
+                                    useIsaSpecificSubdirs);
+                        } else {
+                            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyNativeBinaries");
+                            abi64 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
+                                    nativeLibraryRoot, Build.SUPPORTED_64_BIT_ABIS,
+                                    useIsaSpecificSubdirs);
+                        }
                     } else {
                         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "findSupportedAbi");
                         abi64 = NativeLibraryHelper.findSupportedAbi(
@@ -418,9 +436,15 @@
 
                 final int copyRet;
                 if (extractLibs) {
-                    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyNativeBinaries");
-                    copyRet = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
-                            nativeLibraryRoot, abiList, useIsaSpecificSubdirs);
+                    if (onIncremental) {
+                        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "incrementalNativeBinaries");
+                        copyRet = NativeLibraryHelper.configureNativeBinariesForSupportedAbi(pkg,
+                                handle, nativeLibraryRoot, abiList, useIsaSpecificSubdirs);
+                    } else {
+                        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyNativeBinaries");
+                        copyRet = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
+                                nativeLibraryRoot, abiList, useIsaSpecificSubdirs);
+                    }
                 } else {
                     Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "findSupportedAbi");
                     copyRet = NativeLibraryHelper.findSupportedAbi(handle, abiList);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 0e075b1..8439a0d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -87,6 +87,7 @@
 import static android.content.pm.PackageManager.RESTRICTION_NONE;
 import static android.content.pm.PackageParser.isApkFile;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+import static android.os.incremental.IncrementalManager.isIncrementalPath;
 import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
 import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
 import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
@@ -226,6 +227,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.UserManagerInternal;
+import android.os.incremental.IncrementalManager;
 import android.os.storage.DiskInfo;
 import android.os.storage.IStorageManager;
 import android.os.storage.StorageEventListener;
@@ -1074,6 +1076,8 @@
 
     private Future<?> mPrepareAppDataFuture;
 
+    private final IncrementalManager mIncrementalManager;
+
     private static class IFVerificationParams {
         PackageParser.Package pkg;
         boolean replacing;
@@ -2509,6 +2513,8 @@
         mPermissionManager = injector.getPermissionManagerServiceInternal();
         mSettings = injector.getSettings();
         mPermissionManagerService = (IPermissionManager) ServiceManager.getService("permissionmgr");
+        mIncrementalManager =
+                (IncrementalManager) mContext.getSystemService(Context.INCREMENTAL_SERVICE);
 
         // CHECKSTYLE:ON IndentationCheck
         t.traceEnd();
@@ -8798,6 +8804,7 @@
         // Full APK verification can be skipped during certificate collection, only if the file is
         // in verified partition, or can be verified on access (when apk verity is enabled). In both
         // cases, only data in Signing Block is verified instead of the whole file.
+        // TODO(b/136132412): skip for Incremental installation
         final boolean skipVerify = scanSystemPartition
                 || (forceCollect && canSkipForcedPackageVerification(pkg));
         collectCertificatesLI(pkgSetting, pkg, forceCollect, skipVerify);
@@ -14675,9 +14682,16 @@
             final File afterCodeFile = getNextCodePath(targetDir, pkg.packageName);
 
             if (DEBUG_INSTALL) Slog.d(TAG, "Renaming " + beforeCodeFile + " to " + afterCodeFile);
+            final boolean onIncremental = mIncrementalManager != null
+                    && isIncrementalPath(beforeCodeFile.getAbsolutePath());
             try {
-                Os.rename(beforeCodeFile.getAbsolutePath(), afterCodeFile.getAbsolutePath());
-            } catch (ErrnoException e) {
+                if (onIncremental) {
+                    mIncrementalManager.rename(beforeCodeFile.getAbsolutePath(),
+                            afterCodeFile.getAbsolutePath());
+                } else {
+                    Os.rename(beforeCodeFile.getAbsolutePath(), afterCodeFile.getAbsolutePath());
+                }
+            } catch (IOException | ErrnoException e) {
                 Slog.w(TAG, "Failed to rename", e);
                 return false;
             }
@@ -14737,6 +14751,11 @@
                 return false;
             }
 
+            String codePath = codeFile.getAbsolutePath();
+            if (mIncrementalManager != null && isIncrementalPath(codePath)) {
+                mIncrementalManager.closeStorage(codePath);
+            }
+
             removeCodePathLI(codeFile);
 
             if (resourceFile != null && !FileUtils.contains(codeFile, resourceFile)) {
@@ -15928,6 +15947,8 @@
                             & PackageManagerService.SCAN_AS_INSTANT_APP) != 0);
             final PackageParser.Package pkg = reconciledPkg.pkgSetting.pkg;
             final String packageName = pkg.packageName;
+            final boolean onIncremental = mIncrementalManager != null
+                    && isIncrementalPath(pkg.codePath);
             prepareAppDataAfterInstallLIF(pkg);
             if (reconciledPkg.prepareResult.clearCodeCache) {
                 clearAppDataLIF(pkg, UserHandle.USER_ALL, FLAG_STORAGE_DE | FLAG_STORAGE_CE
@@ -15958,6 +15979,7 @@
             // We only need to dexopt if the package meets ALL of the following conditions:
             //   1) it is not an instant app or if it is then dexopt is enabled via gservices.
             //   2) it is not debuggable.
+            //   3) it is not on Incremental File System.
             //
             // Note that we do not dexopt instant apps by default. dexopt can take some time to
             // complete, so we skip this step during installation. Instead, we'll take extra time
@@ -15968,7 +15990,8 @@
             final boolean performDexopt =
                     (!instantApp || Global.getInt(mContext.getContentResolver(),
                     Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0)
-                    && ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0);
+                    && ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0)
+                    && (!onIncremental);
 
             if (performDexopt) {
                 // Compile the layout resources.
@@ -16200,6 +16223,7 @@
             if (args.signingDetails != PackageParser.SigningDetails.UNKNOWN) {
                 pkg.setSigningDetails(args.signingDetails);
             } else {
+                // TODO(b/136132412): skip for Incremental installation
                 PackageParser.collectCertificates(pkg, false /* skipVerify */);
             }
         } catch (PackageParserException e) {
@@ -23523,7 +23547,7 @@
 
         @Override
         public void uninstallApex(String packageName, long versionCode, int userId,
-                IntentSender intentSender) {
+                IntentSender intentSender, int flags) {
             final int callerUid = Binder.getCallingUid();
             if (callerUid != Process.ROOT_UID && callerUid != Process.SHELL_UID) {
                 throw new SecurityException("Not allowed to uninstall apexes");
@@ -23532,7 +23556,7 @@
                     new PackageInstallerService.PackageDeleteObserverAdapter(
                             PackageManagerService.this.mContext, intentSender, packageName,
                             false, userId);
-            if (userId != UserHandle.USER_ALL) {
+            if ((flags & PackageManager.DELETE_ALL_USERS) == 0) {
                 adapter.onPackageDeleted(packageName, PackageManager.DELETE_FAILED_ABORTED,
                         "Can't uninstall an apex for a single user");
                 return;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 6d86078e..9afc9a8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -1853,7 +1853,7 @@
 
         if (internal.isApexPackage(packageName)) {
             internal.uninstallApex(
-                    packageName, versionCode, translatedUserId, receiver.getIntentSender());
+                    packageName, versionCode, translatedUserId, receiver.getIntentSender(), flags);
         } else if ((flags & PackageManager.DELETE_ALL_USERS) != 0) {
             final PackageInfo info = mInterface.getPackageInfo(packageName,
                     PackageManager.MATCH_STATIC_SHARED_LIBRARIES, translatedUserId);
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 27eefc6..7ea4e98 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -620,32 +620,6 @@
                             PackageManager.INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS,
                             "Cannot stage multiple sessions without checkpoint support", null);
                 }
-
-                // TODO:b/141843321 Add support for staging multiple sessions in apexd
-                // Since apexd doesn't support multiple staged sessions yet, we have to careful how
-                // we handle apex sessions. We want to allow a set of apex sessions under the same
-                // parent to be staged when there is no previously staged apex sessions.
-                if (isApexSession(session) && isApexSession(stagedSession)) {
-                    // session is apex and it can co-exist with stagedSession only if they are from
-                    // same parent
-                    final boolean coExist;
-                    if (!session.hasParentSessionId() && !stagedSession.hasParentSessionId()) {
-                        // Both single package apex sessions. Cannot co-exist.
-                        coExist = false;
-                    } else {
-                        // At least one of the session has parent. Both must be from same parent.
-                        coExist =
-                                session.getParentSessionId() == stagedSession.getParentSessionId();
-                    }
-                    if (!coExist) {
-                        throw new PackageManagerException(
-                                PackageManager.INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS,
-                                "Package: " + session.getPackageName() + " in session: "
-                                        + session.sessionId + " cannot be staged as there is "
-                                        + "already another apex staged session: "
-                                        + stagedSession.sessionId, null);
-                    }
-                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 8144338..faff394 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1634,13 +1634,14 @@
      * See {@link UserManagerInternal#setDevicePolicyUserRestrictions}
      */
     private void setDevicePolicyUserRestrictionsInner(@UserIdInt int userId,
-            @Nullable Bundle restrictions, boolean isDeviceOwner, int cameraRestrictionScope) {
+            @Nullable Bundle restrictions,
+            @UserManagerInternal.OwnerType int restrictionOwnerType) {
         final Bundle global = new Bundle();
         final Bundle local = new Bundle();
 
         // Sort restrictions into local and global ensuring they don't overlap.
-        UserRestrictionsUtils.sortToGlobalAndLocal(restrictions, isDeviceOwner,
-                cameraRestrictionScope, global, local);
+        UserRestrictionsUtils.sortToGlobalAndLocal(restrictions, restrictionOwnerType, global,
+                local);
 
         boolean globalChanged, localChanged;
         synchronized (mRestrictionsLock) {
@@ -1650,7 +1651,7 @@
             localChanged = updateRestrictionsIfNeededLR(
                     userId, local, mDevicePolicyLocalUserRestrictions);
 
-            if (isDeviceOwner) {
+            if (restrictionOwnerType == UserManagerInternal.OWNER_TYPE_DEVICE_OWNER) {
                 // Remember the global restriction owner userId to be able to make a distinction
                 // in getUserRestrictionSource on who set local policies.
                 mDeviceOwnerUserId = userId;
@@ -4484,9 +4485,9 @@
     private class LocalService extends UserManagerInternal {
         @Override
         public void setDevicePolicyUserRestrictions(@UserIdInt int userId,
-                @Nullable Bundle restrictions, boolean isDeviceOwner, int cameraRestrictionScope) {
-            UserManagerService.this.setDevicePolicyUserRestrictionsInner(userId, restrictions,
-                isDeviceOwner, cameraRestrictionScope);
+                @Nullable Bundle restrictions, @OwnerType int restrictionOwnerType) {
+            UserManagerService.this.setDevicePolicyUserRestrictionsInner(userId,
+                    restrictions, restrictionOwnerType);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 3be51c5..f071c65 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -194,7 +194,18 @@
             UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS,
             UserManager.DISALLOW_RUN_IN_BACKGROUND,
             UserManager.DISALLOW_UNMUTE_MICROPHONE,
-            UserManager.DISALLOW_UNMUTE_DEVICE
+            UserManager.DISALLOW_UNMUTE_DEVICE,
+            UserManager.DISALLOW_CAMERA
+    );
+
+    /**
+     * Special user restrictions that are applied globally when set by the profile owner of a
+     * managed profile that was created during the device provisioning flow.
+     */
+    private static final Set<String> PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS =
+            Sets.newArraySet(
+                    UserManager.DISALLOW_CONFIG_DATE_TIME,
+                    UserManager.DISALLOW_CAMERA
     );
 
     /**
@@ -419,15 +430,9 @@
      * Takes restrictions that can be set by device owner, and sort them into what should be applied
      * globally and what should be applied only on the current user.
      */
-    public static void sortToGlobalAndLocal(@Nullable Bundle in, boolean isDeviceOwner,
-            int cameraRestrictionScope,
-            @NonNull Bundle global, @NonNull Bundle local) {
-        // Camera restriction (as well as all others) goes to at most one bundle.
-        if (cameraRestrictionScope == UserManagerInternal.CAMERA_DISABLED_GLOBALLY) {
-            global.putBoolean(UserManager.DISALLOW_CAMERA, true);
-        } else if (cameraRestrictionScope == UserManagerInternal.CAMERA_DISABLED_LOCALLY) {
-            local.putBoolean(UserManager.DISALLOW_CAMERA, true);
-        }
+    public static void sortToGlobalAndLocal(@Nullable Bundle in,
+            @UserManagerInternal.OwnerType int restrictionOwnerType, @NonNull Bundle global,
+            @NonNull Bundle local) {
         if (in == null || in.size() == 0) {
             return;
         }
@@ -435,7 +440,7 @@
             if (!in.getBoolean(key)) {
                 continue;
             }
-            if (isGlobal(isDeviceOwner, key)) {
+            if (isGlobal(restrictionOwnerType, key)) {
                 global.putBoolean(key, true);
             } else {
                 local.putBoolean(key, true);
@@ -446,9 +451,13 @@
     /**
      * Whether given user restriction should be enforced globally.
      */
-    private static boolean isGlobal(boolean isDeviceOwner, String key) {
-        return (isDeviceOwner &&
-                (PRIMARY_USER_ONLY_RESTRICTIONS.contains(key) || GLOBAL_RESTRICTIONS.contains(key)))
+    private static boolean isGlobal(@UserManagerInternal.OwnerType int restrictionOwnerType,
+            String key) {
+        return ((restrictionOwnerType == UserManagerInternal.OWNER_TYPE_DEVICE_OWNER) && (
+                PRIMARY_USER_ONLY_RESTRICTIONS.contains(key) || GLOBAL_RESTRICTIONS.contains(key)))
+                || ((restrictionOwnerType
+                == UserManagerInternal.OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE)
+                && PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS.contains(key))
                 || PROFILE_GLOBAL_RESTRICTIONS.contains(key)
                 || DEVICE_OWNER_ONLY_RESTRICTIONS.contains(key);
     }
diff --git a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
index 6abfbdc..0a6a435 100644
--- a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
+++ b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
@@ -85,28 +85,30 @@
      * System Property whether to only install system packages on a user if they're whitelisted for
      * that user type. These are flags and can be freely combined.
      * <ul>
-     * <li> 0 (0b000) - disable whitelist (install all system packages; no logging)</li>
-     * <li> 1 (0b001) - enforce (only install system packages if they are whitelisted)</li>
-     * <li> 2 (0b010) - log (log when a non-whitelisted package is run)</li>
-     * <li> 4 (0b100) - implicitly whitelist any package not mentioned in the whitelist</li>
-     * <li>-1         - use device default (as defined in res/res/values/config.xml)</li>
+     * <li> 0 (0b0000) - disable whitelist (install all system packages; no logging)</li>
+     * <li> 1 (0b0001) - enforce (only install system packages if they are whitelisted)</li>
+     * <li> 2 (0b0010) - log (log when a non-whitelisted package is run)</li>
+     * <li> 4 (0b0100) - implicitly whitelist any package not mentioned in the whitelist</li>
+     * <li> 8 (0b1000) - ignore OTAs (don't install system packages during OTAs)</li>
+     * <li>-1          - use device default (as defined in res/res/values/config.xml)</li>
      * </ul>
      * Note: This list must be kept current with config_userTypePackageWhitelistMode in
      * frameworks/base/core/res/res/values/config.xml
      */
     static final String PACKAGE_WHITELIST_MODE_PROP = "persist.debug.user.package_whitelist_mode";
     static final int USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE = 0;
-    static final int USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE = 0b001;
-    static final int USER_TYPE_PACKAGE_WHITELIST_MODE_LOG = 0b010;
-    static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST = 0b100;
+    static final int USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE = 0b0001;
+    static final int USER_TYPE_PACKAGE_WHITELIST_MODE_LOG = 0b0010;
+    static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST = 0b0100;
+    static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IGNORE_OTA = 0b1000;
     static final int USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT = -1;
 
     @IntDef(flag = true, prefix = "USER_TYPE_PACKAGE_WHITELIST_MODE_", value = {
             USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE,
             USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE,
-            USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE,
             USER_TYPE_PACKAGE_WHITELIST_MODE_LOG,
             USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST,
+            USER_TYPE_PACKAGE_WHITELIST_MODE_IGNORE_OTA,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface PackageWhitelistMode {}
@@ -169,11 +171,17 @@
     boolean installWhitelistedSystemPackages(boolean isFirstBoot, boolean isUpgrade) {
         final int mode = getWhitelistMode();
         checkWhitelistedSystemPackages(mode);
-        if (!isUpgrade && !isFirstBoot) {
+        final boolean isConsideredUpgrade = isUpgrade && !isIgnoreOtaMode(mode);
+        if (!isConsideredUpgrade && !isFirstBoot) {
+            return false;
+        }
+        if (isFirstBoot && !isEnforceMode(mode)) {
+            // Note that if !isEnforceMode, we nonetheless still install packages if isUpgrade
+            // in order to undo any previous non-installing. isFirstBoot lacks this requirement.
             return false;
         }
         Slog.i(TAG, "Reviewing whitelisted packages due to "
-                + (isFirstBoot ? "[firstBoot]" : "") + (isUpgrade ? "[upgrade]" : ""));
+                + (isFirstBoot ? "[firstBoot]" : "") + (isConsideredUpgrade ? "[upgrade]" : ""));
         final PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
         // Install/uninstall system packages per user.
         for (int userId : mUm.getUserIds()) {
@@ -185,7 +193,7 @@
                 final boolean install =
                         (userWhitelist == null || userWhitelist.contains(pkg.packageName))
                         && !pkg.applicationInfo.hiddenUntilInstalled;
-                if (isUpgrade && !isFirstBoot && !install) {
+                if (isConsideredUpgrade && !isFirstBoot && !install) {
                     return; // To be careful, we don’t uninstall apps during OTAs
                 }
                 final boolean changed = pmInt.setInstalled(pkg, userId, install);
@@ -245,6 +253,19 @@
     }
 
     /**
+     * Whether to ignore OTAs, and therefore not install missing system packages during OTAs.
+     * <p>Note:
+     * If in this mode, old system packages will not be installed on pre-existing users during OTAs.
+     * Any system packages that had not been installed at the time of the user's creation,
+     * due to {@link UserSystemPackageInstaller}'s previous actions, will therefore continue to
+     * remain uninstalled, even if the whitelist (or enforcement mode) now declares that they should
+     * be.
+     */
+    boolean isIgnoreOtaMode() {
+        return isIgnoreOtaMode(getWhitelistMode());
+    }
+
+    /**
      * Whether to log a warning concerning potential problems with the user-type package whitelist.
      */
     boolean isLogMode() {
@@ -264,6 +285,11 @@
         return (whitelistMode & USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE) != 0;
     }
 
+    /** See {@link #isIgnoreOtaMode()}. */
+    private static boolean isIgnoreOtaMode(int whitelistMode) {
+        return (whitelistMode & USER_TYPE_PACKAGE_WHITELIST_MODE_IGNORE_OTA) != 0;
+    }
+
     /** See {@link #isLogMode()}. */
     private static boolean isLogMode(int whitelistMode) {
         return (whitelistMode & USER_TYPE_PACKAGE_WHITELIST_MODE_LOG) != 0;
@@ -536,6 +562,7 @@
         pw.print(isEnforceMode(mode) ? " (enforced)" : "");
         pw.print(isLogMode(mode) ? " (logged)" : "");
         pw.print(isImplicitWhitelistMode(mode) ? " (implicit)" : "");
+        pw.print(isIgnoreOtaMode(mode) ? " (ignore OTAs)" : "");
         pw.println();
 
         pw.print(prefix); pw.println("Legend");
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index 0930975..7f5b403 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -149,6 +149,6 @@
     }
 
     private void enforceSuggestManualTimePermission() {
-        mContext.enforceCallingPermission(android.Manifest.permission.SET_TIME, "set time");
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SET_TIME, "set time");
     }
 }
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
new file mode 100644
index 0000000..23746ac
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 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.timezonedetector;
+
+import android.annotation.Nullable;
+import android.app.AlarmManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import com.android.internal.telephony.TelephonyIntents;
+
+/**
+ * The real implementation of {@link TimeZoneDetectorStrategy.Callback}.
+ */
+public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrategy.Callback {
+
+    private static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
+
+    private final Context mContext;
+    private final ContentResolver mCr;
+
+    TimeZoneDetectorCallbackImpl(Context context) {
+        mContext = context;
+        mCr = context.getContentResolver();
+    }
+
+    @Override
+    public boolean isTimeZoneDetectionEnabled() {
+        return Settings.Global.getInt(mCr, Settings.Global.AUTO_TIME_ZONE, 1 /* default */) > 0;
+    }
+
+    @Override
+    public boolean isDeviceTimeZoneInitialized() {
+        // timezone.equals("GMT") will be true and only true if the time zone was
+        // set to a default value by the system server (when starting, system server
+        // sets the persist.sys.timezone to "GMT" if it's not set). "GMT" is not used by
+        // any code that sets it explicitly (in case where something sets GMT explicitly,
+        // "Etc/GMT" Olson ID would be used).
+
+        String timeZoneId = getDeviceTimeZone();
+        return timeZoneId != null && timeZoneId.length() > 0 && !timeZoneId.equals("GMT");
+    }
+
+    @Override
+    @Nullable
+    public String getDeviceTimeZone() {
+        return SystemProperties.get(TIMEZONE_PROPERTY);
+    }
+
+    @Override
+    public void setDeviceTimeZone(String zoneId) {
+        AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class);
+        alarmManager.setTimeZone(zoneId);
+
+        // TODO Nothing in the platform appears to listen for this. Remove it.
+        Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE);
+        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+        intent.putExtra("time-zone", zoneId);
+        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+    }
+}
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
new file mode 100644
index 0000000..558aa9e
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -0,0 +1,115 @@
+/*
+ * 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.timezonedetector;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.timezonedetector.ITimeZoneDetectorService;
+import android.app.timezonedetector.PhoneTimeZoneSuggestion;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.provider.Settings;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.DumpUtils;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.FgThread;
+import com.android.server.SystemService;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Objects;
+
+/**
+ * The implementation of ITimeZoneDetectorService.aidl.
+ */
+public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub {
+    private static final String TAG = "TimeZoneDetectorService";
+
+    /**
+     * Handles the lifecycle for {@link TimeZoneDetectorService}.
+     */
+    public static class Lifecycle extends SystemService {
+
+        public Lifecycle(@NonNull Context context) {
+            super(context);
+        }
+
+        @Override
+        public void onStart() {
+            TimeZoneDetectorService service = TimeZoneDetectorService.create(getContext());
+
+            // Publish the binder service so it can be accessed from other (appropriately
+            // permissioned) processes.
+            publishBinderService(Context.TIME_ZONE_DETECTOR_SERVICE, service);
+        }
+    }
+
+    @NonNull private final Context mContext;
+    @NonNull private final Handler mHandler;
+    @NonNull private final TimeZoneDetectorStrategy mTimeZoneDetectorStrategy;
+
+    private static TimeZoneDetectorService create(@NonNull Context context) {
+        final TimeZoneDetectorStrategy timeZoneDetectorStrategy =
+                TimeZoneDetectorStrategy.create(context);
+
+        Handler handler = FgThread.getHandler();
+        ContentResolver contentResolver = context.getContentResolver();
+        contentResolver.registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.AUTO_TIME_ZONE), true,
+                new ContentObserver(handler) {
+                    public void onChange(boolean selfChange) {
+                        timeZoneDetectorStrategy.handleTimeZoneDetectionChange();
+                    }
+                });
+
+        return new TimeZoneDetectorService(context, handler, timeZoneDetectorStrategy);
+    }
+
+    @VisibleForTesting
+    public TimeZoneDetectorService(@NonNull Context context, @NonNull Handler handler,
+            @NonNull TimeZoneDetectorStrategy timeZoneDetectorStrategy) {
+        mContext = Objects.requireNonNull(context);
+        mHandler = Objects.requireNonNull(handler);
+        mTimeZoneDetectorStrategy = Objects.requireNonNull(timeZoneDetectorStrategy);
+    }
+
+    @Override
+    public void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion timeZoneSuggestion) {
+        enforceSetTimeZonePermission();
+        Objects.requireNonNull(timeZoneSuggestion);
+
+        mHandler.post(() -> mTimeZoneDetectorStrategy.suggestPhoneTimeZone(timeZoneSuggestion));
+    }
+
+    @Override
+    protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
+            @Nullable String[] args) {
+        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+
+        mTimeZoneDetectorStrategy.dumpState(pw);
+        mTimeZoneDetectorStrategy.dumpLogs(new IndentingPrintWriter(pw, " "));
+    }
+
+    private void enforceSetTimeZonePermission() {
+        mContext.enforceCallingPermission(
+                android.Manifest.permission.SET_TIME_ZONE, "set time zone");
+    }
+}
+
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
new file mode 100644
index 0000000..e24c089
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
@@ -0,0 +1,507 @@
+/*
+ * Copyright 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.timezonedetector;
+
+import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID;
+import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY;
+import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS;
+import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
+import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.timezonedetector.PhoneTimeZoneSuggestion;
+import android.content.Context;
+import android.util.ArrayMap;
+import android.util.LocalLog;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.PrintWriter;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * A singleton, stateful time zone detection strategy that is aware of multiple phone devices. It
+ * keeps track of the most recent suggestion from each phone and it uses the best based on a scoring
+ * algorithm. If several phones provide the same score then the phone with the lowest numeric ID
+ * "wins". If the situation changes and it is no longer possible to be confident about the time
+ * zone, phones must submit an empty suggestion in order to "withdraw" their previous suggestion.
+ */
+public class TimeZoneDetectorStrategy {
+
+    /**
+     * Used by {@link TimeZoneDetectorStrategy} to interact with the surrounding service. It can be
+     * faked for tests.
+     */
+    @VisibleForTesting
+    public interface Callback {
+
+        /**
+         * Returns true if automatic time zone detection is enabled in settings.
+         */
+        boolean isTimeZoneDetectionEnabled();
+
+        /**
+         * Returns true if the device has had an explicit time zone set.
+         */
+        boolean isDeviceTimeZoneInitialized();
+
+        /**
+         * Returns the device's currently configured time zone.
+         */
+        String getDeviceTimeZone();
+
+        /**
+         * Sets the device's time zone.
+         */
+        void setDeviceTimeZone(@NonNull String zoneId);
+    }
+
+    static final String LOG_TAG = "TimeZoneDetectorStrategy";
+    static final boolean DBG = false;
+
+    /**
+     * The abstract score for an empty or invalid suggestion.
+     *
+     * Used to score suggestions where there is no zone.
+     */
+    @VisibleForTesting
+    public static final int SCORE_NONE = 0;
+
+    /**
+     * The abstract score for a low quality suggestion.
+     *
+     * Used to score suggestions where:
+     * The suggested zone ID is one of several possibilities, and the possibilities have different
+     * offsets.
+     *
+     * You would have to be quite desperate to want to use this choice.
+     */
+    @VisibleForTesting
+    public static final int SCORE_LOW = 1;
+
+    /**
+     * The abstract score for a medium quality suggestion.
+     *
+     * Used for:
+     * The suggested zone ID is one of several possibilities but at least the possibilities have the
+     * same offset. Users would get the correct time but for the wrong reason. i.e. their device may
+     * switch to DST at the wrong time and (for example) their calendar events.
+     */
+    @VisibleForTesting
+    public static final int SCORE_MEDIUM = 2;
+
+    /**
+     * The abstract score for a high quality suggestion.
+     *
+     * Used for:
+     * The suggestion was for one zone ID and the answer was unambiguous and likely correct given
+     * the info available.
+     */
+    @VisibleForTesting
+    public static final int SCORE_HIGH = 3;
+
+    /**
+     * The abstract score for a highest quality suggestion.
+     *
+     * Used for:
+     * Suggestions that must "win" because they constitute test or emulator zone ID.
+     */
+    @VisibleForTesting
+    public static final int SCORE_HIGHEST = 4;
+
+    /** The threshold at which suggestions are good enough to use to set the device's time zone. */
+    @VisibleForTesting
+    public static final int SCORE_USAGE_THRESHOLD = SCORE_MEDIUM;
+
+    /** The number of previous phone suggestions to keep for each ID (for use during debugging). */
+    private static final int KEEP_SUGGESTION_HISTORY_SIZE = 30;
+
+    @NonNull
+    private final Callback mCallback;
+
+    /**
+     * A log that records the decisions / decision metadata that affected the device's time zone
+     * (for use during debugging).
+     */
+    @NonNull
+    private final LocalLog mTimeZoneChangesLog = new LocalLog(30);
+
+    /**
+     * A mapping from phoneId to a linked list of time zone suggestions (the head being the latest).
+     * We typically expect one or two entries in this Map: devices will have a small number
+     * of telephony devices and phoneIds are assumed to be stable. The LinkedList associated with
+     * the ID will not exceed {@link #KEEP_SUGGESTION_HISTORY_SIZE} in size.
+     */
+    @GuardedBy("this")
+    private ArrayMap<Integer, LinkedList<QualifiedPhoneTimeZoneSuggestion>> mSuggestionByPhoneId =
+            new ArrayMap<>();
+
+    /**
+     * The most recent best guess of time zone from all phones. Can be {@code null} to indicate
+     * there would be no current suggestion.
+     */
+    @GuardedBy("this")
+    @Nullable
+    private QualifiedPhoneTimeZoneSuggestion mCurrentSuggestion;
+
+    /**
+     * Creates a new instance of {@link TimeZoneDetectorStrategy}.
+     */
+    public static TimeZoneDetectorStrategy create(Context context) {
+        Callback timeZoneDetectionServiceHelper = new TimeZoneDetectorCallbackImpl(context);
+        return new TimeZoneDetectorStrategy(timeZoneDetectionServiceHelper);
+    }
+
+    @VisibleForTesting
+    public TimeZoneDetectorStrategy(Callback callback) {
+        mCallback = Objects.requireNonNull(callback);
+    }
+
+    /**
+     * Suggests a time zone for the device, or withdraws a previous suggestion if
+     * {@link PhoneTimeZoneSuggestion#getZoneId()} is {@code null}. The suggestion is scoped to a
+     * specific {@link PhoneTimeZoneSuggestion#getPhoneId() phone}.
+     * See {@link PhoneTimeZoneSuggestion} for an explanation of the metadata associated with a
+     * suggestion. The service uses suggestions to decide whether to modify the device's time zone
+     * setting and what to set it to.
+     */
+    public synchronized void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion newSuggestion) {
+        if (DBG) {
+            Slog.d(LOG_TAG, "suggestPhoneTimeZone: newSuggestion=" + newSuggestion);
+        }
+        Objects.requireNonNull(newSuggestion);
+
+        int score = scoreSuggestion(newSuggestion);
+        QualifiedPhoneTimeZoneSuggestion scoredSuggestion =
+                new QualifiedPhoneTimeZoneSuggestion(newSuggestion, score);
+
+        // Record the suggestion against the correct phoneId.
+        LinkedList<QualifiedPhoneTimeZoneSuggestion> suggestions =
+                mSuggestionByPhoneId.get(newSuggestion.getPhoneId());
+        if (suggestions == null) {
+            suggestions = new LinkedList<>();
+            mSuggestionByPhoneId.put(newSuggestion.getPhoneId(), suggestions);
+        }
+        suggestions.addFirst(scoredSuggestion);
+        if (suggestions.size() > KEEP_SUGGESTION_HISTORY_SIZE) {
+            suggestions.removeLast();
+        }
+
+        // Now run the competition between the phones' suggestions.
+        doTimeZoneDetection();
+    }
+
+    private static int scoreSuggestion(@NonNull PhoneTimeZoneSuggestion suggestion) {
+        int score;
+        if (suggestion.getZoneId() == null) {
+            score = SCORE_NONE;
+        } else if (suggestion.getMatchType() == MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY
+                || suggestion.getMatchType() == MATCH_TYPE_EMULATOR_ZONE_ID) {
+            // Handle emulator / test cases : These suggestions should always just be used.
+            score = SCORE_HIGHEST;
+        } else if (suggestion.getQuality() == QUALITY_SINGLE_ZONE) {
+            score = SCORE_HIGH;
+        } else if (suggestion.getQuality() == QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET) {
+            // The suggestion may be wrong, but at least the offset should be correct.
+            score = SCORE_MEDIUM;
+        } else if (suggestion.getQuality() == QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS) {
+            // The suggestion has a good chance of being wrong.
+            score = SCORE_LOW;
+        } else {
+            throw new AssertionError();
+        }
+        return score;
+    }
+
+    /**
+     * Finds the best available time zone suggestion from all phones. If it is high-enough quality
+     * and automatic time zone detection is enabled then it will be set on the device. The outcome
+     * can be that this service becomes / remains un-opinionated and nothing is set.
+     */
+    @GuardedBy("this")
+    private void doTimeZoneDetection() {
+        QualifiedPhoneTimeZoneSuggestion bestSuggestion = findBestSuggestion();
+        boolean timeZoneDetectionEnabled = mCallback.isTimeZoneDetectionEnabled();
+
+        // Work out what to do with the best suggestion.
+        if (bestSuggestion == null) {
+            // There is no suggestion. Become un-opinionated.
+            if (DBG) {
+                Slog.d(LOG_TAG, "doTimeZoneDetection: No good suggestion."
+                        + " bestSuggestion=null"
+                        + ", timeZoneDetectionEnabled=" + timeZoneDetectionEnabled);
+            }
+            mCurrentSuggestion = null;
+            return;
+        }
+
+        // Special case handling for uninitialized devices. This should only happen once.
+        String newZoneId = bestSuggestion.suggestion.getZoneId();
+        if (newZoneId != null && !mCallback.isDeviceTimeZoneInitialized()) {
+            Slog.i(LOG_TAG, "doTimeZoneDetection: Device has no time zone set so might set the"
+                    + " device to the best available suggestion."
+                    + " bestSuggestion=" + bestSuggestion
+                    + ", timeZoneDetectionEnabled=" + timeZoneDetectionEnabled);
+
+            mCurrentSuggestion = bestSuggestion;
+            if (timeZoneDetectionEnabled) {
+                setDeviceTimeZone(bestSuggestion.suggestion);
+            }
+            return;
+        }
+
+        boolean suggestionGoodEnough = bestSuggestion.score >= SCORE_USAGE_THRESHOLD;
+        if (!suggestionGoodEnough) {
+            if (DBG) {
+                Slog.d(LOG_TAG, "doTimeZoneDetection: Suggestion not good enough."
+                        + " bestSuggestion=" + bestSuggestion);
+            }
+            mCurrentSuggestion = null;
+            return;
+        }
+
+        // Paranoia: Every suggestion above the SCORE_USAGE_THRESHOLD should have a non-null time
+        // zone ID.
+        if (newZoneId == null) {
+            Slog.w(LOG_TAG, "Empty zone suggestion scored higher than expected. This is an error:"
+                    + " bestSuggestion=" + bestSuggestion);
+            mCurrentSuggestion = null;
+            return;
+        }
+
+        // There is a good suggestion. Store the suggestion and set the device time zone if
+        // settings allow.
+        mCurrentSuggestion = bestSuggestion;
+
+        // Only set the device time zone if time zone detection is enabled.
+        if (!timeZoneDetectionEnabled) {
+            if (DBG) {
+                Slog.d(LOG_TAG, "doTimeZoneDetection: Not setting the time zone because time zone"
+                        + " detection is disabled."
+                        + " bestSuggestion=" + bestSuggestion);
+            }
+            return;
+        }
+        PhoneTimeZoneSuggestion suggestion = bestSuggestion.suggestion;
+        setDeviceTimeZone(suggestion);
+    }
+
+    private void setDeviceTimeZone(@NonNull PhoneTimeZoneSuggestion suggestion) {
+        String currentZoneId = mCallback.getDeviceTimeZone();
+        String newZoneId = suggestion.getZoneId();
+
+        // Paranoia: This should never happen.
+        if (newZoneId == null) {
+            Slog.w(LOG_TAG, "setDeviceTimeZone: Suggested zone is null."
+                    + " timeZoneSuggestion=" + suggestion);
+            return;
+        }
+
+        // Avoid unnecessary changes / intents.
+        if (newZoneId.equals(currentZoneId)) {
+            // No need to set the device time zone - the setting is already what we would be
+            // suggesting.
+            if (DBG) {
+                Slog.d(LOG_TAG, "setDeviceTimeZone: No need to change the time zone;"
+                        + " device is already set to the suggested zone."
+                        + " timeZoneSuggestion=" + suggestion);
+            }
+            return;
+        }
+
+        String msg = "Changing device time zone. currentZoneId=" + currentZoneId
+                + ", timeZoneSuggestion=" + suggestion;
+        if (DBG) {
+            Slog.d(LOG_TAG, msg);
+        }
+        mTimeZoneChangesLog.log(msg);
+        mCallback.setDeviceTimeZone(newZoneId);
+    }
+
+    @GuardedBy("this")
+    @Nullable
+    private QualifiedPhoneTimeZoneSuggestion findBestSuggestion() {
+        QualifiedPhoneTimeZoneSuggestion bestSuggestion = null;
+
+        // Iterate over the latest QualifiedPhoneTimeZoneSuggestion objects received for each phone
+        // and find the best. Note that we deliberately do not look at age: the caller can
+        // rate-limit so age is not a strong indicator of confidence. Instead, the callers are
+        // expected to withdraw suggestions they no longer have confidence in.
+        for (int i = 0; i < mSuggestionByPhoneId.size(); i++) {
+            LinkedList<QualifiedPhoneTimeZoneSuggestion> phoneSuggestions =
+                    mSuggestionByPhoneId.valueAt(i);
+            if (phoneSuggestions == null) {
+                // Unexpected
+                continue;
+            }
+            QualifiedPhoneTimeZoneSuggestion candidateSuggestion = phoneSuggestions.getFirst();
+            if (candidateSuggestion == null) {
+                // Unexpected
+                continue;
+            }
+
+            if (bestSuggestion == null) {
+                bestSuggestion = candidateSuggestion;
+            } else if (candidateSuggestion.score > bestSuggestion.score) {
+                bestSuggestion = candidateSuggestion;
+            } else if (candidateSuggestion.score == bestSuggestion.score) {
+                // Tie! Use the suggestion with the lowest phoneId.
+                int candidatePhoneId = candidateSuggestion.suggestion.getPhoneId();
+                int bestPhoneId = bestSuggestion.suggestion.getPhoneId();
+                if (candidatePhoneId < bestPhoneId) {
+                    bestSuggestion = candidateSuggestion;
+                }
+            }
+        }
+        return bestSuggestion;
+    }
+
+    /**
+     * Returns the current best suggestion. Not intended for general use: it is used during tests
+     * to check service behavior.
+     */
+    @VisibleForTesting
+    @Nullable
+    public synchronized QualifiedPhoneTimeZoneSuggestion findBestSuggestionForTests() {
+        return findBestSuggestion();
+    }
+
+    /**
+     * Called when the has been a change to the automatic time zone detection setting.
+     */
+    @VisibleForTesting
+    public synchronized void handleTimeZoneDetectionChange() {
+        if (DBG) {
+            Slog.d(LOG_TAG, "handleTimeZoneDetectionChange() called");
+        }
+        if (mCallback.isTimeZoneDetectionEnabled()) {
+            // When the user enabled time zone detection, run the time zone detection and change the
+            // device time zone if possible.
+            doTimeZoneDetection();
+        }
+    }
+
+    /**
+     * Dumps any logs held to the supplied writer.
+     */
+    public synchronized void dumpLogs(IndentingPrintWriter ipw) {
+        ipw.println("TimeZoneDetectorStrategy:");
+
+        ipw.increaseIndent(); // level 1
+
+        ipw.println("Time zone change log:");
+        ipw.increaseIndent(); // level 2
+        mTimeZoneChangesLog.dump(ipw);
+        ipw.decreaseIndent(); // level 2
+
+        ipw.println("Phone suggestion history:");
+        ipw.increaseIndent(); // level 2
+        for (Map.Entry<Integer, LinkedList<QualifiedPhoneTimeZoneSuggestion>> entry
+                : mSuggestionByPhoneId.entrySet()) {
+            ipw.println("Phone " + entry.getKey());
+
+            ipw.increaseIndent(); // level 3
+            for (QualifiedPhoneTimeZoneSuggestion suggestion : entry.getValue()) {
+                ipw.println(suggestion);
+            }
+            ipw.decreaseIndent(); // level 3
+        }
+        ipw.decreaseIndent(); // level 2
+        ipw.decreaseIndent(); // level 1
+    }
+
+    /**
+     * Dumps internal state such as field values.
+     */
+    public synchronized void dumpState(PrintWriter pw) {
+        pw.println("mCurrentSuggestion=" + mCurrentSuggestion);
+        pw.println("mCallback.isTimeZoneDetectionEnabled()="
+                + mCallback.isTimeZoneDetectionEnabled());
+        pw.println("mCallback.isDeviceTimeZoneInitialized()="
+                + mCallback.isDeviceTimeZoneInitialized());
+        pw.println("mCallback.getDeviceTimeZone()="
+                + mCallback.getDeviceTimeZone());
+        pw.flush();
+    }
+
+    /**
+     * A method used to inspect service state during tests. Not intended for general use.
+     */
+    @VisibleForTesting
+    public synchronized QualifiedPhoneTimeZoneSuggestion getLatestPhoneSuggestion(int phoneId) {
+        LinkedList<QualifiedPhoneTimeZoneSuggestion> suggestions =
+                mSuggestionByPhoneId.get(phoneId);
+        if (suggestions == null) {
+            return null;
+        }
+        return suggestions.getFirst();
+    }
+
+    /**
+     * A {@link PhoneTimeZoneSuggestion} with additional qualifying metadata.
+     */
+    @VisibleForTesting
+    public static class QualifiedPhoneTimeZoneSuggestion {
+
+        @VisibleForTesting
+        public final PhoneTimeZoneSuggestion suggestion;
+
+        /**
+         * The score the suggestion has been given. This can be used to rank against other
+         * suggestions of the same type.
+         */
+        @VisibleForTesting
+        public final int score;
+
+        @VisibleForTesting
+        public QualifiedPhoneTimeZoneSuggestion(PhoneTimeZoneSuggestion suggestion, int score) {
+            this.suggestion = suggestion;
+            this.score = score;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            QualifiedPhoneTimeZoneSuggestion that = (QualifiedPhoneTimeZoneSuggestion) o;
+            return score == that.score
+                    && suggestion.equals(that.suggestion);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(score, suggestion);
+        }
+
+        @Override
+        public String toString() {
+            return "QualifiedPhoneTimeZoneSuggestion{"
+                    + "suggestion=" + suggestion
+                    + ", score=" + score
+                    + '}';
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 3de3578..077e735 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1520,7 +1520,13 @@
         hasBeenLaunched = false;
         mStackSupervisor = supervisor;
 
-        taskAffinity = aInfo.taskAffinity;
+        // b/35954083: Limit task affinity to uid to avoid various issues associated with sharing
+        // affinity across uids.
+        final String uid = Integer.toString(info.applicationInfo.uid);
+        if (info.taskAffinity != null && !info.taskAffinity.startsWith(uid)) {
+            info.taskAffinity = uid + ":" + info.taskAffinity;
+        }
+        taskAffinity = info.taskAffinity;
         stateNotNeeded = (aInfo.flags & FLAG_STATE_NOT_NEEDED) != 0;
         nonLocalizedLabel = aInfo.nonLocalizedLabel;
         labelRes = aInfo.labelRes;
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 0f912f1..7960842 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -149,7 +149,8 @@
     private final ActivityStartController mController;
 
     // Share state variable among methods when starting an activity.
-    private ActivityRecord mStartActivity;
+    @VisibleForTesting
+    ActivityRecord mStartActivity;
     private Intent mIntent;
     private int mCallingUid;
     private ActivityOptions mOptions;
@@ -173,7 +174,8 @@
     private int mPreferredDisplayId;
 
     private Task mInTask;
-    private boolean mAddingToTask;
+    @VisibleForTesting
+    boolean mAddingToTask;
     private Task mReuseTask;
 
     private ActivityInfo mNewTaskInfo;
@@ -1665,7 +1667,16 @@
      * - Comply to the specified activity launch flags
      * - Determine whether need to add a new activity on top or just brought the task to front.
      */
-    private int recycleTask(Task targetTask, ActivityRecord targetTaskTop, Task reusedTask) {
+    @VisibleForTesting
+    int recycleTask(Task targetTask, ActivityRecord targetTaskTop, Task reusedTask) {
+        // Should not recycle task which is from a different user, just adding the starting
+        // activity to the task.
+        if (targetTask.mUserId != mStartActivity.mUserId) {
+            mTargetStack = targetTask.getStack();
+            mAddingToTask = true;
+            return START_SUCCESS;
+        }
+
         // True if we are clearing top and resetting of a standard (default) launch mode
         // ({@code LAUNCH_MULTIPLE}) activity. The existing activity will be finished.
         final boolean clearTopAndResetStandardLaunchMode =
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index bbe9e4f..a6578f3 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1104,6 +1104,7 @@
                 com.android.internal.R.bool.config_hasPermanentDpad);
         mInTouchMode = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_defaultInTouchMode);
+        inputManager.setInTouchMode(mInTouchMode);
         mDrawLockTimeoutMillis = context.getResources().getInteger(
                 com.android.internal.R.integer.config_drawLockTimeoutMillis);
         mAllowAnimationsInLowPowerMode = context.getResources().getBoolean(
@@ -3402,6 +3403,7 @@
         synchronized (mGlobalLock) {
             mInTouchMode = mode;
         }
+        mInputManager.setInTouchMode(mode);
     }
 
     public void showEmulatorDisplayOverlayIfNeeded() {
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index dd2b27d..471164d 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: [
@@ -133,7 +132,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/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index b546a1d..9344a9b 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -1502,6 +1502,13 @@
     im->getInputManager()->getDispatcher()->setInputFilterEnabled(enabled);
 }
 
+static void nativeSetInTouchMode(JNIEnv* /* env */, jclass /* clazz */,
+        jlong ptr, jboolean inTouchMode) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+    im->getInputManager()->getDispatcher()->setInTouchMode(inTouchMode);
+}
+
 static jint nativeInjectInputEvent(JNIEnv* env, jclass /* clazz */,
         jlong ptr, jobject inputEventObj, jint injectorPid, jint injectorUid,
         jint syncMode, jint timeoutMillis, jint policyFlags) {
@@ -1781,6 +1788,8 @@
             (void*) nativePilferPointers },
     { "nativeSetInputFilterEnabled", "(JZ)V",
             (void*) nativeSetInputFilterEnabled },
+    { "nativeSetInTouchMode", "(JZ)V",
+            (void*) nativeSetInTouchMode },
     { "nativeInjectInputEvent", "(JLandroid/view/InputEvent;IIIII)I",
             (void*) nativeInjectInputEvent },
     { "nativeToggleCapsLock", "(JI)V",
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/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index c4130d9..b264684 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -5574,8 +5574,8 @@
         }
     }
 
-    private void enforceProfileOwnerOfCorpOwnedDevice(ActiveAdmin admin) {
-        if (!isProfileOwnerOfOrganizationOwnedDevicte(admin)) {
+    private void enforceProfileOwnerOfOrganizationOwnedDevice(ActiveAdmin admin) {
+        if (!isProfileOwnerOfOrganizationOwnedDevice(admin)) {
             throw new SecurityException(String.format("Provided admin %s is either not a profile "
                     + "owner or not on a corporate-owned device.", admin));
         }
@@ -6645,7 +6645,7 @@
         }
 
         boolean calledByProfileOwnerOnOrgOwnedDevice =
-                isProfileOwnerOfOrganizationOwnedDevicte(admin);
+                isProfileOwnerOfOrganizationOwnedDevice(admin);
 
         if (calledOnParentInstance && !calledByProfileOwnerOnOrgOwnedDevice) {
             throw new SecurityException("Wiping the entire device can only be done by a profile"
@@ -6685,6 +6685,9 @@
                     mUserManager.setUserRestriction(
                             UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, false,
                             UserHandle.SYSTEM);
+
+                    // Device-wide policies set by the profile owner need to be cleaned up here.
+                    mLockPatternUtils.setDeviceOwnerInfo(null);
                 } finally {
                     mInjector.binderRestoreCallingIdentity(ident);
                 }
@@ -8026,7 +8029,7 @@
      * {@code getActiveAdminForCallerLocked} or one of the similar variants, not caller-supplied
      * input.
      */
-    private boolean isProfileOwnerOfOrganizationOwnedDevicte(@Nullable ActiveAdmin admin) {
+    private boolean isProfileOwnerOfOrganizationOwnedDevice(@Nullable ActiveAdmin admin) {
         if (admin == null) {
             return false;
         }
@@ -8334,14 +8337,17 @@
         }
 
         synchronized (getLockObject()) {
-            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
-            long token = mInjector.binderClearCallingIdentity();
-            try {
-                mLockPatternUtils.setDeviceOwnerInfo(info != null ? info.toString() : null);
-            } finally {
-                mInjector.binderRestoreCallingIdentity(token);
+            ActiveAdmin admin = getActiveAdminForCallerLocked(who,
+                    DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            if (!isProfileOwnerOfOrganizationOwnedDevice(admin) && !isDeviceOwner(admin)) {
+                throw new SecurityException("Only Device Owner or Profile Owner of"
+                        + " organization-owned device can set screen lock info.");
             }
         }
+
+        mInjector.binderWithCleanCallingIdentity(() ->
+                mLockPatternUtils.setDeviceOwnerInfo(info != null ? info.toString() : null));
+
         DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.SET_DEVICE_OWNER_LOCK_SCREEN_INFO)
                 .setAdmin(who)
@@ -10229,8 +10235,7 @@
         synchronized (getLockObject()) {
             final boolean isDeviceOwner = mOwners.isDeviceOwnerUserId(userId);
             final Bundle userRestrictions;
-            // Whether device owner enforces camera restriction.
-            boolean disallowCameraGlobally = false;
+            final int restrictionOwnerType;
 
             if (isDeviceOwner) {
                 final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
@@ -10238,33 +10243,45 @@
                     return; // Shouldn't happen.
                 }
                 userRestrictions = deviceOwner.userRestrictions;
-                // DO can disable camera globally.
-                disallowCameraGlobally = deviceOwner.disableCamera;
+                addOrRemoveDisableCameraRestriction(userRestrictions, deviceOwner);
+                restrictionOwnerType = UserManagerInternal.OWNER_TYPE_DEVICE_OWNER;
             } else {
                 final ActiveAdmin profileOwner = getProfileOwnerAdminLocked(userId);
                 userRestrictions = profileOwner != null ? profileOwner.userRestrictions : null;
+                addOrRemoveDisableCameraRestriction(userRestrictions, userId);
+
+                if (isProfileOwnerOfOrganizationOwnedDevice(profileOwner)) {
+                    restrictionOwnerType =
+                          UserManagerInternal.OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE;
+                } else if (profileOwner != null) {
+                    restrictionOwnerType = UserManagerInternal.OWNER_TYPE_PROFILE_OWNER;
+                } else {
+                    restrictionOwnerType = UserManagerInternal.OWNER_TYPE_NO_OWNER;
+                }
             }
 
-            // Whether any admin enforces camera restriction.
-            final int cameraRestrictionScope =
-                    getCameraRestrictionScopeLocked(userId, disallowCameraGlobally);
-
             mUserManagerInternal.setDevicePolicyUserRestrictions(userId, userRestrictions,
-                    isDeviceOwner, cameraRestrictionScope);
+                    restrictionOwnerType);
         }
     }
 
-    /**
-     * Get the scope of camera restriction for a given user if any.
-     */
-    private int getCameraRestrictionScopeLocked(int userId, boolean disallowCameraGlobally) {
-        if (disallowCameraGlobally) {
-            return UserManagerInternal.CAMERA_DISABLED_GLOBALLY;
-        } else if (getCameraDisabled(
-                /* who= */ null, userId, /* mergeDeviceOwnerRestriction= */ false)) {
-            return UserManagerInternal.CAMERA_DISABLED_LOCALLY;
+    private void addOrRemoveDisableCameraRestriction(Bundle userRestrictions, ActiveAdmin admin) {
+        if (userRestrictions == null) return;
+        if (admin.disableCamera) {
+            userRestrictions.putBoolean(UserManager.DISALLOW_CAMERA, true);
+        } else {
+            userRestrictions.remove(UserManager.DISALLOW_CAMERA);
         }
-        return UserManagerInternal.CAMERA_NOT_DISABLED;
+    }
+
+    private void addOrRemoveDisableCameraRestriction(Bundle userRestrictions, int userId) {
+        if (userRestrictions == null) return;
+        if (getCameraDisabled(/* who= */ null, userId, /* mergeDeviceOwnerRestriction= */
+                false)) {
+            userRestrictions.putBoolean(UserManager.DISALLOW_CAMERA, true);
+        } else {
+            userRestrictions.remove(UserManager.DISALLOW_CAMERA);
+        }
     }
 
     @Override
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index f46857a..5a946f2 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -43,6 +43,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;
@@ -268,6 +269,8 @@
             "com.android.internal.car.CarServiceHelperService";
     private static final String TIME_DETECTOR_SERVICE_CLASS =
             "com.android.server.timedetector.TimeDetectorService$Lifecycle";
+    private static final String TIME_ZONE_DETECTOR_SERVICE_CLASS =
+            "com.android.server.timezonedetector.TimeZoneDetectorService$Lifecycle";
     private static final String ACCESSIBILITY_MANAGER_SERVICE_CLASS =
             "com.android.server.accessibility.AccessibilityManagerService$Lifecycle";
     private static final String ADB_SERVICE_CLASS =
@@ -1480,6 +1483,14 @@
             }
             t.traceEnd();
 
+            t.traceBegin("StartTimeZoneDetectorService");
+            try {
+                mSystemServiceManager.startService(TIME_ZONE_DETECTOR_SERVICE_CLASS);
+            } catch (Throwable e) {
+                reportWtf("starting StartTimeZoneDetectorService service", e);
+            }
+            t.traceEnd();
+
             if (!isWatch) {
                 t.traceBegin("StartSearchManagerService");
                 try {
@@ -2204,6 +2215,15 @@
             }
             t.traceEnd();
 
+            t.traceBegin("StartTethering");
+            try {
+                // Tethering must start after ConnectivityService and NetworkStack.
+                TetheringManager.getInstance().start();
+            } catch (Throwable e) {
+                reportWtf("starting Tethering", e);
+            }
+            t.traceEnd();
+
             t.traceBegin("MakeCountryDetectionServiceReady");
             try {
                 if (countryDetectorF != null) {
diff --git a/services/net/Android.bp b/services/net/Android.bp
index 7ef9f44..3babb0b 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -9,8 +9,8 @@
     name: "services.net",
     srcs: [
         ":net-module-utils-srcs",
-        ":tethering-servicesnet-srcs",
         ":services.net-sources",
+        ":tethering-manager",
     ],
     static_libs: [
         "dnsresolver_aidl_interface-V2-java",
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java b/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java
index 2cebbeb..223a98b 100644
--- a/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java
@@ -29,6 +29,9 @@
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
 
+import java.util.Map;
+import java.util.Set;
+
 @Implements(PerformUnifiedRestoreTask.class)
 public class ShadowPerformUnifiedRestoreTask {
     @Nullable private static ShadowPerformUnifiedRestoreTask sLastShadow;
@@ -64,7 +67,8 @@
             int pmToken,
             boolean isFullSystemRestore,
             @Nullable String[] filterSet,
-            OnTaskFinishedListener listener) {
+            OnTaskFinishedListener listener,
+            Map<String, Set<String>> excludedKeys) {
         mBackupManagerService = backupManagerService;
         mPackage = targetPackage;
         mIsFullSystemRestore = isFullSystemRestore;
diff --git a/services/tests/servicestests/src/com/android/server/backup/DataChangedJournalTest.java b/services/tests/servicestests/src/com/android/server/backup/DataChangedJournalTest.java
index f588c4f..4e7fe44 100644
--- a/services/tests/servicestests/src/com/android/server/backup/DataChangedJournalTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/DataChangedJournalTest.java
@@ -18,6 +18,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertEquals;
+
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
@@ -50,6 +53,7 @@
     @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
 
     @Mock private Consumer<String> mConsumer;
+    @Mock private File invalidFile;
 
     private File mFile;
     private DataChangedJournal mJournal;
@@ -131,4 +135,10 @@
     public void toString_isSameAsFileToString() throws Exception {
         assertThat(mJournal.toString()).isEqualTo(mFile.toString());
     }
+
+    public void listJournals_invalidJournalFile_returnsEmptyList() throws Exception {
+        when(invalidFile.listFiles()).thenReturn(null);
+
+        assertEquals(0, DataChangedJournal.listJournals(invalidFile).size());
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/backup/UserBackupPreferencesTest.java b/services/tests/servicestests/src/com/android/server/backup/UserBackupPreferencesTest.java
new file mode 100644
index 0000000..d6efe35
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/backup/UserBackupPreferencesTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.backup;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class UserBackupPreferencesTest {
+    private static final String EXCLUDED_PACKAGE_1 = "package1";
+    private static final String EXCLUDED_PACKAGE_2 = "package2";
+    private static final List<String> EXCLUDED_KEYS_1 = Arrays.asList("key1", "key2");
+    private static final List<String> EXCLUDED_KEYS_2 = Arrays.asList("key1");
+
+    @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+    private UserBackupPreferences mExcludedRestoreKeysStorage;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mExcludedRestoreKeysStorage =
+                new UserBackupPreferences(
+                        InstrumentationRegistry.getContext(), mTemporaryFolder.newFolder());
+    }
+
+    @Test
+    public void testGetExcludedKeysForPackages_returnsExcludedKeys() {
+        mExcludedRestoreKeysStorage.addExcludedKeys(EXCLUDED_PACKAGE_1, EXCLUDED_KEYS_1);
+        mExcludedRestoreKeysStorage.addExcludedKeys(EXCLUDED_PACKAGE_2, EXCLUDED_KEYS_2);
+
+        Map<String, Set<String>> excludedKeys =
+                mExcludedRestoreKeysStorage.getExcludedRestoreKeysForPackages(EXCLUDED_PACKAGE_1);
+        assertTrue(excludedKeys.containsKey(EXCLUDED_PACKAGE_1));
+        assertFalse(excludedKeys.containsKey(EXCLUDED_PACKAGE_2));
+        assertEquals(new HashSet<>(EXCLUDED_KEYS_1), excludedKeys.get(EXCLUDED_PACKAGE_1));
+    }
+
+    @Test
+    public void testGetExcludedKeysForPackages_withEmpty_list_returnsAllExcludedKeys() {
+        mExcludedRestoreKeysStorage.addExcludedKeys(EXCLUDED_PACKAGE_1, EXCLUDED_KEYS_1);
+        mExcludedRestoreKeysStorage.addExcludedKeys(EXCLUDED_PACKAGE_2, EXCLUDED_KEYS_2);
+
+        Map<String, Set<String>> excludedKeys =
+                mExcludedRestoreKeysStorage.getAllExcludedRestoreKeys();
+        assertTrue(excludedKeys.containsKey(EXCLUDED_PACKAGE_1));
+        assertTrue(excludedKeys.containsKey(EXCLUDED_PACKAGE_2));
+        assertEquals(new HashSet<>(EXCLUDED_KEYS_1), excludedKeys.get(EXCLUDED_PACKAGE_1));
+        assertEquals(new HashSet<>(EXCLUDED_KEYS_2), excludedKeys.get(EXCLUDED_PACKAGE_2));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java b/services/tests/servicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
new file mode 100644
index 0000000..6359edf
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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.backup.restore;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import android.app.backup.BackupDataInput;
+import android.app.backup.BackupDataOutput;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.ArrayDeque;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class PerformUnifiedRestoreTaskTest {
+    private static final String PACKAGE_NAME = "package";
+    private static final String INCLUDED_KEY = "included_key";
+    private static final String EXCLUDED_KEY_1 = "excluded_key_1";
+    private static final String EXCLUDED_KEY_2 = "excluded_key_2";
+
+    @Mock private BackupDataInput mBackupDataInput;
+    @Mock private BackupDataOutput mBackupDataOutput;
+
+    private Set<String> mExcludedkeys = new HashSet<>();
+    private Map<String, String> mBackupData = new HashMap<>();
+    // Mock BackupDataInput reads backup data from here.
+    private Queue<String> mBackupDataSource;
+    // Mock BackupDataOutput will write backup data here.
+    private Set<String> mBackupDataDump;
+    private PerformUnifiedRestoreTask mRestoreTask;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        populateTestData();
+
+        mBackupDataSource = new ArrayDeque<>(mBackupData.keySet());
+        when(mBackupDataInput.readNextHeader()).then(new Answer<Boolean>() {
+            @Override
+            public Boolean answer(InvocationOnMock invocation) throws Throwable {
+                return !mBackupDataSource.isEmpty();
+            }
+        });
+        when(mBackupDataInput.getKey()).then(new Answer<String>() {
+            @Override
+            public String answer(InvocationOnMock invocation) throws Throwable {
+                return mBackupDataSource.poll();
+            }
+        });
+        when(mBackupDataInput.getDataSize()).thenReturn(0);
+
+        mBackupDataDump = new HashSet<>();
+        ArgumentCaptor<String> keyCaptor = ArgumentCaptor.forClass(String.class);
+        when(mBackupDataOutput.writeEntityHeader(keyCaptor.capture(), anyInt())).then(
+                new Answer<Void>() {
+                    @Override
+                    public Void answer(InvocationOnMock invocation) throws Throwable {
+                        mBackupDataDump.add(keyCaptor.getValue());
+                        return null;
+                    }
+                });
+
+        mRestoreTask = new PerformUnifiedRestoreTask(Collections.singletonMap(
+                PACKAGE_NAME, mExcludedkeys));
+    }
+
+    private void populateTestData() {
+        mBackupData = new HashMap<>();
+        mBackupData.put(INCLUDED_KEY, "1");
+        mBackupData.put(EXCLUDED_KEY_1, "2");
+        mBackupData.put(EXCLUDED_KEY_2, "3");
+
+        mExcludedkeys = new HashSet<>();
+        mExcludedkeys.add(EXCLUDED_KEY_1);
+        mExcludedkeys.add(EXCLUDED_KEY_2);
+    }
+
+    @Test
+    public void testFilterExcludedKeys() throws Exception {
+        mRestoreTask.filterExcludedKeys(PACKAGE_NAME, mBackupDataInput, mBackupDataOutput);
+
+        // Verify only the correct were written into BackupDataOutput object.
+        Set<String> allowedBackupKeys = new HashSet<>(mBackupData.keySet());
+        allowedBackupKeys.removeAll(mExcludedkeys);
+        assertEquals(allowedBackupKeys, mBackupDataDump);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index b6ed22b..162e766 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -26,9 +26,6 @@
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
 import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
 import static android.app.admin.PasswordMetrics.computeForPassword;
-import static android.os.UserManagerInternal.CAMERA_DISABLED_GLOBALLY;
-import static android.os.UserManagerInternal.CAMERA_DISABLED_LOCALLY;
-import static android.os.UserManagerInternal.CAMERA_NOT_DISABLED;
 
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
 import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback;
@@ -83,6 +80,7 @@
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.UserManagerInternal;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
 import android.security.KeyChain;
@@ -1155,9 +1153,8 @@
                 MockUtils.checkUserHandle(UserHandle.USER_SYSTEM));
 
         verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
-                eq(UserHandle.USER_SYSTEM),
-                eq(null),
-                eq(true), eq(CAMERA_NOT_DISABLED));
+                eq(UserHandle.USER_SYSTEM), eq(null),
+                eq(UserManagerInternal.OWNER_TYPE_DEVICE_OWNER));
 
         verify(getServices().usageStatsManagerInternal).setActiveAdminApps(
                 null, UserHandle.USER_SYSTEM);
@@ -1726,8 +1723,7 @@
         verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
                 eq(UserHandle.USER_SYSTEM),
                 MockUtils.checkUserRestrictions(defaultRestrictions),
-                eq(true) /* isDeviceOwner */,
-                eq(CAMERA_NOT_DISABLED)
+                eq(UserManagerInternal.OWNER_TYPE_DEVICE_OWNER)
         );
         reset(getServices().userManagerInternal);
 
@@ -1742,7 +1738,7 @@
         verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
                 eq(UserHandle.USER_SYSTEM),
                 MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADD_USER),
-                eq(true), eq(CAMERA_NOT_DISABLED));
+                eq(UserManagerInternal.OWNER_TYPE_DEVICE_OWNER));
         reset(getServices().userManagerInternal);
 
         dpm.addUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
@@ -1750,7 +1746,7 @@
                 eq(UserHandle.USER_SYSTEM),
                 MockUtils.checkUserRestrictions(UserManager.DISALLOW_OUTGOING_CALLS,
                         UserManager.DISALLOW_ADD_USER),
-                eq(true), eq(CAMERA_NOT_DISABLED));
+                eq(UserManagerInternal.OWNER_TYPE_DEVICE_OWNER));
         reset(getServices().userManagerInternal);
 
         DpmTestUtils.assertRestrictions(
@@ -1768,7 +1764,7 @@
         verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
                 eq(UserHandle.USER_SYSTEM),
                 MockUtils.checkUserRestrictions(UserManager.DISALLOW_OUTGOING_CALLS),
-                eq(true), eq(CAMERA_NOT_DISABLED));
+                eq(UserManagerInternal.OWNER_TYPE_DEVICE_OWNER));
         reset(getServices().userManagerInternal);
 
         DpmTestUtils.assertRestrictions(
@@ -1784,7 +1780,7 @@
         verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
                 eq(UserHandle.USER_SYSTEM),
                 MockUtils.checkUserRestrictions(),
-                eq(true), eq(CAMERA_NOT_DISABLED));
+                eq(UserManagerInternal.OWNER_TYPE_DEVICE_OWNER));
         reset(getServices().userManagerInternal);
 
         assertNoDeviceOwnerRestrictions();
@@ -1798,7 +1794,7 @@
                 eq(UserHandle.USER_SYSTEM),
                 MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADJUST_VOLUME,
                         UserManager.DISALLOW_UNMUTE_MICROPHONE),
-                eq(true), eq(CAMERA_NOT_DISABLED));
+                eq(UserManagerInternal.OWNER_TYPE_DEVICE_OWNER));
         reset(getServices().userManagerInternal);
 
         dpm.clearUserRestriction(admin1, UserManager.DISALLOW_ADJUST_VOLUME);
@@ -1810,7 +1806,7 @@
         verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
                 eq(UserHandle.USER_SYSTEM),
                 MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADD_USER),
-                eq(true), eq(CAMERA_NOT_DISABLED));
+                eq(UserManagerInternal.OWNER_TYPE_DEVICE_OWNER));
         reset(getServices().userManagerInternal);
 
         dpm.addUserRestriction(admin1, UserManager.DISALLOW_FUN);
@@ -1818,16 +1814,16 @@
                 eq(UserHandle.USER_SYSTEM),
                 MockUtils.checkUserRestrictions(UserManager.DISALLOW_FUN,
                         UserManager.DISALLOW_ADD_USER),
-                eq(true), eq(CAMERA_NOT_DISABLED));
+                eq(UserManagerInternal.OWNER_TYPE_DEVICE_OWNER));
         reset(getServices().userManagerInternal);
 
         dpm.setCameraDisabled(admin1, true);
         verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
                 eq(UserHandle.USER_SYSTEM),
-                // DISALLOW_CAMERA will be applied to both local and global.
+                // DISALLOW_CAMERA will be applied globally.
                 MockUtils.checkUserRestrictions(UserManager.DISALLOW_FUN,
-                        UserManager.DISALLOW_ADD_USER),
-                eq(true), eq(CAMERA_DISABLED_GLOBALLY));
+                        UserManager.DISALLOW_ADD_USER, UserManager.DISALLOW_CAMERA),
+                eq(UserManagerInternal.OWNER_TYPE_DEVICE_OWNER));
         reset(getServices().userManagerInternal);
     }
 
@@ -1873,7 +1869,7 @@
         verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
                 eq(DpmMockContext.CALLER_USER_HANDLE),
                 MockUtils.checkUserRestrictions(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES),
-                eq(false), eq(CAMERA_NOT_DISABLED));
+                eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER));
         reset(getServices().userManagerInternal);
 
         dpm.addUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
@@ -1881,7 +1877,7 @@
                 eq(DpmMockContext.CALLER_USER_HANDLE),
                 MockUtils.checkUserRestrictions(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
                         UserManager.DISALLOW_OUTGOING_CALLS),
-                eq(false), eq(CAMERA_NOT_DISABLED));
+                eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER));
         reset(getServices().userManagerInternal);
 
         DpmTestUtils.assertRestrictions(
@@ -1904,7 +1900,7 @@
         verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
                 eq(DpmMockContext.CALLER_USER_HANDLE),
                 MockUtils.checkUserRestrictions(UserManager.DISALLOW_OUTGOING_CALLS),
-                eq(false), eq(CAMERA_NOT_DISABLED));
+                eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER));
         reset(getServices().userManagerInternal);
 
         DpmTestUtils.assertRestrictions(
@@ -1925,7 +1921,7 @@
         verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
                 eq(DpmMockContext.CALLER_USER_HANDLE),
                 MockUtils.checkUserRestrictions(),
-                eq(false), eq(CAMERA_NOT_DISABLED));
+                eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER));
         reset(getServices().userManagerInternal);
 
         DpmTestUtils.assertRestrictions(
@@ -1947,20 +1943,52 @@
                 eq(DpmMockContext.CALLER_USER_HANDLE),
                 MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADJUST_VOLUME,
                         UserManager.DISALLOW_UNMUTE_MICROPHONE),
-                eq(false), eq(CAMERA_NOT_DISABLED));
+                eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER));
         reset(getServices().userManagerInternal);
 
         dpm.setCameraDisabled(admin1, true);
         verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
                 eq(DpmMockContext.CALLER_USER_HANDLE),
                 MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADJUST_VOLUME,
-                        UserManager.DISALLOW_UNMUTE_MICROPHONE),
-                eq(false), eq(CAMERA_DISABLED_LOCALLY));
+                        UserManager.DISALLOW_UNMUTE_MICROPHONE, UserManager.DISALLOW_CAMERA),
+                eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER));
         reset(getServices().userManagerInternal);
 
         // TODO Make sure restrictions are written to the file.
     }
 
+    public void testSetUserRestriction_asPoOfOrgOwnedDevice() throws Exception {
+        setupProfileOwner();
+
+        final long ident = mServiceContext.binder.clearCallingIdentity();
+        configureContextForAccess(mServiceContext, true);
+
+        mServiceContext.binder.callingUid =
+                UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE,
+                        DpmMockContext.CALLER_MANAGED_PROVISIONING_UID);
+        try {
+            runAsCaller(mServiceContext, dpms, dpm -> {
+                dpm.markProfileOwnerOnOrganizationOwnedDevice(admin1);
+            });
+        } finally {
+            mServiceContext.binder.restoreCallingIdentity(ident);
+        }
+
+        dpm.addUserRestriction(admin1, UserManager.DISALLOW_CONFIG_DATE_TIME);
+        verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(DpmMockContext.CALLER_USER_HANDLE),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_CONFIG_DATE_TIME),
+                eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE));
+        reset(getServices().userManagerInternal);
+
+        dpm.setCameraDisabled(admin1, true);
+        verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(DpmMockContext.CALLER_USER_HANDLE),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_CONFIG_DATE_TIME,
+                        UserManager.DISALLOW_CAMERA),
+                eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE));
+        reset(getServices().userManagerInternal);
+    }
 
     public void testDefaultEnabledUserRestrictions() throws Exception {
         mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
@@ -1995,8 +2023,7 @@
         verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
                 eq(UserHandle.USER_SYSTEM),
                 MockUtils.checkUserRestrictions(defaultRestrictions),
-                eq(true) /* isDeviceOwner */,
-                eq(CAMERA_NOT_DISABLED)
+                eq(UserManagerInternal.OWNER_TYPE_DEVICE_OWNER)
         );
         reset(getServices().userManagerInternal);
 
@@ -2036,10 +2063,9 @@
                 dpm.getUserRestrictions(admin1)
             );
             verify(getServices().userManagerInternal, atLeast(1)).setDevicePolicyUserRestrictions(
-                eq(UserHandle.USER_SYSTEM),
-                MockUtils.checkUserRestrictions(newDefaultEnabledRestriction),
-                eq(true) /* isDeviceOwner */,
-                eq(CAMERA_NOT_DISABLED)
+                    eq(UserHandle.USER_SYSTEM),
+                    MockUtils.checkUserRestrictions(newDefaultEnabledRestriction),
+                    eq(UserManagerInternal.OWNER_TYPE_DEVICE_OWNER)
             );
             reset(getServices().userManagerInternal);
 
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleXmlSerializerTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleXmlSerializerTest.java
index 40e89ba..180de2f 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleXmlSerializerTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleXmlSerializerTest.java
@@ -38,6 +38,7 @@
 import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.Map;
+import java.util.Optional;
 
 @RunWith(JUnit4.class)
 public class RuleXmlSerializerTest {
@@ -48,7 +49,9 @@
         RuleSerializer xmlSerializer = new RuleXmlSerializer();
         String expectedRules = "<RL />";
 
-        String actualRules = xmlSerializer.serialize(Collections.singletonList(rule));
+        String actualRules =
+                xmlSerializer.serialize(
+                        Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
 
         assertEquals(expectedRules, actualRules);
     }
@@ -56,53 +59,74 @@
     @Test
     public void testXmlString_serializeMultipleRules_oneEmpty() throws Exception {
         Rule rule1 = null;
-        Rule rule2 = new Rule(
-                new AtomicFormula.StringAtomicFormula(AtomicFormula.PACKAGE_NAME, "com.app.test",
-                        /* isHashedValue= */ false),
-                Rule.DENY);
+        Rule rule2 =
+                new Rule(
+                        new AtomicFormula.StringAtomicFormula(
+                                AtomicFormula.PACKAGE_NAME,
+                                "com.app.test",
+                                /* isHashedValue= */ false),
+                        Rule.DENY);
         RuleSerializer xmlSerializer = new RuleXmlSerializer();
         Map<String, String> packageNameAttrs = new LinkedHashMap<>();
         packageNameAttrs.put("K", String.valueOf(AtomicFormula.PACKAGE_NAME));
         packageNameAttrs.put("V", "com.app.test");
         packageNameAttrs.put("H", "false");
-        String expectedRules = "<RL>"
-                + generateTagWithAttribute(/* tag= */ "R",
-                    Collections.singletonMap("E", String.valueOf(Rule.DENY)),
-                        /* closed= */ false)
-                + generateTagWithAttribute(/* tag= */ "AF", packageNameAttrs, /* closed= */ true)
-                + "</R>"
-                + "</RL>";
+        String expectedRules =
+                "<RL>"
+                        + generateTagWithAttribute(
+                                /* tag= */ "R",
+                                Collections.singletonMap("E", String.valueOf(Rule.DENY)),
+                                /* closed= */ false)
+                        + generateTagWithAttribute(
+                                /* tag= */ "AF", packageNameAttrs, /* closed= */ true)
+                        + "</R>"
+                        + "</RL>";
 
-        String actualRules = xmlSerializer.serialize(Arrays.asList(rule1, rule2));
+        String actualRules =
+                xmlSerializer.serialize(
+                        Arrays.asList(rule1, rule2), /* formatVersion= */ Optional.empty());
 
         assertEquals(expectedRules, actualRules);
     }
 
     @Test
     public void testXmlStream_serializeValidOpenFormula() throws Exception {
-        Rule rule = new Rule(new CompoundFormula(CompoundFormula.NOT,
-                Collections.singletonList(
-                        new AtomicFormula.StringAtomicFormula(AtomicFormula.PACKAGE_NAME,
-                                "com.app.test", /* isHashedValue= */ false))), Rule.DENY);
+        Rule rule =
+                new Rule(
+                        new CompoundFormula(
+                                CompoundFormula.NOT,
+                                Collections.singletonList(
+                                        new AtomicFormula.StringAtomicFormula(
+                                                AtomicFormula.PACKAGE_NAME,
+                                                "com.app.test",
+                                                /* isHashedValue= */ false))),
+                        Rule.DENY);
         RuleSerializer xmlSerializer = new RuleXmlSerializer();
         OutputStream outputStream = new ByteArrayOutputStream();
         Map<String, String> packageNameAttrs = new LinkedHashMap<>();
         packageNameAttrs.put("K", String.valueOf(AtomicFormula.PACKAGE_NAME));
         packageNameAttrs.put("V", "com.app.test");
         packageNameAttrs.put("H", "false");
-        String expectedRules = "<RL>"
-                + generateTagWithAttribute(/* tag= */ "R",
-                    Collections.singletonMap("E", String.valueOf(Rule.DENY)),
-                        /* closed= */ false)
-                + generateTagWithAttribute(/* tag= */ "OF",
-                    Collections.singletonMap("C", String.valueOf(CompoundFormula.NOT)),
-                        /* closed= */ false)
-                + generateTagWithAttribute(/* tag= */ "AF", packageNameAttrs, /* closed= */ true)
-                + "</OF>"
-                + "</R>"
-                + "</RL>";
+        String expectedRules =
+                "<RL>"
+                        + generateTagWithAttribute(
+                                /* tag= */ "R",
+                                Collections.singletonMap("E", String.valueOf(Rule.DENY)),
+                                /* closed= */ false)
+                        + generateTagWithAttribute(
+                                /* tag= */ "OF",
+                                Collections.singletonMap("C", String.valueOf(CompoundFormula.NOT)),
+                                /* closed= */ false)
+                        + generateTagWithAttribute(
+                                /* tag= */ "AF", packageNameAttrs, /* closed= */ true)
+                        + "</OF>"
+                        + "</R>"
+                        + "</RL>";
 
-        xmlSerializer.serialize(Collections.singletonList(rule), outputStream);
+        xmlSerializer.serialize(
+                Collections.singletonList(rule),
+                /* formatVersion= */ Optional.empty(),
+                outputStream);
 
         String actualRules = outputStream.toString();
         assertEquals(expectedRules, actualRules);
@@ -110,39 +134,60 @@
 
     @Test
     public void testXmlString_serializeValidOpenFormula_notConnector() throws Exception {
-        Rule rule = new Rule(new CompoundFormula(CompoundFormula.NOT,
-                Collections.singletonList(
-                        new AtomicFormula.StringAtomicFormula(AtomicFormula.PACKAGE_NAME,
-                                "com.app.test", /* isHashedValue= */ false))), Rule.DENY);
+        Rule rule =
+                new Rule(
+                        new CompoundFormula(
+                                CompoundFormula.NOT,
+                                Collections.singletonList(
+                                        new AtomicFormula.StringAtomicFormula(
+                                                AtomicFormula.PACKAGE_NAME,
+                                                "com.app.test",
+                                                /* isHashedValue= */ false))),
+                        Rule.DENY);
         RuleSerializer xmlSerializer = new RuleXmlSerializer();
         Map<String, String> packageNameAttrs = new LinkedHashMap<>();
         packageNameAttrs.put("K", String.valueOf(AtomicFormula.PACKAGE_NAME));
         packageNameAttrs.put("V", "com.app.test");
         packageNameAttrs.put("H", "false");
-        String expectedRules = "<RL>"
-                + generateTagWithAttribute(/* tag= */ "R",
-                    Collections.singletonMap("E", String.valueOf(Rule.DENY)),
-                        /* closed= */ false)
-                + generateTagWithAttribute(/* tag= */ "OF",
-                    Collections.singletonMap("C", String.valueOf(CompoundFormula.NOT)),
-                        /* closed= */ false)
-                + generateTagWithAttribute(/* tag= */ "AF", packageNameAttrs, /* closed= */ true)
-                + "</OF>"
-                + "</R>"
-                + "</RL>";
+        String expectedRules =
+                "<RL>"
+                        + generateTagWithAttribute(
+                                /* tag= */ "R",
+                                Collections.singletonMap("E", String.valueOf(Rule.DENY)),
+                                /* closed= */ false)
+                        + generateTagWithAttribute(
+                                /* tag= */ "OF",
+                                Collections.singletonMap("C", String.valueOf(CompoundFormula.NOT)),
+                                /* closed= */ false)
+                        + generateTagWithAttribute(
+                                /* tag= */ "AF", packageNameAttrs, /* closed= */ true)
+                        + "</OF>"
+                        + "</R>"
+                        + "</RL>";
 
-        String actualRules = xmlSerializer.serialize(Collections.singletonList(rule));
+        String actualRules =
+                xmlSerializer.serialize(
+                        Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
 
         assertEquals(expectedRules, actualRules);
     }
 
     @Test
     public void testXmlString_serializeValidOpenFormula_andConnector() throws Exception {
-        Rule rule = new Rule(new CompoundFormula(CompoundFormula.AND,
-                Arrays.asList(new AtomicFormula.StringAtomicFormula(AtomicFormula.PACKAGE_NAME,
-                                "com.app.test", /* isHashedValue= */ false),
-                        new AtomicFormula.StringAtomicFormula(AtomicFormula.APP_CERTIFICATE,
-                                "test_cert", /* isHashedValue= */ false))), Rule.DENY);
+        Rule rule =
+                new Rule(
+                        new CompoundFormula(
+                                CompoundFormula.AND,
+                                Arrays.asList(
+                                        new AtomicFormula.StringAtomicFormula(
+                                                AtomicFormula.PACKAGE_NAME,
+                                                "com.app.test",
+                                                /* isHashedValue= */ false),
+                                        new AtomicFormula.StringAtomicFormula(
+                                                AtomicFormula.APP_CERTIFICATE,
+                                                "test_cert",
+                                                /* isHashedValue= */ false))),
+                        Rule.DENY);
         RuleSerializer xmlSerializer = new RuleXmlSerializer();
         Map<String, String> packageNameAttrs = new LinkedHashMap<>();
         packageNameAttrs.put("K", String.valueOf(AtomicFormula.PACKAGE_NAME));
@@ -152,31 +197,47 @@
         appCertificateAttrs.put("K", String.valueOf(AtomicFormula.APP_CERTIFICATE));
         appCertificateAttrs.put("V", "test_cert");
         appCertificateAttrs.put("H", "false");
-        String expectedRules = "<RL>"
-                + generateTagWithAttribute(/* tag= */ "R",
-                    Collections.singletonMap("E", String.valueOf(Rule.DENY)),
-                        /* closed= */ false)
-                + generateTagWithAttribute(/* tag= */ "OF",
-                    Collections.singletonMap("C", String.valueOf(CompoundFormula.AND)),
-                        /* closed= */ false)
-                + generateTagWithAttribute(/* tag= */ "AF", packageNameAttrs, /* closed= */ true)
-                + generateTagWithAttribute(/* tag= */ "AF", appCertificateAttrs, /* closed= */ true)
-                + "</OF>"
-                + "</R>"
-                + "</RL>";
+        String expectedRules =
+                "<RL>"
+                        + generateTagWithAttribute(
+                                /* tag= */ "R",
+                                Collections.singletonMap("E", String.valueOf(Rule.DENY)),
+                                /* closed= */ false)
+                        + generateTagWithAttribute(
+                                /* tag= */ "OF",
+                                Collections.singletonMap("C", String.valueOf(CompoundFormula.AND)),
+                                /* closed= */ false)
+                        + generateTagWithAttribute(
+                                /* tag= */ "AF", packageNameAttrs, /* closed= */ true)
+                        + generateTagWithAttribute(
+                                /* tag= */ "AF", appCertificateAttrs, /* closed= */ true)
+                        + "</OF>"
+                        + "</R>"
+                        + "</RL>";
 
-        String actualRules = xmlSerializer.serialize(Collections.singletonList(rule));
+        String actualRules =
+                xmlSerializer.serialize(
+                        Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
 
         assertEquals(expectedRules, actualRules);
     }
 
     @Test
     public void testXmlString_serializeValidOpenFormula_orConnector() throws Exception {
-        Rule rule = new Rule(new CompoundFormula(CompoundFormula.OR,
-                Arrays.asList(new AtomicFormula.StringAtomicFormula(AtomicFormula.PACKAGE_NAME,
-                                "com.app.test", /* isHashedValue= */ false),
-                        new AtomicFormula.StringAtomicFormula(AtomicFormula.APP_CERTIFICATE,
-                                "test_cert", /* isHashedValue= */ false))), Rule.DENY);
+        Rule rule =
+                new Rule(
+                        new CompoundFormula(
+                                CompoundFormula.OR,
+                                Arrays.asList(
+                                        new AtomicFormula.StringAtomicFormula(
+                                                AtomicFormula.PACKAGE_NAME,
+                                                "com.app.test",
+                                                /* isHashedValue= */ false),
+                                        new AtomicFormula.StringAtomicFormula(
+                                                AtomicFormula.APP_CERTIFICATE,
+                                                "test_cert",
+                                                /* isHashedValue= */ false))),
+                        Rule.DENY);
         RuleSerializer xmlSerializer = new RuleXmlSerializer();
         Map<String, String> packageNameAttrs = new LinkedHashMap<>();
         packageNameAttrs.put("K", String.valueOf(AtomicFormula.PACKAGE_NAME));
@@ -186,89 +247,117 @@
         appCertificateAttrs.put("K", String.valueOf(AtomicFormula.APP_CERTIFICATE));
         appCertificateAttrs.put("V", "test_cert");
         appCertificateAttrs.put("H", "false");
-        String expectedRules = "<RL>"
-                + generateTagWithAttribute(/* tag= */ "R",
-                    Collections.singletonMap("E", String.valueOf(Rule.DENY)),
-                        /* closed= */ false)
-                + generateTagWithAttribute(/* tag= */ "OF",
-                    Collections.singletonMap("C", String.valueOf(CompoundFormula.OR)),
-                        /* closed= */ false)
-                + generateTagWithAttribute(/* tag= */ "AF", packageNameAttrs, /* closed= */ true)
-                + generateTagWithAttribute(/* tag= */ "AF", appCertificateAttrs, /* closed= */ true)
-                + "</OF>"
-                + "</R>"
-                + "</RL>";
+        String expectedRules =
+                "<RL>"
+                        + generateTagWithAttribute(
+                                /* tag= */ "R",
+                                Collections.singletonMap("E", String.valueOf(Rule.DENY)),
+                                /* closed= */ false)
+                        + generateTagWithAttribute(
+                                /* tag= */ "OF",
+                                Collections.singletonMap("C", String.valueOf(CompoundFormula.OR)),
+                                /* closed= */ false)
+                        + generateTagWithAttribute(
+                                /* tag= */ "AF", packageNameAttrs, /* closed= */ true)
+                        + generateTagWithAttribute(
+                                /* tag= */ "AF", appCertificateAttrs, /* closed= */ true)
+                        + "</OF>"
+                        + "</R>"
+                        + "</RL>";
 
-        String actualRules = xmlSerializer.serialize(Collections.singletonList(rule));
+        String actualRules =
+                xmlSerializer.serialize(
+                        Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
 
         assertEquals(expectedRules, actualRules);
     }
 
     @Test
     public void testXmlString_serializeValidAtomicFormula_stringValue() throws Exception {
-        Rule rule = new Rule(
-                new AtomicFormula.StringAtomicFormula(AtomicFormula.PACKAGE_NAME, "com.app.test",
-                        /* isHashedValue= */ false),
-                Rule.DENY);
+        Rule rule =
+                new Rule(
+                        new AtomicFormula.StringAtomicFormula(
+                                AtomicFormula.PACKAGE_NAME,
+                                "com.app.test",
+                                /* isHashedValue= */ false),
+                        Rule.DENY);
         RuleSerializer xmlSerializer = new RuleXmlSerializer();
         Map<String, String> packageNameAttrs = new LinkedHashMap<>();
         packageNameAttrs.put("K", String.valueOf(AtomicFormula.PACKAGE_NAME));
         packageNameAttrs.put("V", "com.app.test");
         packageNameAttrs.put("H", "false");
-        String expectedRules = "<RL>"
-                + generateTagWithAttribute(/* tag= */ "R",
-                    Collections.singletonMap("E", String.valueOf(Rule.DENY)),
-                        /* closed= */ false)
-                + generateTagWithAttribute(/* tag= */ "AF", packageNameAttrs, /* closed= */ true)
-                + "</R>"
-                + "</RL>";
+        String expectedRules =
+                "<RL>"
+                        + generateTagWithAttribute(
+                                /* tag= */ "R",
+                                Collections.singletonMap("E", String.valueOf(Rule.DENY)),
+                                /* closed= */ false)
+                        + generateTagWithAttribute(
+                                /* tag= */ "AF", packageNameAttrs, /* closed= */ true)
+                        + "</R>"
+                        + "</RL>";
 
-        String actualRules = xmlSerializer.serialize(Collections.singletonList(rule));
+        String actualRules =
+                xmlSerializer.serialize(
+                        Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
 
         assertEquals(expectedRules, actualRules);
     }
 
     @Test
     public void testXmlString_serializeValidAtomicFormula_integerValue() throws Exception {
-        Rule rule = new Rule(
-                new AtomicFormula.IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.EQ, 1),
-                Rule.DENY);
+        Rule rule =
+                new Rule(
+                        new AtomicFormula.IntAtomicFormula(
+                                AtomicFormula.VERSION_CODE, AtomicFormula.EQ, 1),
+                        Rule.DENY);
         RuleSerializer xmlSerializer = new RuleXmlSerializer();
         Map<String, String> versionCodeAttrs = new LinkedHashMap<>();
         versionCodeAttrs.put("K", String.valueOf(AtomicFormula.VERSION_CODE));
         versionCodeAttrs.put("O", String.valueOf(AtomicFormula.EQ));
         versionCodeAttrs.put("V", "1");
-        String expectedRules = "<RL>"
-                + generateTagWithAttribute(/* tag= */ "R",
-                    Collections.singletonMap("E", String.valueOf(Rule.DENY)),
-                        /* closed= */ false)
-                + generateTagWithAttribute(/* tag= */ "AF", versionCodeAttrs, /* closed= */ true)
-                + "</R>"
-                + "</RL>";
+        String expectedRules =
+                "<RL>"
+                        + generateTagWithAttribute(
+                                /* tag= */ "R",
+                                Collections.singletonMap("E", String.valueOf(Rule.DENY)),
+                                /* closed= */ false)
+                        + generateTagWithAttribute(
+                                /* tag= */ "AF", versionCodeAttrs, /* closed= */ true)
+                        + "</R>"
+                        + "</RL>";
 
-        String actualRules = xmlSerializer.serialize(Collections.singletonList(rule));
+        String actualRules =
+                xmlSerializer.serialize(
+                        Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
 
         assertEquals(expectedRules, actualRules);
     }
 
     @Test
     public void testXmlString_serializeValidAtomicFormula_booleanValue() throws Exception {
-        Rule rule = new Rule(
-                new AtomicFormula.BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true),
-                Rule.DENY);
+        Rule rule =
+                new Rule(
+                        new AtomicFormula.BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true),
+                        Rule.DENY);
         RuleSerializer xmlSerializer = new RuleXmlSerializer();
         Map<String, String> preInstalledAttrs = new LinkedHashMap<>();
         preInstalledAttrs.put("K", String.valueOf(AtomicFormula.PRE_INSTALLED));
         preInstalledAttrs.put("V", "true");
-        String expectedRules = "<RL>"
-                + generateTagWithAttribute(/* tag= */ "R",
-                    Collections.singletonMap("E", String.valueOf(Rule.DENY)),
-                        /* closed= */ false)
-                + generateTagWithAttribute(/* tag= */ "AF", preInstalledAttrs, /* closed= */ true)
-                + "</R>"
-                + "</RL>";
+        String expectedRules =
+                "<RL>"
+                        + generateTagWithAttribute(
+                                /* tag= */ "R",
+                                Collections.singletonMap("E", String.valueOf(Rule.DENY)),
+                                /* closed= */ false)
+                        + generateTagWithAttribute(
+                                /* tag= */ "AF", preInstalledAttrs, /* closed= */ true)
+                        + "</R>"
+                        + "</RL>";
 
-        String actualRules = xmlSerializer.serialize(Collections.singletonList(rule));
+        String actualRules =
+                xmlSerializer.serialize(
+                        Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
 
         assertEquals(expectedRules, actualRules);
     }
@@ -282,11 +371,14 @@
         assertExpectException(
                 RuleSerializeException.class,
                 /* expectedExceptionMessageRegex */ "Invalid formula type",
-                () -> xmlSerializer.serialize(Collections.singletonList(rule)));
+                () ->
+                        xmlSerializer.serialize(
+                                Collections.singletonList(rule),
+                                /* formatVersion= */ Optional.empty()));
     }
 
-    private String generateTagWithAttribute(String tag, Map<String, String> attributeValues,
-            boolean closed) {
+    private String generateTagWithAttribute(
+            String tag, Map<String, String> attributeValues, boolean closed) {
         StringBuilder res = new StringBuilder("<");
         res.append(tag);
         for (String attribute : attributeValues.keySet()) {
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
index 2cc5323..1a630ff 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
@@ -122,13 +122,15 @@
         final Bundle local = new Bundle();
         final Bundle global = new Bundle();
 
-        UserRestrictionsUtils.sortToGlobalAndLocal(null, false /* isDeviceOwner */,
-                UserManagerInternal.CAMERA_NOT_DISABLED, global, local);
+        UserRestrictionsUtils.sortToGlobalAndLocal(null,
+                UserManagerInternal.OWNER_TYPE_PROFILE_OWNER,
+                global, local);
         assertEquals(0, global.size());
         assertEquals(0, local.size());
 
-        UserRestrictionsUtils.sortToGlobalAndLocal(Bundle.EMPTY, false /* isDeviceOwner */,
-                UserManagerInternal.CAMERA_NOT_DISABLED, global, local);
+        UserRestrictionsUtils.sortToGlobalAndLocal(Bundle.EMPTY,
+                UserManagerInternal.OWNER_TYPE_PROFILE_OWNER,
+                global, local);
         assertEquals(0, global.size());
         assertEquals(0, local.size());
 
@@ -140,8 +142,10 @@
                 UserManager.DISALLOW_CONFIG_TETHERING,
                 UserManager.DISALLOW_OUTGOING_BEAM,
                 UserManager.DISALLOW_APPS_CONTROL,
-                UserManager.ENSURE_VERIFY_APPS
-        ), true /* isDeviceOwner */, UserManagerInternal.CAMERA_NOT_DISABLED, global, local);
+                UserManager.ENSURE_VERIFY_APPS,
+                UserManager.DISALLOW_CAMERA
+                ), UserManagerInternal.OWNER_TYPE_DEVICE_OWNER,
+                global, local);
 
 
         assertRestrictions(newRestrictions(
@@ -154,7 +158,10 @@
 
                 // These can only be set by DO.
                 UserManager.DISALLOW_USB_FILE_TRANSFER,
-                UserManager.DISALLOW_CONFIG_TETHERING
+                UserManager.DISALLOW_CONFIG_TETHERING,
+
+                // This can be set by DO or PO of organisation owned device
+                UserManager.DISALLOW_CAMERA
         ), global);
 
         assertRestrictions(newRestrictions(
@@ -174,8 +181,10 @@
                 UserManager.DISALLOW_CONFIG_TETHERING,
                 UserManager.DISALLOW_OUTGOING_BEAM,
                 UserManager.DISALLOW_APPS_CONTROL,
-                UserManager.ENSURE_VERIFY_APPS
-        ), false /* isDeviceOwner */, UserManagerInternal.CAMERA_NOT_DISABLED, global, local);
+                UserManager.ENSURE_VERIFY_APPS,
+                UserManager.DISALLOW_CAMERA
+                ), UserManagerInternal.OWNER_TYPE_PROFILE_OWNER,
+                global, local);
 
         assertRestrictions(newRestrictions(
                 // This one is global no matter who sets it.
@@ -193,23 +202,47 @@
 
                 // These can only be set by DO.
                 UserManager.DISALLOW_USB_FILE_TRANSFER,
-                UserManager.DISALLOW_CONFIG_TETHERING
+                UserManager.DISALLOW_CONFIG_TETHERING,
+
+                // This can be set by DO or PO of organisation owned device
+                UserManager.DISALLOW_CAMERA
         ), local);
 
+        local.clear();
+        global.clear();
+
+        // Restrictions set by PO of organisation owned device
+        UserRestrictionsUtils.sortToGlobalAndLocal(newRestrictions(
+                UserManager.DISALLOW_CONFIG_DATE_TIME
+                ), UserManagerInternal.OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE,
+                global, local);
+
+        assertRestrictions(newRestrictions(
+                // This user restriction is global when set by PO of org owned device
+                UserManager.DISALLOW_CONFIG_DATE_TIME
+        ), global);
+        assertEquals(0, local.size());
     }
 
     public void testSortToLocalAndGlobalWithCameraDisabled() {
         final Bundle local = new Bundle();
         final Bundle global = new Bundle();
 
-        UserRestrictionsUtils.sortToGlobalAndLocal(Bundle.EMPTY, false,
-                UserManagerInternal.CAMERA_DISABLED_GLOBALLY, global, local);
+        UserRestrictionsUtils.sortToGlobalAndLocal(newRestrictions(UserManager.DISALLOW_CAMERA),
+                UserManagerInternal.OWNER_TYPE_DEVICE_OWNER, global, local);
         assertRestrictions(newRestrictions(UserManager.DISALLOW_CAMERA), global);
         assertEquals(0, local.size());
         global.clear();
 
-        UserRestrictionsUtils.sortToGlobalAndLocal(Bundle.EMPTY, false,
-                UserManagerInternal.CAMERA_DISABLED_LOCALLY, global, local);
+        UserRestrictionsUtils.sortToGlobalAndLocal(newRestrictions(UserManager.DISALLOW_CAMERA),
+                UserManagerInternal.OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE, global,
+                local);
+        assertRestrictions(newRestrictions(UserManager.DISALLOW_CAMERA), global);
+        assertEquals(0, local.size());
+        global.clear();
+
+        UserRestrictionsUtils.sortToGlobalAndLocal(newRestrictions(UserManager.DISALLOW_CAMERA),
+                UserManagerInternal.OWNER_TYPE_PROFILE_OWNER, global, local);
         assertEquals(0, global.size());
         assertRestrictions(newRestrictions(UserManager.DISALLOW_CAMERA), local);
     }
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java
index 601e2e9..790f2b4 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java
@@ -24,6 +24,7 @@
 import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT;
 import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE;
 import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE;
+import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_IGNORE_OTA;
 import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST;
 import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_LOG;
 
@@ -394,33 +395,45 @@
         assertFalse(mUserSystemPackageInstaller.isLogMode());
         assertFalse(mUserSystemPackageInstaller.isEnforceMode());
         assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+        assertFalse(mUserSystemPackageInstaller.isIgnoreOtaMode());
 
         setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_LOG);
         assertTrue(mUserSystemPackageInstaller.isLogMode());
         assertFalse(mUserSystemPackageInstaller.isEnforceMode());
         assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+        assertFalse(mUserSystemPackageInstaller.isIgnoreOtaMode());
 
         setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE);
         assertFalse(mUserSystemPackageInstaller.isLogMode());
         assertTrue(mUserSystemPackageInstaller.isEnforceMode());
         assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+        assertFalse(mUserSystemPackageInstaller.isIgnoreOtaMode());
 
         setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST);
         assertFalse(mUserSystemPackageInstaller.isLogMode());
         assertFalse(mUserSystemPackageInstaller.isEnforceMode());
         assertTrue(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+        assertFalse(mUserSystemPackageInstaller.isIgnoreOtaMode());
+
+        setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_IGNORE_OTA);
+        assertFalse(mUserSystemPackageInstaller.isLogMode());
+        assertFalse(mUserSystemPackageInstaller.isEnforceMode());
+        assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+        assertTrue(mUserSystemPackageInstaller.isIgnoreOtaMode());
 
         setUserTypePackageWhitelistMode(
                 USER_TYPE_PACKAGE_WHITELIST_MODE_LOG | USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE);
         assertTrue(mUserSystemPackageInstaller.isLogMode());
         assertTrue(mUserSystemPackageInstaller.isEnforceMode());
         assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+        assertFalse(mUserSystemPackageInstaller.isIgnoreOtaMode());
 
         setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST
                 | USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE);
         assertFalse(mUserSystemPackageInstaller.isLogMode());
         assertTrue(mUserSystemPackageInstaller.isEnforceMode());
         assertTrue(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+        assertFalse(mUserSystemPackageInstaller.isIgnoreOtaMode());
     }
 
     /** Sets the whitelist mode to the desired value via adb's setprop. */
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyTest.java
new file mode 100644
index 0000000..f9f23c3
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyTest.java
@@ -0,0 +1,592 @@
+/*
+ * Copyright 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.timezonedetector;
+
+import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID;
+import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET;
+import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY;
+import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY;
+import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS;
+import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
+import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
+
+import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.SCORE_HIGH;
+import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.SCORE_HIGHEST;
+import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.SCORE_LOW;
+import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.SCORE_MEDIUM;
+import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.SCORE_NONE;
+import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.SCORE_USAGE_THRESHOLD;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.timezonedetector.PhoneTimeZoneSuggestion;
+import android.app.timezonedetector.PhoneTimeZoneSuggestion.MatchType;
+import android.app.timezonedetector.PhoneTimeZoneSuggestion.Quality;
+
+import com.android.server.timezonedetector.TimeZoneDetectorStrategy.QualifiedPhoneTimeZoneSuggestion;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedList;
+
+/**
+ * White-box unit tests for {@link TimeZoneDetectorStrategy}.
+ */
+public class TimeZoneDetectorStrategyTest {
+
+    /** A time zone used for initialization that does not occur elsewhere in tests. */
+    private static final String ARBITRARY_TIME_ZONE_ID = "Etc/UTC";
+    private static final int PHONE1_ID = 10000;
+    private static final int PHONE2_ID = 20000;
+
+    // Suggestion test cases are ordered so that each successive one is of the same or higher score
+    // than the previous.
+    private static final SuggestionTestCase[] TEST_CASES = new SuggestionTestCase[] {
+            newTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY,
+                    QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS, SCORE_LOW),
+            newTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY, QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET,
+                    SCORE_MEDIUM),
+            newTestCase(MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET,
+                    QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, SCORE_MEDIUM),
+            newTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY, QUALITY_SINGLE_ZONE, SCORE_HIGH),
+            newTestCase(MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE, SCORE_HIGH),
+            newTestCase(MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY,
+                    QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, SCORE_HIGHEST),
+            newTestCase(MATCH_TYPE_EMULATOR_ZONE_ID, QUALITY_SINGLE_ZONE, SCORE_HIGHEST),
+    };
+
+    private TimeZoneDetectorStrategy mTimeZoneDetectorStrategy;
+    private FakeTimeZoneDetectorStrategyCallback mFakeTimeZoneDetectorStrategyCallback;
+
+    @Before
+    public void setUp() {
+        mFakeTimeZoneDetectorStrategyCallback = new FakeTimeZoneDetectorStrategyCallback();
+        mTimeZoneDetectorStrategy =
+                new TimeZoneDetectorStrategy(mFakeTimeZoneDetectorStrategyCallback);
+    }
+
+    @Test
+    public void testEmptySuggestions() {
+        PhoneTimeZoneSuggestion phone1TimeZoneSuggestion = createEmptyPhone1Suggestion();
+        PhoneTimeZoneSuggestion phone2TimeZoneSuggestion = createEmptyPhone2Suggestion();
+        Script script = new Script()
+                .initializeTimeZoneDetectionEnabled(true)
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
+
+        script.suggestPhoneTimeZone(phone1TimeZoneSuggestion)
+                .verifyTimeZoneNotSet();
+
+        // Assert internal service state.
+        QualifiedPhoneTimeZoneSuggestion expectedPhone1ScoredSuggestion =
+                new QualifiedPhoneTimeZoneSuggestion(phone1TimeZoneSuggestion, SCORE_NONE);
+        assertEquals(expectedPhone1ScoredSuggestion,
+                mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
+        assertNull(mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE2_ID));
+        assertEquals(expectedPhone1ScoredSuggestion,
+                mTimeZoneDetectorStrategy.findBestSuggestionForTests());
+
+        script.suggestPhoneTimeZone(phone2TimeZoneSuggestion)
+                .verifyTimeZoneNotSet();
+
+        // Assert internal service state.
+        QualifiedPhoneTimeZoneSuggestion expectedPhone2ScoredSuggestion =
+                new QualifiedPhoneTimeZoneSuggestion(phone2TimeZoneSuggestion, SCORE_NONE);
+        assertEquals(expectedPhone1ScoredSuggestion,
+                mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
+        assertEquals(expectedPhone2ScoredSuggestion,
+                mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE2_ID));
+        // Phone 1 should always beat phone 2, all other things being equal.
+        assertEquals(expectedPhone1ScoredSuggestion,
+                mTimeZoneDetectorStrategy.findBestSuggestionForTests());
+    }
+
+    @Test
+    public void testFirstPlausibleSuggestionAcceptedWhenTimeZoneUninitialized() {
+        SuggestionTestCase testCase = newTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY,
+                QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS, SCORE_LOW);
+        PhoneTimeZoneSuggestion lowQualitySuggestion =
+                testCase.createSuggestion(PHONE1_ID, "America/New_York");
+
+        // The device time zone setting is left uninitialized.
+        Script script = new Script()
+                .initializeTimeZoneDetectionEnabled(true);
+
+        // The very first suggestion will be taken.
+        script.suggestPhoneTimeZone(lowQualitySuggestion)
+                .verifyTimeZoneSetAndReset(lowQualitySuggestion);
+
+        // Assert internal service state.
+        QualifiedPhoneTimeZoneSuggestion expectedScoredSuggestion =
+                new QualifiedPhoneTimeZoneSuggestion(lowQualitySuggestion, testCase.expectedScore);
+        assertEquals(expectedScoredSuggestion,
+                mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
+        assertEquals(expectedScoredSuggestion,
+                mTimeZoneDetectorStrategy.findBestSuggestionForTests());
+
+        // Another low quality suggestion will be ignored now that the setting is initialized.
+        PhoneTimeZoneSuggestion lowQualitySuggestion2 =
+                testCase.createSuggestion(PHONE1_ID, "America/Los_Angeles");
+        script.suggestPhoneTimeZone(lowQualitySuggestion2)
+                .verifyTimeZoneNotSet();
+
+        // Assert internal service state.
+        QualifiedPhoneTimeZoneSuggestion expectedScoredSuggestion2 =
+                new QualifiedPhoneTimeZoneSuggestion(lowQualitySuggestion2, testCase.expectedScore);
+        assertEquals(expectedScoredSuggestion2,
+                mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
+        assertEquals(expectedScoredSuggestion2,
+                mTimeZoneDetectorStrategy.findBestSuggestionForTests());
+    }
+
+    /**
+     * Confirms that toggling the auto time zone detection setting has the expected behavior when
+     * the strategy is "opinionated".
+     */
+    @Test
+    public void testTogglingTimeZoneDetection() {
+        Script script = new Script();
+
+        for (SuggestionTestCase testCase : TEST_CASES) {
+            // Start with the device in a known state.
+            script.initializeTimeZoneDetectionEnabled(false)
+                    .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
+
+            PhoneTimeZoneSuggestion suggestion =
+                    testCase.createSuggestion(PHONE1_ID, "Europe/London");
+            script.suggestPhoneTimeZone(suggestion);
+
+            // When time zone detection is not enabled, the time zone suggestion will not be set
+            // regardless of the score.
+            script.verifyTimeZoneNotSet();
+
+            // Assert internal service state.
+            QualifiedPhoneTimeZoneSuggestion expectedScoredSuggestion =
+                    new QualifiedPhoneTimeZoneSuggestion(suggestion, testCase.expectedScore);
+            assertEquals(expectedScoredSuggestion,
+                    mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
+            assertEquals(expectedScoredSuggestion,
+                    mTimeZoneDetectorStrategy.findBestSuggestionForTests());
+
+            // Toggling the time zone setting on should cause the device setting to be set.
+            script.timeZoneDetectionEnabled(true);
+
+            // When time zone detection is already enabled the suggestion (if it scores highly
+            // enough) should be set immediately.
+            if (testCase.expectedScore >= SCORE_USAGE_THRESHOLD) {
+                script.verifyTimeZoneSetAndReset(suggestion);
+            } else {
+                script.verifyTimeZoneNotSet();
+            }
+
+            // Assert internal service state.
+            assertEquals(expectedScoredSuggestion,
+                    mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
+            assertEquals(expectedScoredSuggestion,
+                    mTimeZoneDetectorStrategy.findBestSuggestionForTests());
+
+            // Toggling the time zone setting should off should do nothing.
+            script.timeZoneDetectionEnabled(false)
+                    .verifyTimeZoneNotSet();
+
+            // Assert internal service state.
+            assertEquals(expectedScoredSuggestion,
+                    mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
+            assertEquals(expectedScoredSuggestion,
+                    mTimeZoneDetectorStrategy.findBestSuggestionForTests());
+        }
+    }
+
+    @Test
+    public void testSuggestionsSinglePhone() {
+        Script script = new Script()
+                .initializeTimeZoneDetectionEnabled(true)
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID);
+
+        for (SuggestionTestCase testCase : TEST_CASES) {
+            makePhone1SuggestionAndCheckState(script, testCase);
+        }
+
+        /*
+         * This is the same test as above but the test cases are in
+         * reverse order of their expected score. New suggestions always replace previous ones:
+         * there's effectively no history and so ordering shouldn't make any difference.
+         */
+
+        // Each test case will have the same or lower score than the last.
+        ArrayList<SuggestionTestCase> descendingCasesByScore =
+                new ArrayList<>(Arrays.asList(TEST_CASES));
+        Collections.reverse(descendingCasesByScore);
+
+        for (SuggestionTestCase testCase : descendingCasesByScore) {
+            makePhone1SuggestionAndCheckState(script, testCase);
+        }
+    }
+
+    private void makePhone1SuggestionAndCheckState(Script script, SuggestionTestCase testCase) {
+        // Give the next suggestion a different zone from the currently set device time zone;
+        String currentZoneId = mFakeTimeZoneDetectorStrategyCallback.getDeviceTimeZone();
+        String suggestionZoneId =
+                "Europe/London".equals(currentZoneId) ? "Europe/Paris" : "Europe/London";
+        PhoneTimeZoneSuggestion zonePhone1Suggestion =
+                testCase.createSuggestion(PHONE1_ID, suggestionZoneId);
+        QualifiedPhoneTimeZoneSuggestion expectedZonePhone1ScoredSuggestion =
+                new QualifiedPhoneTimeZoneSuggestion(zonePhone1Suggestion, testCase.expectedScore);
+
+        script.suggestPhoneTimeZone(zonePhone1Suggestion);
+        if (testCase.expectedScore >= SCORE_USAGE_THRESHOLD) {
+            script.verifyTimeZoneSetAndReset(zonePhone1Suggestion);
+        } else {
+            script.verifyTimeZoneNotSet();
+        }
+
+        // Assert internal service state.
+        assertEquals(expectedZonePhone1ScoredSuggestion,
+                mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
+        assertEquals(expectedZonePhone1ScoredSuggestion,
+                mTimeZoneDetectorStrategy.findBestSuggestionForTests());
+    }
+
+    /**
+     * Tries a set of test cases to see if the phone with the lowest ID is given preference. This
+     * test also confirms that the time zone setting would only be set if a suggestion is of
+     * sufficient quality.
+     */
+    @Test
+    public void testMultiplePhoneSuggestionScoringAndPhoneIdBias() {
+        String[] zoneIds = { "Europe/London", "Europe/Paris" };
+        PhoneTimeZoneSuggestion emptyPhone1Suggestion = createEmptyPhone1Suggestion();
+        PhoneTimeZoneSuggestion emptyPhone2Suggestion = createEmptyPhone2Suggestion();
+        QualifiedPhoneTimeZoneSuggestion expectedEmptyPhone1ScoredSuggestion =
+                new QualifiedPhoneTimeZoneSuggestion(emptyPhone1Suggestion, SCORE_NONE);
+        QualifiedPhoneTimeZoneSuggestion expectedEmptyPhone2ScoredSuggestion =
+                new QualifiedPhoneTimeZoneSuggestion(emptyPhone2Suggestion, SCORE_NONE);
+
+        Script script = new Script()
+                .initializeTimeZoneDetectionEnabled(true)
+                .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+                // Initialize the latest suggestions as empty so we don't need to worry about nulls
+                // below for the first loop.
+                .suggestPhoneTimeZone(emptyPhone1Suggestion)
+                .suggestPhoneTimeZone(emptyPhone2Suggestion)
+                .resetState();
+
+        for (SuggestionTestCase testCase : TEST_CASES) {
+            PhoneTimeZoneSuggestion zonePhone1Suggestion =
+                    testCase.createSuggestion(PHONE1_ID, zoneIds[0]);
+            PhoneTimeZoneSuggestion zonePhone2Suggestion =
+                    testCase.createSuggestion(PHONE2_ID, zoneIds[1]);
+            QualifiedPhoneTimeZoneSuggestion expectedZonePhone1ScoredSuggestion =
+                    new QualifiedPhoneTimeZoneSuggestion(zonePhone1Suggestion,
+                            testCase.expectedScore);
+            QualifiedPhoneTimeZoneSuggestion expectedZonePhone2ScoredSuggestion =
+                    new QualifiedPhoneTimeZoneSuggestion(zonePhone2Suggestion,
+                            testCase.expectedScore);
+
+            // Start the test by making a suggestion for phone 1.
+            script.suggestPhoneTimeZone(zonePhone1Suggestion);
+            if (testCase.expectedScore >= SCORE_USAGE_THRESHOLD) {
+                script.verifyTimeZoneSetAndReset(zonePhone1Suggestion);
+            } else {
+                script.verifyTimeZoneNotSet();
+            }
+
+            // Assert internal service state.
+            assertEquals(expectedZonePhone1ScoredSuggestion,
+                    mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
+            assertEquals(expectedEmptyPhone2ScoredSuggestion,
+                    mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE2_ID));
+            assertEquals(expectedZonePhone1ScoredSuggestion,
+                    mTimeZoneDetectorStrategy.findBestSuggestionForTests());
+
+            // Phone 2 then makes an alternative suggestion with an identical score. Phone 1's
+            // suggestion should still "win" if it is above the required threshold.
+            script.suggestPhoneTimeZone(zonePhone2Suggestion);
+            script.verifyTimeZoneNotSet();
+
+            // Assert internal service state.
+            assertEquals(expectedZonePhone1ScoredSuggestion,
+                    mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
+            assertEquals(expectedZonePhone2ScoredSuggestion,
+                    mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE2_ID));
+            // Phone 1 should always beat phone 2, all other things being equal.
+            assertEquals(expectedZonePhone1ScoredSuggestion,
+                    mTimeZoneDetectorStrategy.findBestSuggestionForTests());
+
+            // Withdrawing phone 1's suggestion should leave phone 2 as the new winner. Since the
+            // zoneId is different, the time zone setting should be updated if the score is high
+            // enough.
+            script.suggestPhoneTimeZone(emptyPhone1Suggestion);
+            if (testCase.expectedScore >= SCORE_USAGE_THRESHOLD) {
+                script.verifyTimeZoneSetAndReset(zonePhone2Suggestion);
+            } else {
+                script.verifyTimeZoneNotSet();
+            }
+
+            // Assert internal service state.
+            assertEquals(expectedEmptyPhone1ScoredSuggestion,
+                    mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
+            assertEquals(expectedZonePhone2ScoredSuggestion,
+                    mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE2_ID));
+            assertEquals(expectedZonePhone2ScoredSuggestion,
+                    mTimeZoneDetectorStrategy.findBestSuggestionForTests());
+
+            // Reset the state for the next loop.
+            script.suggestPhoneTimeZone(emptyPhone2Suggestion)
+                    .verifyTimeZoneNotSet();
+            assertEquals(expectedEmptyPhone1ScoredSuggestion,
+                    mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE1_ID));
+            assertEquals(expectedEmptyPhone2ScoredSuggestion,
+                    mTimeZoneDetectorStrategy.getLatestPhoneSuggestion(PHONE2_ID));
+        }
+    }
+
+    /**
+     * The {@link TimeZoneDetectorStrategy.Callback} is left to detect whether changing the time
+     * zone is actually necessary. This test proves that the service doesn't assume it knows the
+     * current setting.
+     */
+    @Test
+    public void testTimeZoneDetectorStrategyDoesNotAssumeCurrentSetting() {
+        Script script = new Script()
+                .initializeTimeZoneDetectionEnabled(true);
+
+        SuggestionTestCase testCase =
+                newTestCase(MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE, SCORE_HIGH);
+        PhoneTimeZoneSuggestion losAngelesSuggestion =
+                testCase.createSuggestion(PHONE1_ID, "America/Los_Angeles");
+        PhoneTimeZoneSuggestion newYorkSuggestion =
+                testCase.createSuggestion(PHONE1_ID, "America/New_York");
+
+        // Initialization.
+        script.suggestPhoneTimeZone(losAngelesSuggestion)
+                .verifyTimeZoneSetAndReset(losAngelesSuggestion);
+        // Suggest it again - it should not be set because it is already set.
+        script.suggestPhoneTimeZone(losAngelesSuggestion)
+                .verifyTimeZoneNotSet();
+
+        // Toggling time zone detection should set the device time zone only if the current setting
+        // value is different from the most recent phone suggestion.
+        script.timeZoneDetectionEnabled(false)
+                .verifyTimeZoneNotSet()
+                .timeZoneDetectionEnabled(true)
+                .verifyTimeZoneNotSet();
+
+        // Simulate a user turning auto detection off, a new suggestion being made while auto
+        // detection is off, and the user turning it on again.
+        script.timeZoneDetectionEnabled(false)
+                .suggestPhoneTimeZone(newYorkSuggestion)
+                .verifyTimeZoneNotSet();
+        // Latest suggestion should be used.
+        script.timeZoneDetectionEnabled(true)
+                .verifyTimeZoneSetAndReset(newYorkSuggestion);
+    }
+
+    private static PhoneTimeZoneSuggestion createEmptyPhone1Suggestion() {
+        return new PhoneTimeZoneSuggestion.Builder(PHONE1_ID).build();
+    }
+
+    private static PhoneTimeZoneSuggestion createEmptyPhone2Suggestion() {
+        return new PhoneTimeZoneSuggestion.Builder(PHONE2_ID).build();
+    }
+
+    class FakeTimeZoneDetectorStrategyCallback implements TimeZoneDetectorStrategy.Callback {
+
+        private boolean mTimeZoneDetectionEnabled;
+        private TestState<String> mTimeZoneId = new TestState<>();
+
+        @Override
+        public boolean isTimeZoneDetectionEnabled() {
+            return mTimeZoneDetectionEnabled;
+        }
+
+        @Override
+        public boolean isDeviceTimeZoneInitialized() {
+            return mTimeZoneId.getLatest() != null;
+        }
+
+        @Override
+        public String getDeviceTimeZone() {
+            return mTimeZoneId.getLatest();
+        }
+
+        @Override
+        public void setDeviceTimeZone(String zoneId) {
+            mTimeZoneId.set(zoneId);
+        }
+
+        void initializeTimeZoneDetectionEnabled(boolean enabled) {
+            mTimeZoneDetectionEnabled = enabled;
+        }
+
+        void initializeTimeZone(String zoneId) {
+            mTimeZoneId.init(zoneId);
+        }
+
+        void setTimeZoneDetectionEnabled(boolean enabled) {
+            mTimeZoneDetectionEnabled = enabled;
+        }
+
+        void assertTimeZoneNotSet() {
+            mTimeZoneId.assertHasNotBeenSet();
+        }
+
+        void assertTimeZoneSet(String timeZoneId) {
+            mTimeZoneId.assertHasBeenSet();
+            mTimeZoneId.assertChangeCount(1);
+            mTimeZoneId.assertLatestEquals(timeZoneId);
+        }
+
+        void commitAllChanges() {
+            mTimeZoneId.commitLatest();
+        }
+    }
+
+    /** Some piece of state that tests want to track. */
+    private static class TestState<T> {
+        private T mInitialValue;
+        private LinkedList<T> mValues = new LinkedList<>();
+
+        void init(T value) {
+            mValues.clear();
+            mInitialValue = value;
+        }
+
+        void set(T value) {
+            mValues.addFirst(value);
+        }
+
+        boolean hasBeenSet() {
+            return mValues.size() > 0;
+        }
+
+        void assertHasNotBeenSet() {
+            assertFalse(hasBeenSet());
+        }
+
+        void assertHasBeenSet() {
+            assertTrue(hasBeenSet());
+        }
+
+        void commitLatest() {
+            if (hasBeenSet()) {
+                mInitialValue = mValues.getLast();
+                mValues.clear();
+            }
+        }
+
+        void assertLatestEquals(T expected) {
+            assertEquals(expected, getLatest());
+        }
+
+        void assertChangeCount(int expectedCount) {
+            assertEquals(expectedCount, mValues.size());
+        }
+
+        public T getLatest() {
+            if (hasBeenSet()) {
+                return mValues.getFirst();
+            }
+            return mInitialValue;
+        }
+    }
+
+    /**
+     * A "fluent" class allows reuse of code in tests: initialization, simulation and verification
+     * logic.
+     */
+    private class Script {
+
+        Script initializeTimeZoneDetectionEnabled(boolean enabled) {
+            mFakeTimeZoneDetectorStrategyCallback.initializeTimeZoneDetectionEnabled(enabled);
+            return this;
+        }
+
+        Script initializeTimeZoneSetting(String zoneId) {
+            mFakeTimeZoneDetectorStrategyCallback.initializeTimeZone(zoneId);
+            return this;
+        }
+
+        Script timeZoneDetectionEnabled(boolean enabled) {
+            mFakeTimeZoneDetectorStrategyCallback.setTimeZoneDetectionEnabled(enabled);
+            mTimeZoneDetectorStrategy.handleTimeZoneDetectionChange();
+            return this;
+        }
+
+        /** Simulates the time zone detection service receiving a phone-originated suggestion. */
+        Script suggestPhoneTimeZone(PhoneTimeZoneSuggestion phoneTimeZoneSuggestion) {
+            mTimeZoneDetectorStrategy.suggestPhoneTimeZone(phoneTimeZoneSuggestion);
+            return this;
+        }
+
+        /** Simulates the user manually setting the time zone. */
+        Script manuallySetTimeZone(String timeZoneId) {
+            // Assert the test code is correct to call this method.
+            assertFalse(mFakeTimeZoneDetectorStrategyCallback.isTimeZoneDetectionEnabled());
+
+            mFakeTimeZoneDetectorStrategyCallback.initializeTimeZone(timeZoneId);
+            return this;
+        }
+
+        Script verifyTimeZoneNotSet() {
+            mFakeTimeZoneDetectorStrategyCallback.assertTimeZoneNotSet();
+            return this;
+        }
+
+        Script verifyTimeZoneSetAndReset(PhoneTimeZoneSuggestion timeZoneSuggestion) {
+            mFakeTimeZoneDetectorStrategyCallback.assertTimeZoneSet(timeZoneSuggestion.getZoneId());
+            mFakeTimeZoneDetectorStrategyCallback.commitAllChanges();
+            return this;
+        }
+
+        Script resetState() {
+            mFakeTimeZoneDetectorStrategyCallback.commitAllChanges();
+            return this;
+        }
+    }
+
+    private static class SuggestionTestCase {
+        public final int matchType;
+        public final int quality;
+        public final int expectedScore;
+
+        SuggestionTestCase(int matchType, int quality, int expectedScore) {
+            this.matchType = matchType;
+            this.quality = quality;
+            this.expectedScore = expectedScore;
+        }
+
+        private PhoneTimeZoneSuggestion createSuggestion(int phoneId, String zoneId) {
+            return new PhoneTimeZoneSuggestion.Builder(phoneId)
+                    .setZoneId(zoneId)
+                    .setMatchType(matchType)
+                    .setQuality(quality)
+                    .build();
+        }
+    }
+
+    private static SuggestionTestCase newTestCase(
+            @MatchType int matchType, @Quality int quality, int expectedScore) {
+        return new SuggestionTestCase(matchType, quality, expectedScore);
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index ae1bb8e..c712d6d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -962,4 +962,19 @@
         }
         assertThat(exceptionCaught).isTrue();
     }
+
+    @Test
+    public void testRecycleTaskFromAnotherUser() {
+        final ActivityStarter starter = prepareStarter(0 /* flags */);
+        starter.mStartActivity = new ActivityBuilder(mService).build();
+        final Task task = new TaskBuilder(mService.mStackSupervisor)
+                .setStack(mService.mRootActivityContainer.getDefaultDisplay().createStack(
+                        WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */))
+                .setUserId(10)
+                .build();
+
+        final int result = starter.recycleTask(task, null, null);
+        assertThat(result == START_SUCCESS).isTrue();
+        assertThat(starter.mAddingToTask).isTrue();
+    }
 }
diff --git a/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java b/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java
index 2cdf2f6..dcea9bb 100644
--- a/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java
+++ b/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java
@@ -23,7 +23,7 @@
 import android.util.SparseIntArray;
 
 import com.android.internal.telephony.cdma.sms.UserData;
-import com.android.internal.util.XmlUtils;
+import com.android.internal.telephony.util.XmlUtils;
 
 import dalvik.annotation.compat.UnsupportedAppUsage;
 
diff --git a/telephony/java/com/android/internal/telephony/util/XmlUtils.java b/telephony/java/com/android/internal/telephony/util/XmlUtils.java
new file mode 100644
index 0000000..72c5d3a
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/util/XmlUtils.java
@@ -0,0 +1,75 @@
+/*
+ * 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.internal.telephony.util;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/** Utility methods for XML operations. */
+public final class XmlUtils {
+    private XmlUtils() {}
+
+    /**
+     * Moves parser to the first start tag, and expects the tag name being {@code firstElementName}.
+     */
+    public static void beginDocument(XmlPullParser parser, String firstElementName)
+            throws XmlPullParserException, IOException {
+        int type;
+        while ((type = parser.next()) != parser.START_TAG && type != parser.END_DOCUMENT) {
+            // no-op
+        }
+
+        if (type != parser.START_TAG) {
+            throw new XmlPullParserException("No start tag found");
+        }
+
+        if (!parser.getName().equals(firstElementName)) {
+            throw new XmlPullParserException("Unexpected start tag: found " + parser.getName()
+                    + ", expected " + firstElementName);
+        }
+    }
+
+    /**
+     * Moves parser to the next start tag.
+     */
+    public static void nextElement(XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        int type;
+        while ((type = parser.next()) != parser.START_TAG && type != parser.END_DOCUMENT) {
+            // no-op
+        }
+    }
+
+    /**
+     * Moves parser to the next start tag within the {@code outerDepth}.
+     */
+    public static boolean nextElementWithin(XmlPullParser parser, int outerDepth)
+            throws IOException, XmlPullParserException {
+        for (;;) {
+            int type = parser.next();
+            if (type == XmlPullParser.END_DOCUMENT
+                    || (type == XmlPullParser.END_TAG && parser.getDepth() == outerDepth)) {
+                return false;
+            }
+            if (type == XmlPullParser.START_TAG && parser.getDepth() == outerDepth + 1) {
+                return true;
+            }
+        }
+    }
+}
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 06b58fd..e308781 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -670,11 +670,11 @@
     public void testPackageHealthCheckStateTransitions() {
         TestController controller = new TestController();
         PackageWatchdog wd = createWatchdog(controller, true /* withPackagesReady */);
-        MonitoredPackage m1 = wd.new MonitoredPackage(APP_A, LONG_DURATION,
+        MonitoredPackage m1 = wd.newMonitoredPackage(APP_A, LONG_DURATION,
                 false /* hasPassedHealthCheck */);
-        MonitoredPackage m2 = wd.new MonitoredPackage(APP_B, LONG_DURATION, false);
-        MonitoredPackage m3 = wd.new MonitoredPackage(APP_C, LONG_DURATION, false);
-        MonitoredPackage m4 = wd.new MonitoredPackage(APP_D, LONG_DURATION, SHORT_DURATION, true);
+        MonitoredPackage m2 = wd.newMonitoredPackage(APP_B, LONG_DURATION, false);
+        MonitoredPackage m3 = wd.newMonitoredPackage(APP_C, LONG_DURATION, false);
+        MonitoredPackage m4 = wd.newMonitoredPackage(APP_D, LONG_DURATION, SHORT_DURATION, true);
 
         // Verify transition: inactive -> active -> passed
         // Verify initially inactive
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);
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
index 56bff8f..aea2432 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -264,8 +264,8 @@
         &options_.keep_raw_values);
     AddOptionalFlag("--no-compress-regex",
         "Do not compress extensions matching the regular expression. Remember to\n"
-            " use the '$' symbol for end of line. Uses a non case-sensitive\n"
-            " ECMAScript regular expression grammar.",
+            "use the '$' symbol for end of line. Uses a case-sensitive ECMAScript"
+            "regular expression grammar.",
         &no_compress_regex);
     AddOptionalSwitch("--warn-manifest-validation",
         "Treat manifest validation errors as warnings.",
diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp
index e2c65ba7..7214f1a 100644
--- a/tools/aapt2/cmd/Util.cpp
+++ b/tools/aapt2/cmd/Util.cpp
@@ -436,9 +436,9 @@
 }
 
 std::regex GetRegularExpression(const std::string &input) {
-  // Standard ECMAScript grammar plus case insensitive.
+  // Standard ECMAScript grammar.
   std::regex case_insensitive(
-      input, std::regex_constants::icase | std::regex_constants::ECMAScript);
+      input, std::regex_constants::ECMAScript);
   return case_insensitive;
 }
 
diff --git a/tools/aapt2/cmd/Util_test.cpp b/tools/aapt2/cmd/Util_test.cpp
index 2f090bb..ac1f981 100644
--- a/tools/aapt2/cmd/Util_test.cpp
+++ b/tools/aapt2/cmd/Util_test.cpp
@@ -383,13 +383,32 @@
   EXPECT_NE(*adjusted_contraints[1].configs.begin(), ConfigDescription::DefaultConfig());
 }
 
-// TODO(127793905): Enable test
-/*TEST(UtilTest, RegularExperssions) {
+TEST (UtilTest, RegularExperssionsSimple) {
   std::string valid(".bc$");
   std::regex expression = GetRegularExpression(valid);
   EXPECT_TRUE(std::regex_search("file.abc", expression));
   EXPECT_TRUE(std::regex_search("file.123bc", expression));
   EXPECT_FALSE(std::regex_search("abc.zip", expression));
-}*/
+}
+
+TEST (UtilTest, RegularExpressionComplex) {
+  std::string valid("\\.(d|D)(e|E)(x|X)$");
+  std::regex expression = GetRegularExpression(valid);
+  EXPECT_TRUE(std::regex_search("file.dex", expression));
+  EXPECT_TRUE(std::regex_search("file.DEX", expression));
+  EXPECT_TRUE(std::regex_search("file.dEx", expression));
+  EXPECT_FALSE(std::regex_search("file.dexx", expression));
+  EXPECT_FALSE(std::regex_search("dex.file", expression));
+  EXPECT_FALSE(std::regex_search("file.adex", expression));
+}
+
+TEST (UtilTest, RegularExpressionNonEnglish) {
+  std::string valid("\\.(k|K)(o|O)(ń|Ń)(c|C)(ó|Ó)(w|W)(k|K)(a|A)$");
+  std::regex expression = GetRegularExpression(valid);
+  EXPECT_TRUE(std::regex_search("file.końcówka", expression));
+  EXPECT_TRUE(std::regex_search("file.KOŃCÓWKA", expression));
+  EXPECT_TRUE(std::regex_search("file.kOńcÓwkA", expression));
+  EXPECT_FALSE(std::regex_search("file.koncowka", expression));
+}
 
 }  // namespace aapt
diff --git a/wifi/java/android/net/wifi/SynchronousExecutor.java b/wifi/java/android/net/wifi/SynchronousExecutor.java
new file mode 100644
index 0000000..9926b1b
--- /dev/null
+++ b/wifi/java/android/net/wifi/SynchronousExecutor.java
@@ -0,0 +1,29 @@
+/*
+ * 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.wifi;
+
+import java.util.concurrent.Executor;
+
+/**
+ * An executor implementation that runs synchronously on the current thread.
+ * @hide
+ */
+public class SynchronousExecutor implements Executor {
+    @Override
+    public void execute(Runnable r) {
+        r.run();
+    }
+}
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index 0de5066..77f7b9e 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -17,13 +17,16 @@
 package android.net.wifi;
 
 import android.Manifest;
+import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.content.Context;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
@@ -45,6 +48,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.concurrent.Executor;
 
 /**
  * This class provides a way to scan the Wifi universe around the device
@@ -196,24 +200,29 @@
      */
     public static final int REPORT_EVENT_NO_BATCH = (1 << 2);
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"SCAN_TYPE_"}, value = {
+            SCAN_TYPE_LOW_LATENCY,
+            SCAN_TYPE_LOW_POWER,
+            SCAN_TYPE_HIGH_ACCURACY})
+    public @interface ScanType {}
+
     /**
-     * This is used to indicate the purpose of the scan to the wifi chip in
-     * {@link ScanSettings#type}.
-     * On devices with multiple hardware radio chains (and hence different modes of scan),
-     * this type serves as an indication to the hardware on what mode of scan to perform.
-     * Only apps holding android.Manifest.permission.NETWORK_STACK permission can set this value.
-     *
-     * Note: This serves as an intent and not as a stipulation, the wifi chip
-     * might honor or ignore the indication based on the current radio conditions. Always
-     * use the {@link ScanResult#radioChainInfos} to figure out the radio chain configuration used
-     * to receive the corresponding scan result.
+     * Optimize the scan for lower latency.
+     * @see ScanSettings#type
      */
-    /** {@hide} */
-    public static final int TYPE_LOW_LATENCY = 0;
-    /** {@hide} */
-    public static final int TYPE_LOW_POWER = 1;
-    /** {@hide} */
-    public static final int TYPE_HIGH_ACCURACY = 2;
+    public static final int SCAN_TYPE_LOW_LATENCY = 0;
+    /**
+     * Optimize the scan for lower power usage.
+     * @see ScanSettings#type
+     */
+    public static final int SCAN_TYPE_LOW_POWER = 1;
+    /**
+     * Optimize the scan for higher accuracy.
+     * @see ScanSettings#type
+     */
+    public static final int SCAN_TYPE_HIGH_ACCURACY = 2;
 
     /** {@hide} */
     public static final String SCAN_PARAMS_SCAN_SETTINGS_KEY = "ScanSettings";
@@ -228,18 +237,14 @@
      * scan configuration parameters to be sent to {@link #startBackgroundScan}
      */
     public static class ScanSettings implements Parcelable {
-        /**
-         * Hidden network to be scanned for.
-         * {@hide}
-         */
+        /** Hidden network to be scanned for. */
         public static class HiddenNetwork {
             /** SSID of the network */
-            public String ssid;
+            @NonNull
+            public final String ssid;
 
-            /**
-             * Default constructor for HiddenNetwork.
-             */
-            public HiddenNetwork(String ssid) {
+            /** Default constructor for HiddenNetwork. */
+            public HiddenNetwork(@NonNull String ssid) {
                 this.ssid = ssid;
             }
         }
@@ -249,12 +254,12 @@
         /** list of channels; used when band is set to WIFI_BAND_UNSPECIFIED */
         public ChannelSpec[] channels;
         /**
-         * list of hidden networks to scan for. Explicit probe requests are sent out for such
+         * List of hidden networks to scan for. Explicit probe requests are sent out for such
          * networks during scan. Only valid for single scan requests.
-         * {@hide}
          */
+        @NonNull
         @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
-        public HiddenNetwork[] hiddenNetworks;
+        public final List<HiddenNetwork> hiddenNetworks = new ArrayList<>();
         /** period of background scan; in millisecond, 0 => single shot scan */
         public int periodInMs;
         /** must have a valid REPORT_EVENT value */
@@ -285,11 +290,24 @@
         public boolean isPnoScan;
         /**
          * Indicate the type of scan to be performed by the wifi chip.
-         * Default value: {@link #TYPE_LOW_LATENCY}.
-         * {@hide}
+         *
+         * On devices with multiple hardware radio chains (and hence different modes of scan),
+         * this type serves as an indication to the hardware on what mode of scan to perform.
+         * Only apps holding {@link android.Manifest.permission.NETWORK_STACK} permission can set
+         * this value.
+         *
+         * Note: This serves as an intent and not as a stipulation, the wifi chip
+         * might honor or ignore the indication based on the current radio conditions. Always
+         * use the {@link ScanResult#radioChainInfos} to figure out the radio chain configuration
+         * used to receive the corresponding scan result.
+         *
+         * One of {@link #SCAN_TYPE_LOW_LATENCY}, {@link #SCAN_TYPE_LOW_POWER},
+         * {@link #SCAN_TYPE_HIGH_ACCURACY}.
+         * Default value: {@link #SCAN_TYPE_LOW_LATENCY}.
          */
+        @ScanType
         @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
-        public int type = TYPE_LOW_LATENCY;
+        public int type = SCAN_TYPE_LOW_LATENCY;
         /**
          * This scan request may ignore location settings while receiving scans. This should only
          * be used in emergency situations.
@@ -336,13 +354,9 @@
             } else {
                 dest.writeInt(0);
             }
-            if (hiddenNetworks != null) {
-                dest.writeInt(hiddenNetworks.length);
-                for (int i = 0; i < hiddenNetworks.length; i++) {
-                    dest.writeString(hiddenNetworks[i].ssid);
-                }
-            } else {
-                dest.writeInt(0);
+            dest.writeInt(hiddenNetworks.size());
+            for (HiddenNetwork hiddenNetwork : hiddenNetworks) {
+                dest.writeString(hiddenNetwork.ssid);
             }
         }
 
@@ -372,10 +386,10 @@
                             settings.channels[i] = spec;
                         }
                         int numNetworks = in.readInt();
-                        settings.hiddenNetworks = new HiddenNetwork[numNetworks];
+                        settings.hiddenNetworks.clear();
                         for (int i = 0; i < numNetworks; i++) {
                             String ssid = in.readString();
-                            settings.hiddenNetworks[i] = new HiddenNetwork(ssid);;
+                            settings.hiddenNetworks.add(new HiddenNetwork(ssid));
                         }
                         return settings;
                     }
@@ -801,33 +815,44 @@
     }
 
     /**
-     * Register a listener that will receive results from all single scans
-     * Either the onSuccess/onFailure will be called once when the listener is registered. After
-     * (assuming onSuccess was called) all subsequent single scan results will be delivered to the
-     * listener. It is possible that onFullResult will not be called for all results of the first
-     * scan if the listener was registered during the scan.
+     * Register a listener that will receive results from all single scans.
+     * Either the {@link ScanListener#onSuccess()} or  {@link ScanListener#onFailure(int, String)}
+     * method will be called once when the listener is registered.
+     * Afterwards (assuming onSuccess was called), all subsequent single scan results will be
+     * delivered to the listener. It is possible that onFullResult will not be called for all
+     * results of the first scan if the listener was registered during the scan.
      *
      * @param listener specifies the object to report events to. This object is also treated as a
      *                 key for this request, and must also be specified to cancel the request.
      *                 Multiple requests should also not share this object.
-     * {@hide}
      */
     @RequiresPermission(Manifest.permission.NETWORK_STACK)
-    public void registerScanListener(ScanListener listener) {
+    public void registerScanListener(@NonNull @CallbackExecutor Executor executor,
+            @NonNull ScanListener listener) {
+        Preconditions.checkNotNull(executor, "executor cannot be null");
         Preconditions.checkNotNull(listener, "listener cannot be null");
-        int key = addListener(listener);
+        int key = addListener(listener, executor);
         if (key == INVALID_KEY) return;
         validateChannel();
         mAsyncChannel.sendMessage(CMD_REGISTER_SCAN_LISTENER, 0, key);
     }
 
     /**
+     * Overload of {@link #registerScanListener(Executor, ScanListener)} that executes the callback
+     * synchronously.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.NETWORK_STACK)
+    public void registerScanListener(@NonNull ScanListener listener) {
+        registerScanListener(new SynchronousExecutor(), listener);
+    }
+
+    /**
      * Deregister a listener for ongoing single scans
      * @param listener specifies which scan to cancel; must be same object as passed in {@link
      *  #registerScanListener}
-     * {@hide}
      */
-    public void deregisterScanListener(ScanListener listener) {
+    public void unregisterScanListener(@NonNull ScanListener listener) {
         Preconditions.checkNotNull(listener, "listener cannot be null");
         int key = removeListener(listener);
         if (key == INVALID_KEY) return;
@@ -1280,6 +1305,7 @@
     private int mListenerKey = 1;
 
     private final SparseArray mListenerMap = new SparseArray();
+    private final SparseArray<Executor> mExecutorMap = new SparseArray<>();
     private final Object mListenerMapLock = new Object();
 
     private AsyncChannel mAsyncChannel;
@@ -1327,10 +1353,14 @@
                 "No permission to access and change wifi or a bad initialization");
     }
 
+    private int addListener(ActionListener listener) {
+        return addListener(listener, null);
+    }
+
     // Add a listener into listener map. If the listener already exists, return INVALID_KEY and
     // send an error message to internal handler; Otherwise add the listener to the listener map and
     // return the key of the listener.
-    private int addListener(ActionListener listener) {
+    private int addListener(ActionListener listener, Executor executor) {
         synchronized (mListenerMapLock) {
             boolean keyExists = (getListenerKey(listener) != INVALID_KEY);
             // Note we need to put the listener into listener map even if it's a duplicate as the
@@ -1346,6 +1376,7 @@
                 message.sendToTarget();
                 return INVALID_KEY;
             } else {
+                mExecutorMap.put(key, executor);
                 return key;
             }
         }
@@ -1363,11 +1394,22 @@
         return key;
     }
 
-    private Object getListener(int key) {
-        if (key == INVALID_KEY) return null;
+    private static class ListenerWithExecutor {
+        @Nullable final Object mListener;
+        @Nullable final Executor mExecutor;
+
+        ListenerWithExecutor(@Nullable Object listener, @Nullable Executor executor) {
+            mListener = listener;
+            mExecutor = executor;
+        }
+    }
+
+    private ListenerWithExecutor getListenerWithExecutor(int key) {
+        if (key == INVALID_KEY) return new ListenerWithExecutor(null, null);
         synchronized (mListenerMapLock) {
             Object listener = mListenerMap.get(key);
-            return listener;
+            Executor executor = mExecutorMap.get(key);
+            return new ListenerWithExecutor(listener, executor);
         }
     }
 
@@ -1388,6 +1430,7 @@
         synchronized (mListenerMapLock) {
             Object listener = mListenerMap.get(key);
             mListenerMap.remove(key);
+            mExecutorMap.remove(key);
             return listener;
         }
     }
@@ -1400,6 +1443,7 @@
         }
         synchronized (mListenerMapLock) {
             mListenerMap.remove(key);
+            mExecutorMap.remove(key);
             return key;
         }
     }
@@ -1458,7 +1502,8 @@
                     return;
             }
 
-            Object listener = getListener(msg.arg2);
+            ListenerWithExecutor listenerWithExecutor = getListenerWithExecutor(msg.arg2);
+            Object listener = listenerWithExecutor.mListener;
 
             if (listener == null) {
                 if (DBG) Log.d(TAG, "invalid listener key = " + msg.arg2);
@@ -1467,36 +1512,52 @@
                 if (DBG) Log.d(TAG, "listener key = " + msg.arg2);
             }
 
+            Executor executor = listenerWithExecutor.mExecutor;
+            if (executor == null) {
+                executor = new SynchronousExecutor();
+            }
+
             switch (msg.what) {
-                    /* ActionListeners grouped together */
-                case CMD_OP_SUCCEEDED :
-                    ((ActionListener) listener).onSuccess();
-                    break;
-                case CMD_OP_FAILED : {
-                        OperationResult result = (OperationResult)msg.obj;
-                        ((ActionListener) listener).onFailure(result.reason, result.description);
-                        removeListener(msg.arg2);
-                    }
-                    break;
-                case CMD_SCAN_RESULT :
-                    ((ScanListener) listener).onResults(
-                            ((ParcelableScanData) msg.obj).getResults());
-                    return;
-                case CMD_FULL_SCAN_RESULT :
+                /* ActionListeners grouped together */
+                case CMD_OP_SUCCEEDED: {
+                    ActionListener actionListener = (ActionListener) listener;
+                    Binder.clearCallingIdentity();
+                    executor.execute(actionListener::onSuccess);
+                } break;
+                case CMD_OP_FAILED: {
+                    OperationResult result = (OperationResult) msg.obj;
+                    ActionListener actionListener = (ActionListener) listener;
+                    removeListener(msg.arg2);
+                    Binder.clearCallingIdentity();
+                    executor.execute(() ->
+                            actionListener.onFailure(result.reason, result.description));
+                } break;
+                case CMD_SCAN_RESULT: {
+                    ScanListener scanListener = (ScanListener) listener;
+                    ParcelableScanData parcelableScanData = (ParcelableScanData) msg.obj;
+                    Binder.clearCallingIdentity();
+                    executor.execute(() -> scanListener.onResults(parcelableScanData.getResults()));
+                } break;
+                case CMD_FULL_SCAN_RESULT: {
                     ScanResult result = (ScanResult) msg.obj;
-                    ((ScanListener) listener).onFullResult(result);
-                    return;
-                case CMD_SINGLE_SCAN_COMPLETED:
+                    ScanListener scanListener = ((ScanListener) listener);
+                    Binder.clearCallingIdentity();
+                    executor.execute(() -> scanListener.onFullResult(result));
+                } break;
+                case CMD_SINGLE_SCAN_COMPLETED: {
                     if (DBG) Log.d(TAG, "removing listener for single scan");
                     removeListener(msg.arg2);
-                    break;
-                case CMD_PNO_NETWORK_FOUND:
-                    ((PnoScanListener) listener).onPnoNetworkFound(
-                            ((ParcelableScanResults) msg.obj).getResults());
-                    return;
-                default:
+                } break;
+                case CMD_PNO_NETWORK_FOUND: {
+                    PnoScanListener pnoScanListener = (PnoScanListener) listener;
+                    ParcelableScanResults parcelableScanResults = (ParcelableScanResults) msg.obj;
+                    Binder.clearCallingIdentity();
+                    executor.execute(() ->
+                            pnoScanListener.onPnoNetworkFound(parcelableScanResults.getResults()));
+                } break;
+                default: {
                     if (DBG) Log.d(TAG, "Ignoring message " + msg.what);
-                    return;
+                } break;
             }
         }
     }
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java b/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java
index 13b2520..98ec208 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java
@@ -286,14 +286,12 @@
     }
 
     /**
-     * Update device details. This will throw an exception if the device address does not match.
+     * Update this device's details using another {@link WifiP2pDevice} instance.
+     * This will throw an exception if the device address does not match.
      *
-     * @param device to be updated
+     * @param device another instance of {@link WifiP2pDevice} used to update this instance.
      * @throws IllegalArgumentException if the device is null or the device address does not match
-     *
-     * @hide
      */
-    @UnsupportedAppUsage
     public void update(@NonNull WifiP2pDevice device) {
         updateSupplicantDetails(device);
         status = device.status;
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index 8cdcba6..d326201 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -1437,15 +1437,6 @@
     }
 
     /**
-     * Defined for testing purpose.
-     */
-    class SynchronousExecutor implements Executor {
-        public void execute(Runnable r) {
-            r.run();
-        }
-    }
-
-    /**
      * Test behavior of isEnhancedOpenSupported
      */
     @Test
diff --git a/wifi/tests/src/android/net/wifi/WifiScannerTest.java b/wifi/tests/src/android/net/wifi/WifiScannerTest.java
index f4fa38b..b1436c90 100644
--- a/wifi/tests/src/android/net/wifi/WifiScannerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiScannerTest.java
@@ -22,7 +22,9 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.validateMockitoUsage;
@@ -33,6 +35,7 @@
 import android.net.wifi.WifiScanner.PnoSettings;
 import android.net.wifi.WifiScanner.PnoSettings.PnoNetwork;
 import android.net.wifi.WifiScanner.ScanData;
+import android.net.wifi.WifiScanner.ScanListener;
 import android.net.wifi.WifiScanner.ScanSettings;
 import android.os.Bundle;
 import android.os.Handler;
@@ -51,8 +54,10 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
 
 import java.util.Arrays;
+import java.util.concurrent.Executor;
 
 /**
  * Unit tests for {@link android.net.wifi.WifiScanner}.
@@ -63,6 +68,13 @@
     private Context mContext;
     @Mock
     private IWifiScanner mService;
+    @Spy
+    private Executor mExecutor = new SynchronousExecutor();
+    @Mock
+    private ScanListener mScanListener;
+    @Mock
+    private WifiScanner.ParcelableScanData mParcelableScanData;
+    private ScanData[] mScanData = {};
 
     private static final boolean TEST_PNOSETTINGS_IS_CONNECTED = false;
     private static final int TEST_PNOSETTINGS_MIN_5GHZ_RSSI = -60;
@@ -76,6 +88,7 @@
     private static final String TEST_SSID_2 = "TEST2";
     private static final int[] TEST_FREQUENCIES_1 = {};
     private static final int[] TEST_FREQUENCIES_2 = {2500, 5124};
+    private static final String DESCRIPTION_NOT_AUTHORIZED = "Not authorized";
 
     private WifiScanner mWifiScanner;
     private TestLooper mLooper;
@@ -95,6 +108,7 @@
         when(mService.getMessenger()).thenReturn(mBidirectionalAsyncChannelServer.getMessenger());
         mWifiScanner = new WifiScanner(mContext, mService, mLooper.getLooper());
         mLooper.dispatchAll();
+        when(mParcelableScanData.getResults()).thenReturn(mScanData);
     }
 
     /**
@@ -111,7 +125,7 @@
     @Test
     public void verifyScanSettingsParcelWithBand() throws Exception {
         ScanSettings writeSettings = new ScanSettings();
-        writeSettings.type = WifiScanner.TYPE_LOW_POWER;
+        writeSettings.type = WifiScanner.SCAN_TYPE_LOW_POWER;
         writeSettings.band = WifiScanner.WIFI_BAND_BOTH_WITH_DFS;
 
         ScanSettings readSettings = parcelWriteRead(writeSettings);
@@ -126,7 +140,7 @@
     @Test
     public void verifyScanSettingsParcelWithChannels() throws Exception {
         ScanSettings writeSettings = new ScanSettings();
-        writeSettings.type = WifiScanner.TYPE_HIGH_ACCURACY;
+        writeSettings.type = WifiScanner.SCAN_TYPE_HIGH_ACCURACY;
         writeSettings.band = WifiScanner.WIFI_BAND_UNSPECIFIED;
         writeSettings.channels = new WifiScanner.ChannelSpec[] {
                 new WifiScanner.ChannelSpec(5),
@@ -243,13 +257,13 @@
 
 
     /**
-     * Test behavior of {@link WifiScanner#startScan(ScanSettings, WifiScanner.ScanListener)}
+     * Test behavior of {@link WifiScanner#startScan(ScanSettings, ScanListener)}
      * @throws Exception
      */
     @Test
     public void testStartScan() throws Exception {
         ScanSettings scanSettings = new ScanSettings();
-        WifiScanner.ScanListener scanListener = mock(WifiScanner.ScanListener.class);
+        ScanListener scanListener = mock(ScanListener.class);
 
         mWifiScanner.startScan(scanSettings, scanListener);
         mLooper.dispatchAll();
@@ -273,13 +287,13 @@
     }
 
     /**
-     * Test behavior of {@link WifiScanner#stopScan(WifiScanner.ScanListener)}
+     * Test behavior of {@link WifiScanner#stopScan(ScanListener)}
      * @throws Exception
      */
     @Test
     public void testStopScan() throws Exception {
         ScanSettings scanSettings = new ScanSettings();
-        WifiScanner.ScanListener scanListener = mock(WifiScanner.ScanListener.class);
+        ScanListener scanListener = mock(ScanListener.class);
 
         mWifiScanner.startScan(scanSettings, scanListener);
         mLooper.dispatchAll();
@@ -302,13 +316,13 @@
     }
 
     /**
-     * Test behavior of {@link WifiScanner#startScan(ScanSettings, WifiScanner.ScanListener)}
+     * Test behavior of {@link WifiScanner#startScan(ScanSettings, ScanListener)}
      * @throws Exception
      */
     @Test
     public void testStartScanListenerOnSuccess() throws Exception {
         ScanSettings scanSettings = new ScanSettings();
-        WifiScanner.ScanListener scanListener = mock(WifiScanner.ScanListener.class);
+        ScanListener scanListener = mock(ScanListener.class);
 
         mWifiScanner.startScan(scanSettings, scanListener);
         mLooper.dispatchAll();
@@ -332,13 +346,13 @@
     }
 
     /**
-     * Test behavior of {@link WifiScanner#startScan(ScanSettings, WifiScanner.ScanListener)}
+     * Test behavior of {@link WifiScanner#startScan(ScanSettings, ScanListener)}
      * @throws Exception
      */
     @Test
     public void testStartScanListenerOnResults() throws Exception {
         ScanSettings scanSettings = new ScanSettings();
-        WifiScanner.ScanListener scanListener = mock(WifiScanner.ScanListener.class);
+        ScanListener scanListener = mock(ScanListener.class);
 
         mWifiScanner.startScan(scanSettings, scanListener);
         mLooper.dispatchAll();
@@ -425,7 +439,7 @@
     }
 
     /**
-     * Test behavior of {@link WifiScanner#stopPnoScan(WifiScanner.ScanListener)}
+     * Test behavior of {@link WifiScanner#stopPnoScan(ScanListener)}
      * WifiScanner.PnoScanListener)}
      * @throws Exception
      */
@@ -480,4 +494,134 @@
         assertEquals(scanData.getResults().length, readScanData.getResults().length);
         assertEquals(scanData.getResults()[0].SSID, readScanData.getResults()[0].SSID);
     }
+
+    /** Tests that upon registration success, {@link ScanListener#onSuccess()} is called. */
+    @Test
+    public void testRegisterScanListenerSuccess() throws Exception {
+        mWifiScanner.registerScanListener(mExecutor, mScanListener);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandler).handleMessage(messageArgumentCaptor.capture());
+        Message sentMessage = messageArgumentCaptor.getValue();
+        assertNotNull(sentMessage);
+
+        assertEquals(1, mBidirectionalAsyncChannelServer.getClientMessengers().size());
+        Messenger scannerMessenger =
+                mBidirectionalAsyncChannelServer.getClientMessengers().iterator().next();
+
+        Message responseMessage = Message.obtain();
+        responseMessage.what = WifiScanner.CMD_OP_SUCCEEDED;
+        responseMessage.arg2 = sentMessage.arg2;
+        scannerMessenger.send(responseMessage);
+        mLooper.dispatchAll();
+
+        verify(mExecutor).execute(any());
+        verify(mScanListener).onSuccess();
+    }
+
+    /**
+     * Tests that upon registration failed, {@link ScanListener#onFailure(int, String)} is called.
+     */
+    @Test
+    public void testRegisterScanListenerFailed() throws Exception {
+        mWifiScanner.registerScanListener(mExecutor, mScanListener);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandler).handleMessage(messageArgumentCaptor.capture());
+        Message sentMessage = messageArgumentCaptor.getValue();
+        assertNotNull(sentMessage);
+
+        assertEquals(1, mBidirectionalAsyncChannelServer.getClientMessengers().size());
+        Messenger scannerMessenger =
+                mBidirectionalAsyncChannelServer.getClientMessengers().iterator().next();
+
+        {
+            Message responseMessage = Message.obtain();
+            responseMessage.what = WifiScanner.CMD_OP_FAILED;
+            responseMessage.arg2 = sentMessage.arg2;
+            responseMessage.obj = new WifiScanner.OperationResult(
+                    WifiScanner.REASON_NOT_AUTHORIZED, DESCRIPTION_NOT_AUTHORIZED);
+            scannerMessenger.send(responseMessage);
+            mLooper.dispatchAll();
+        }
+
+        verify(mExecutor).execute(any());
+        verify(mScanListener).onFailure(
+                WifiScanner.REASON_NOT_AUTHORIZED, DESCRIPTION_NOT_AUTHORIZED);
+
+        // CMD_OP_FAILED should have caused the removal of the listener, verify this
+        {
+            Message responseMessage = Message.obtain();
+            responseMessage.what = WifiScanner.CMD_SCAN_RESULT;
+            responseMessage.arg2 = sentMessage.arg2;
+            responseMessage.obj = mParcelableScanData;
+            scannerMessenger.send(responseMessage);
+            mLooper.dispatchAll();
+        }
+        // execute() called once before, not called again
+        verify(mExecutor, times(1)).execute(any());
+        // onResults() never triggered
+        verify(mScanListener, never()).onResults(any());
+    }
+
+    /**
+     * Tests that when the ScanListener is triggered, {@link ScanListener#onResults(ScanData[])}
+     * is called.
+     */
+    @Test
+    public void testRegisterScanListenerReceiveScanResults() throws Exception {
+        mWifiScanner.registerScanListener(mExecutor, mScanListener);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandler).handleMessage(messageArgumentCaptor.capture());
+        Message sentMessage = messageArgumentCaptor.getValue();
+        assertNotNull(sentMessage);
+
+        assertEquals(1, mBidirectionalAsyncChannelServer.getClientMessengers().size());
+        Messenger scannerMessenger =
+                mBidirectionalAsyncChannelServer.getClientMessengers().iterator().next();
+
+        Message responseMessage = Message.obtain();
+        responseMessage.what = WifiScanner.CMD_SCAN_RESULT;
+        responseMessage.arg2 = sentMessage.arg2;
+        responseMessage.obj = mParcelableScanData;
+        scannerMessenger.send(responseMessage);
+        mLooper.dispatchAll();
+
+        verify(mExecutor).execute(any());
+        verify(mScanListener).onResults(mScanData);
+    }
+
+    /**
+     * Tests that after unregistering a scan listener, {@link ScanListener#onResults(ScanData[])}
+     * is not called.
+     */
+    @Test
+    public void testUnregisterScanListener() throws Exception {
+        mWifiScanner.registerScanListener(mExecutor, mScanListener);
+        mWifiScanner.unregisterScanListener(mScanListener);
+        mLooper.dispatchAll();
+
+        assertEquals(1, mBidirectionalAsyncChannelServer.getClientMessengers().size());
+        Messenger scannerMessenger =
+                mBidirectionalAsyncChannelServer.getClientMessengers().iterator().next();
+
+        ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandler, times(2)).handleMessage(messageArgumentCaptor.capture());
+        Message sentMessage = messageArgumentCaptor.getValue();
+        assertNotNull(sentMessage);
+
+        Message responseMessage = Message.obtain();
+        responseMessage.what = WifiScanner.CMD_SCAN_RESULT;
+        responseMessage.obj = mParcelableScanData;
+        responseMessage.arg2 = sentMessage.arg2;
+        scannerMessenger.send(responseMessage);
+        mLooper.dispatchAll();
+
+        verify(mExecutor, never()).execute(any());
+        verify(mScanListener, never()).onResults(mScanData);
+    }
 }