Merge "[SP19] Reduce log when set limit and request stats update"
diff --git a/Android.bp b/Android.bp
index 79a8a4b..84f3fad 100644
--- a/Android.bp
+++ b/Android.bp
@@ -368,6 +368,7 @@
         "devicepolicyprotosnano",
 
         "com.android.sysprop.apex",
+        "com.android.sysprop.init",
         "PlatformProperties",
     ],
     sdk_version: "core_platform",
@@ -396,7 +397,6 @@
         "ext",
         "unsupportedappusage",
         "updatable_media_stubs",
-        "framework-tethering",
     ],
 
     jarjar_rules: ":framework-jarjar-rules",
@@ -455,7 +455,9 @@
     name: "framework-minus-apex",
     defaults: ["framework-defaults"],
     srcs: [":framework-non-updatable-sources"],
-    libs: ["ike-stubs"],
+    libs: [
+        "framework-tethering-stubs",
+    ],
     installable: true,
     javac_shard_size: 150,
     required: [
@@ -471,6 +473,10 @@
     // For backwards compatibility.
     stem: "framework",
     apex_available: ["//apex_available:platform"],
+    visibility: [
+        // TODO(b/147128803) remove the below lines
+        "//frameworks/base/packages/Tethering/tests/unit",
+    ],
 }
 
 // This "framework" module is NOT installed to the device. It's
@@ -490,9 +496,7 @@
         "framework-minus-apex",
         "updatable_media_stubs",
         "framework-sdkextensions-stubs-systemapi",
-        // TODO(b/147200698): should be the stub of framework-tethering
-        "framework-tethering",
-        "ike-stubs",
+        "framework-tethering-stubs",
     ],
     sdk_version: "core_platform",
     apex_available: ["//apex_available:platform"],
@@ -608,7 +612,9 @@
         "core/java/android/annotation/Nullable.java",
         "core/java/android/annotation/IntDef.java",
         "core/java/android/annotation/IntRange.java",
+        "core/java/android/annotation/RequiresPermission.java",
         "core/java/android/annotation/SystemApi.java",
+        "core/java/android/annotation/TestApi.java",
         "core/java/com/android/internal/annotations/GuardedBy.java",
         "core/java/com/android/internal/annotations/VisibleForTesting.java",
     ],
@@ -671,17 +677,6 @@
     ],
 }
 
-filegroup {
-    name: "framework-tethering-annotations",
-    srcs: [
-        "core/java/android/annotation/NonNull.java",
-        "core/java/android/annotation/Nullable.java",
-        "core/java/android/annotation/RequiresPermission.java",
-        "core/java/android/annotation/SystemApi.java",
-        "core/java/android/annotation/TestApi.java",
-        "core/java/com/android/internal/annotations/GuardedBy.java",
-    ],
-}
 // Build ext.jar
 // ============================================================
 java_library {
@@ -915,22 +910,6 @@
     output_extension: "proto.h",
 }
 
-
-subdirs = [
-    "cmds/*",
-    "core/*",
-    "libs/*",
-    "media/*",
-    "proto",
-    "tools/*",
-    "native/android",
-    "native/graphics/jni",
-]
-
-optional_subdirs = [
-    "core/tests/utiltests/jni",
-]
-
 // TODO(b/77285514): remove this once the last few hidl interfaces have been
 // updated to use hwbinder.stubs.
 java_library {
@@ -990,13 +969,6 @@
 }
 
 filegroup {
-    name: "framework-annotation-nonnull-srcs",
-    srcs: [
-        "core/java/android/annotation/NonNull.java",
-    ],
-}
-
-filegroup {
     name: "framework-media-annotation-srcs",
     srcs: [
         "core/java/android/annotation/CallbackExecutor.java",
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 8abe64c..84b3625 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -49,7 +49,6 @@
         ":opt-net-voip-srcs",
         ":core-current-stubs-source",
         ":core_public_api_files",
-        ":ike-api-srcs",
     ],
     libs: ["framework-internal-utils"],
     installable: false,
@@ -193,20 +192,17 @@
             api_file: "api/module-lib-current.txt",
             removed_api_file: "api/module-lib-removed.txt",
         },
-        // TODO(b/147559833) enable the compatibility check against the last release API
-        // and the API lint
-        //last_released: {
-        //    api_file: ":last-released-module-lib-api",
-        //    removed_api_file: "api/module-lib-removed.txt",
-        //    baseline_file: ":module-lib-api-incompatibilities-with-last-released"
-        //},
-        //api_lint: {
-        //    enabled: true,
-        //    new_since: ":last-released-module-lib-api",
-        //    baseline_file: "api/module-lib-lint-baseline.txt",
-        //},
+        last_released: {
+            api_file: ":last-released-module-lib-api",
+            removed_api_file: "api/module-lib-removed.txt",
+            baseline_file: ":module-lib-api-incompatibilities-with-last-released"
+        },
+        api_lint: {
+            enabled: true,
+            new_since: ":last-released-module-lib-api",
+            baseline_file: "api/module-lib-lint-baseline.txt",
+        },
     },
-    //jdiff_enabled: true,
 }
 
 
diff --git a/apex/sdkextensions/Android.bp b/apex/sdkextensions/Android.bp
index 4c5c2b2..25765af 100644
--- a/apex/sdkextensions/Android.bp
+++ b/apex/sdkextensions/Android.bp
@@ -28,7 +28,6 @@
     name: "com.android.sdkext-defaults",
     java_libs: [ "framework-sdkextensions" ],
     prebuilts: [
-        "com.android.sdkext.ldconfig",
         "derive_sdk.rc",
     ],
     key: "com.android.sdkext.key",
@@ -51,13 +50,6 @@
     certificate: "com.android.sdkext",
 }
 
-prebuilt_etc {
-    name: "com.android.sdkext.ldconfig",
-    src: "ld.config.txt",
-    filename: "ld.config.txt",
-    installable: false,
-}
-
 python_binary_host {
     name: "gen_sdkinfo",
     srcs: [
diff --git a/apex/sdkextensions/ld.config.txt b/apex/sdkextensions/ld.config.txt
deleted file mode 100644
index dcc69b8..0000000
--- a/apex/sdkextensions/ld.config.txt
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright (C) 2019 The Android Open Source Project
-#
-# Bionic loader config file for the sdkextensions apex.
-
-dir.sdkextensions = /apex/com.android.sdkext/bin/
-
-[sdkextensions]
-additional.namespaces = platform
-
-namespace.default.isolated = true
-namespace.default.links = platform
-namespace.default.link.platform.allow_all_shared_libs = true
-
-###############################################################################
-# "platform" namespace: used for NDK libraries
-###############################################################################
-namespace.platform.isolated = true
-namespace.platform.search.paths = /system/${LIB}
-namespace.platform.asan.search.paths = /data/asan/system/${LIB}
-
-# /system/lib/libc.so, etc are symlinks to /apex/com.android.lib/lib/bionic/libc.so, etc.
-# Add /apex/... path to the permitted paths because linker uses realpath(3)
-# to check the accessibility of the lib. We could add this to search.paths
-# instead but that makes the resolution of bionic libs be dependent on
-# the order of /system/lib and /apex/... in search.paths. If /apex/...
-# is after /system/lib, then /apex/... is never tried because libc.so
-# is always found in /system/lib but fails to pass the accessibility test
-# because of its realpath.  It's better to not depend on the ordering if
-# possible.
-namespace.platform.permitted.paths = /apex/com.android.runtime/${LIB}/bionic
-namespace.platform.asan.permitted.paths = /apex/com.android.runtime/${LIB}/bionic
diff --git a/api/current.txt b/api/current.txt
index 37b7ebc..4118bcc 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -11775,6 +11775,7 @@
     field public static final String FEATURE_VR_HEADTRACKING = "android.hardware.vr.headtracking";
     field @Deprecated public static final String FEATURE_VR_MODE = "android.software.vr.mode";
     field public static final String FEATURE_VR_MODE_HIGH_PERFORMANCE = "android.hardware.vr.high_performance";
+    field public static final String FEATURE_VULKAN_DEQP_LEVEL = "android.software.vulkan.deqp.level";
     field public static final String FEATURE_VULKAN_HARDWARE_COMPUTE = "android.hardware.vulkan.compute";
     field public static final String FEATURE_VULKAN_HARDWARE_LEVEL = "android.hardware.vulkan.level";
     field public static final String FEATURE_VULKAN_HARDWARE_VERSION = "android.hardware.vulkan.version";
@@ -43835,7 +43836,7 @@
     method public final android.telecom.CallAudioState getCallAudioState();
     method public final String getCallerDisplayName();
     method public final int getCallerDisplayNamePresentation();
-    method public int getCallerNumberVerificationStatus();
+    method public final int getCallerNumberVerificationStatus();
     method public final android.telecom.Conference getConference();
     method public final java.util.List<android.telecom.Conferenceable> getConferenceables();
     method public final int getConnectionCapabilities();
@@ -43888,7 +43889,7 @@
     method public final void setAudioModeIsVoip(boolean);
     method public final void setAudioRoute(int);
     method public final void setCallerDisplayName(String, int);
-    method public void setCallerNumberVerificationStatus(int);
+    method public final void setCallerNumberVerificationStatus(int);
     method public final void setConferenceableConnections(java.util.List<android.telecom.Connection>);
     method public final void setConferenceables(java.util.List<android.telecom.Conferenceable>);
     method public final void setConnectionCapabilities(int);
@@ -44703,8 +44704,8 @@
     field public static final String KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL = "allow_add_call_during_video_call";
     field public static final String KEY_ALLOW_EMERGENCY_NUMBERS_IN_CALL_LOG_BOOL = "allow_emergency_numbers_in_call_log_bool";
     field public static final String KEY_ALLOW_EMERGENCY_VIDEO_CALLS_BOOL = "allow_emergency_video_calls_bool";
-    field public static final String KEY_ALLOW_HOLDING_VIDEO_CALL_BOOL = "allow_holding_video_call";
     field public static final String KEY_ALLOW_HOLD_CALL_DURING_EMERGENCY_BOOL = "allow_hold_call_during_emergency_bool";
+    field public static final String KEY_ALLOW_HOLD_VIDEO_CALL_BOOL = "allow_hold_video_call_bool";
     field public static final String KEY_ALLOW_LOCAL_DTMF_TONES_BOOL = "allow_local_dtmf_tones_bool";
     field public static final String KEY_ALLOW_MERGE_WIFI_CALLS_WHEN_VOWIFI_OFF_BOOL = "allow_merge_wifi_calls_when_vowifi_off_bool";
     field public static final String KEY_ALLOW_NON_EMERGENCY_CALLS_IN_ECM_BOOL = "allow_non_emergency_calls_in_ecm_bool";
diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt
index 2831924..1b72fc3 100644
--- a/api/module-lib-current.txt
+++ b/api/module-lib-current.txt
@@ -9,3 +9,123 @@
 
 }
 
+package android.net {
+
+  public final class TetheredClient implements android.os.Parcelable {
+    ctor public TetheredClient(@NonNull android.net.MacAddress, @NonNull java.util.Collection<android.net.TetheredClient.AddressInfo>, int);
+    method public int describeContents();
+    method @NonNull public java.util.List<android.net.TetheredClient.AddressInfo> getAddresses();
+    method @NonNull public android.net.MacAddress getMacAddress();
+    method public int getTetheringType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.TetheredClient> CREATOR;
+  }
+
+  public static final class TetheredClient.AddressInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.net.LinkAddress getAddress();
+    method @Nullable public String getHostname();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.TetheredClient.AddressInfo> CREATOR;
+  }
+
+  public class TetheringConstants {
+    field public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType";
+    field public static final String EXTRA_PROVISION_CALLBACK = "extraProvisionCallback";
+    field public static final String EXTRA_REM_TETHER_TYPE = "extraRemTetherType";
+    field public static final String EXTRA_RUN_PROVISION = "extraRunProvision";
+    field public static final String EXTRA_SET_ALARM = "extraSetAlarm";
+  }
+
+  public class TetheringManager {
+    ctor public TetheringManager(@NonNull android.content.Context, @NonNull java.util.function.Supplier<android.os.IBinder>);
+    method public int getLastTetherError(@NonNull String);
+    method @NonNull public String[] getTetherableBluetoothRegexs();
+    method @NonNull public String[] getTetherableIfaces();
+    method @NonNull public String[] getTetherableUsbRegexs();
+    method @NonNull public String[] getTetherableWifiRegexs();
+    method @NonNull public String[] getTetheredIfaces();
+    method @NonNull public String[] getTetheringErroredIfaces();
+    method public boolean isTetheringSupported();
+    method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.TetheringEventCallback);
+    method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void requestLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.OnTetheringEntitlementResultListener);
+    method public void requestLatestTetheringEntitlementResult(int, @NonNull android.os.ResultReceiver, boolean);
+    method @Deprecated public int setUsbTethering(boolean);
+    method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void startTethering(@NonNull android.net.TetheringManager.TetheringRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.StartTetheringCallback);
+    method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void startTethering(int, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.StartTetheringCallback);
+    method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void stopAllTethering();
+    method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void stopTethering(int);
+    method @Deprecated public int tether(@NonNull String);
+    method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.ACCESS_NETWORK_STATE}) public void unregisterTetheringEventCallback(@NonNull android.net.TetheringManager.TetheringEventCallback);
+    method @Deprecated public int untether(@NonNull String);
+    field public static final String ACTION_TETHER_STATE_CHANGED = "android.net.conn.TETHER_STATE_CHANGED";
+    field public static final String EXTRA_ACTIVE_LOCAL_ONLY = "android.net.extra.ACTIVE_LOCAL_ONLY";
+    field public static final String EXTRA_ACTIVE_TETHER = "tetherArray";
+    field public static final String EXTRA_AVAILABLE_TETHER = "availableArray";
+    field public static final String EXTRA_ERRORED_TETHER = "erroredArray";
+    field public static final int TETHERING_BLUETOOTH = 2; // 0x2
+    field public static final int TETHERING_ETHERNET = 5; // 0x5
+    field public static final int TETHERING_INVALID = -1; // 0xffffffff
+    field public static final int TETHERING_NCM = 4; // 0x4
+    field public static final int TETHERING_USB = 1; // 0x1
+    field public static final int TETHERING_WIFI = 0; // 0x0
+    field public static final int TETHERING_WIFI_P2P = 3; // 0x3
+    field public static final int TETHER_ERROR_DHCPSERVER_ERROR = 12; // 0xc
+    field public static final int TETHER_ERROR_DISABLE_NAT_ERROR = 9; // 0x9
+    field public static final int TETHER_ERROR_ENABLE_NAT_ERROR = 8; // 0x8
+    field public static final int TETHER_ERROR_ENTITLEMENT_UNKNOWN = 13; // 0xd
+    field public static final int TETHER_ERROR_IFACE_CFG_ERROR = 10; // 0xa
+    field public static final int TETHER_ERROR_MASTER_ERROR = 5; // 0x5
+    field public static final int TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION = 15; // 0xf
+    field public static final int TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION = 14; // 0xe
+    field public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0
+    field public static final int TETHER_ERROR_PROVISION_FAILED = 11; // 0xb
+    field public static final int TETHER_ERROR_SERVICE_UNAVAIL = 2; // 0x2
+    field public static final int TETHER_ERROR_TETHER_IFACE_ERROR = 6; // 0x6
+    field public static final int TETHER_ERROR_UNAVAIL_IFACE = 4; // 0x4
+    field public static final int TETHER_ERROR_UNKNOWN_IFACE = 1; // 0x1
+    field public static final int TETHER_ERROR_UNSUPPORTED = 3; // 0x3
+    field public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = 7; // 0x7
+  }
+
+  public static interface TetheringManager.OnTetheringEntitlementResultListener {
+    method public void onTetheringEntitlementResult(int);
+  }
+
+  public abstract static class TetheringManager.StartTetheringCallback {
+    ctor public TetheringManager.StartTetheringCallback();
+    method public void onTetheringFailed(int);
+    method public void onTetheringStarted();
+  }
+
+  public abstract static class TetheringManager.TetheringEventCallback {
+    ctor public TetheringManager.TetheringEventCallback();
+    method public void onClientsChanged(@NonNull java.util.Collection<android.net.TetheredClient>);
+    method public void onError(@NonNull String, int);
+    method @Deprecated public void onTetherableInterfaceRegexpsChanged(@NonNull android.net.TetheringManager.TetheringInterfaceRegexps);
+    method public void onTetherableInterfacesChanged(@NonNull java.util.List<java.lang.String>);
+    method public void onTetheredInterfacesChanged(@NonNull java.util.List<java.lang.String>);
+    method public void onTetheringSupported(boolean);
+    method public void onUpstreamChanged(@Nullable android.net.Network);
+  }
+
+  @Deprecated public static class TetheringManager.TetheringInterfaceRegexps {
+    ctor @Deprecated public TetheringManager.TetheringInterfaceRegexps(@NonNull String[], @NonNull String[], @NonNull String[]);
+    method @Deprecated @NonNull public java.util.List<java.lang.String> getTetherableBluetoothRegexs();
+    method @Deprecated @NonNull public java.util.List<java.lang.String> getTetherableUsbRegexs();
+    method @Deprecated @NonNull public java.util.List<java.lang.String> getTetherableWifiRegexs();
+  }
+
+  public static class TetheringManager.TetheringRequest {
+  }
+
+  public static class TetheringManager.TetheringRequest.Builder {
+    ctor public TetheringManager.TetheringRequest.Builder(int);
+    method @NonNull public android.net.TetheringManager.TetheringRequest build();
+    method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder setExemptFromEntitlementCheck(boolean);
+    method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder setSilentProvisioning(boolean);
+    method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder useStaticIpv4Addresses(@NonNull android.net.LinkAddress);
+  }
+
+}
+
diff --git a/api/module-lib-lint-baseline.txt b/api/module-lib-lint-baseline.txt
new file mode 100644
index 0000000..56f7a02
--- /dev/null
+++ b/api/module-lib-lint-baseline.txt
@@ -0,0 +1,29 @@
+// Baseline format: 1.0
+ActionValue: android.net.TetheringConstants#EXTRA_ADD_TETHER_TYPE:
+    Inconsistent extra value; expected `android.net.extra.ADD_TETHER_TYPE`, was `extraAddTetherType`
+ActionValue: android.net.TetheringConstants#EXTRA_PROVISION_CALLBACK:
+    Inconsistent extra value; expected `android.net.extra.PROVISION_CALLBACK`, was `extraProvisionCallback`
+ActionValue: android.net.TetheringConstants#EXTRA_REM_TETHER_TYPE:
+    Inconsistent extra value; expected `android.net.extra.REM_TETHER_TYPE`, was `extraRemTetherType`
+ActionValue: android.net.TetheringConstants#EXTRA_RUN_PROVISION:
+    Inconsistent extra value; expected `android.net.extra.RUN_PROVISION`, was `extraRunProvision`
+ActionValue: android.net.TetheringConstants#EXTRA_SET_ALARM:
+    Inconsistent extra value; expected `android.net.extra.SET_ALARM`, was `extraSetAlarm`
+ActionValue: android.net.TetheringManager#ACTION_TETHER_STATE_CHANGED:
+    Inconsistent action value; expected `android.net.action.TETHER_STATE_CHANGED`, was `android.net.conn.TETHER_STATE_CHANGED`
+ActionValue: android.net.TetheringManager#EXTRA_ACTIVE_TETHER:
+    Inconsistent extra value; expected `android.net.extra.ACTIVE_TETHER`, was `tetherArray`
+ActionValue: android.net.TetheringManager#EXTRA_AVAILABLE_TETHER:
+    Inconsistent extra value; expected `android.net.extra.AVAILABLE_TETHER`, was `availableArray`
+ActionValue: android.net.TetheringManager#EXTRA_ERRORED_TETHER:
+    Inconsistent extra value; expected `android.net.extra.ERRORED_TETHER`, was `erroredArray`
+
+
+ManagerConstructor: android.net.TetheringManager#TetheringManager(android.content.Context, java.util.function.Supplier<android.os.IBinder>):
+    Managers must always be obtained from Context; no direct constructors
+
+
+PrivateSuperclass: android.location.GnssAntennaInfo.PhaseCenterVariationCorrections:
+    Public class android.location.GnssAntennaInfo.PhaseCenterVariationCorrections extends private class android.location.GnssAntennaInfo.SphericalCorrections
+PrivateSuperclass: android.location.GnssAntennaInfo.SignalGainCorrections:
+    Public class android.location.GnssAntennaInfo.SignalGainCorrections extends private class android.location.GnssAntennaInfo.SphericalCorrections
diff --git a/api/system-current.txt b/api/system-current.txt
index 971ea08..f0e167e 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5001,337 +5001,6 @@
 
 }
 
-package android.net.eap {
-
-  public final class EapSessionConfig {
-  }
-
-  public static final class EapSessionConfig.Builder {
-    ctor public EapSessionConfig.Builder();
-    method @NonNull public android.net.eap.EapSessionConfig build();
-    method @NonNull public android.net.eap.EapSessionConfig.Builder setEapAkaConfig(int, int);
-    method @NonNull public android.net.eap.EapSessionConfig.Builder setEapAkaPrimeConfig(int, int, @NonNull String, boolean);
-    method @NonNull public android.net.eap.EapSessionConfig.Builder setEapIdentity(@NonNull byte[]);
-    method @NonNull public android.net.eap.EapSessionConfig.Builder setEapMsChapV2Config(@NonNull String, @NonNull String);
-    method @NonNull public android.net.eap.EapSessionConfig.Builder setEapSimConfig(int, int);
-  }
-
-  public static class EapSessionConfig.EapAkaConfig extends android.net.eap.EapSessionConfig.EapUiccConfig {
-  }
-
-  public static class EapSessionConfig.EapAkaPrimeConfig extends android.net.eap.EapSessionConfig.EapAkaConfig {
-    method public boolean allowsMismatchedNetworkNames();
-    method @NonNull public String getNetworkName();
-  }
-
-  public abstract static class EapSessionConfig.EapMethodConfig {
-    method public int getMethodType();
-  }
-
-  public static class EapSessionConfig.EapMsChapV2Config extends android.net.eap.EapSessionConfig.EapMethodConfig {
-    method @NonNull public String getPassword();
-    method @NonNull public String getUsername();
-  }
-
-  public static class EapSessionConfig.EapSimConfig extends android.net.eap.EapSessionConfig.EapUiccConfig {
-  }
-
-  public abstract static class EapSessionConfig.EapUiccConfig extends android.net.eap.EapSessionConfig.EapMethodConfig {
-    method public int getAppType();
-    method public int getSubId();
-  }
-
-}
-
-package android.net.ipsec.ike {
-
-  public final class ChildSaProposal extends android.net.ipsec.ike.SaProposal {
-  }
-
-  public static final class ChildSaProposal.Builder {
-    ctor public ChildSaProposal.Builder();
-    method @NonNull public android.net.ipsec.ike.ChildSaProposal.Builder addDhGroup(int);
-    method @NonNull public android.net.ipsec.ike.ChildSaProposal.Builder addEncryptionAlgorithm(int, int);
-    method @NonNull public android.net.ipsec.ike.ChildSaProposal.Builder addIntegrityAlgorithm(int);
-    method @NonNull public android.net.ipsec.ike.ChildSaProposal build();
-  }
-
-  public interface ChildSessionCallback {
-    method public void onClosed();
-    method public void onClosedExceptionally(@NonNull android.net.ipsec.ike.exceptions.IkeException);
-    method public void onIpSecTransformCreated(@NonNull android.net.IpSecTransform, int);
-    method public void onIpSecTransformDeleted(@NonNull android.net.IpSecTransform, int);
-    method public void onOpened(@NonNull android.net.ipsec.ike.ChildSessionConfiguration);
-  }
-
-  public final class ChildSessionConfiguration {
-    method @NonNull public java.util.List<android.net.ipsec.ike.IkeTrafficSelector> getInboundTrafficSelectors();
-    method @NonNull public java.util.List<android.net.LinkAddress> getInternalAddresses();
-    method @NonNull public java.util.List<java.net.InetAddress> getInternalDhcpServers();
-    method @NonNull public java.util.List<java.net.InetAddress> getInternalDnsServers();
-    method @NonNull public java.util.List<android.net.IpPrefix> getInternalSubnets();
-    method @NonNull public java.util.List<android.net.ipsec.ike.IkeTrafficSelector> getOutboundTrafficSelectors();
-  }
-
-  public abstract class ChildSessionParams {
-    method public long getHardLifetime();
-    method @NonNull public java.util.List<android.net.ipsec.ike.IkeTrafficSelector> getLocalTrafficSelectors();
-    method @NonNull public java.util.List<android.net.ipsec.ike.IkeTrafficSelector> getRemoteTrafficSelectors();
-    method @NonNull public java.util.List<android.net.ipsec.ike.ChildSaProposal> getSaProposals();
-    method public long getSoftLifetime();
-  }
-
-  public class IkeFqdnIdentification extends android.net.ipsec.ike.IkeIdentification {
-    ctor public IkeFqdnIdentification(@NonNull String);
-    field @NonNull public final String fqdn;
-  }
-
-  public abstract class IkeIdentification {
-  }
-
-  public final class IkeIpv4AddrIdentification extends android.net.ipsec.ike.IkeIdentification {
-    ctor public IkeIpv4AddrIdentification(@NonNull java.net.Inet4Address);
-    field @NonNull public final java.net.Inet4Address ipv4Address;
-  }
-
-  public class IkeIpv6AddrIdentification extends android.net.ipsec.ike.IkeIdentification {
-    ctor public IkeIpv6AddrIdentification(@NonNull java.net.Inet6Address);
-    field @NonNull public final java.net.Inet6Address ipv6Address;
-  }
-
-  public final class IkeKeyIdIdentification extends android.net.ipsec.ike.IkeIdentification {
-    ctor public IkeKeyIdIdentification(@NonNull byte[]);
-    field @NonNull public final byte[] keyId;
-  }
-
-  public final class IkeRfc822AddrIdentification extends android.net.ipsec.ike.IkeIdentification {
-    ctor public IkeRfc822AddrIdentification(@NonNull String);
-    field @NonNull public final String rfc822Name;
-  }
-
-  public final class IkeSaProposal extends android.net.ipsec.ike.SaProposal {
-    method @NonNull public java.util.List<java.lang.Integer> getPseudorandomFunctions();
-  }
-
-  public static final class IkeSaProposal.Builder {
-    ctor public IkeSaProposal.Builder();
-    method @NonNull public android.net.ipsec.ike.IkeSaProposal.Builder addDhGroup(int);
-    method @NonNull public android.net.ipsec.ike.IkeSaProposal.Builder addEncryptionAlgorithm(int, int);
-    method @NonNull public android.net.ipsec.ike.IkeSaProposal.Builder addIntegrityAlgorithm(int);
-    method @NonNull public android.net.ipsec.ike.IkeSaProposal.Builder addPseudorandomFunction(int);
-    method @NonNull public android.net.ipsec.ike.IkeSaProposal build();
-  }
-
-  public final class IkeSession implements java.lang.AutoCloseable {
-    ctor public IkeSession(@NonNull android.content.Context, @NonNull android.net.ipsec.ike.IkeSessionParams, @NonNull android.net.ipsec.ike.ChildSessionParams, @NonNull java.util.concurrent.Executor, @NonNull android.net.ipsec.ike.IkeSessionCallback, @NonNull android.net.ipsec.ike.ChildSessionCallback);
-    method public void close();
-    method public void closeChildSession(@NonNull android.net.ipsec.ike.ChildSessionCallback);
-    method public void kill();
-    method public void openChildSession(@NonNull android.net.ipsec.ike.ChildSessionParams, @NonNull android.net.ipsec.ike.ChildSessionCallback);
-  }
-
-  public interface IkeSessionCallback {
-    method public void onClosed();
-    method public void onClosedExceptionally(@NonNull android.net.ipsec.ike.exceptions.IkeException);
-    method public void onError(@NonNull android.net.ipsec.ike.exceptions.IkeProtocolException);
-    method public void onOpened(@NonNull android.net.ipsec.ike.IkeSessionConfiguration);
-  }
-
-  public final class IkeSessionConfiguration {
-    method @NonNull public java.util.List<java.net.InetAddress> getPcscfServers();
-    method @NonNull public String getRemoteApplicationVersion();
-    method @NonNull public java.util.List<byte[]> getRemoteVendorIDs();
-    method public boolean isIkeExtensionEnabled(int);
-    field public static final int EXTENSION_TYPE_FRAGMENTATION = 1; // 0x1
-    field public static final int EXTENSION_TYPE_MOBIKE = 2; // 0x2
-  }
-
-  public final class IkeSessionParams {
-    method @NonNull public java.util.List<android.net.ipsec.ike.IkeSessionParams.IkeConfigRequest> getConfigurationRequests();
-    method public long getHardLifetime();
-    method @NonNull public android.net.ipsec.ike.IkeSessionParams.IkeAuthConfig getLocalAuthConfig();
-    method @NonNull public android.net.ipsec.ike.IkeIdentification getLocalIdentification();
-    method @NonNull public android.net.ipsec.ike.IkeSessionParams.IkeAuthConfig getRemoteAuthConfig();
-    method @NonNull public android.net.ipsec.ike.IkeIdentification getRemoteIdentification();
-    method @NonNull public java.util.List<android.net.ipsec.ike.IkeSaProposal> getSaProposals();
-    method @NonNull public java.net.InetAddress getServerAddress();
-    method public long getSoftLifetime();
-    method @NonNull public android.net.IpSecManager.UdpEncapsulationSocket getUdpEncapsulationSocket();
-  }
-
-  public static final class IkeSessionParams.Builder {
-    ctor public IkeSessionParams.Builder();
-    method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder addPcscfServerRequest(@NonNull java.net.InetAddress);
-    method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder addPcscfServerRequest(int);
-    method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder addSaProposal(@NonNull android.net.ipsec.ike.IkeSaProposal);
-    method @NonNull public android.net.ipsec.ike.IkeSessionParams build();
-    method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setAuthDigitalSignature(@Nullable java.security.cert.X509Certificate, @NonNull java.security.cert.X509Certificate, @NonNull java.security.PrivateKey);
-    method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setAuthDigitalSignature(@Nullable java.security.cert.X509Certificate, @NonNull java.security.cert.X509Certificate, @NonNull java.util.List<java.security.cert.X509Certificate>, @NonNull java.security.PrivateKey);
-    method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setAuthEap(@Nullable java.security.cert.X509Certificate, @NonNull android.net.eap.EapSessionConfig);
-    method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setAuthPsk(@NonNull byte[]);
-    method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setLifetime(long, long);
-    method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setLocalIdentification(@NonNull android.net.ipsec.ike.IkeIdentification);
-    method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setRemoteIdentification(@NonNull android.net.ipsec.ike.IkeIdentification);
-    method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setServerAddress(@NonNull java.net.InetAddress);
-    method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setUdpEncapsulationSocket(@NonNull android.net.IpSecManager.UdpEncapsulationSocket);
-  }
-
-  public static interface IkeSessionParams.ConfigRequestIpv4PcscfServer extends android.net.ipsec.ike.IkeSessionParams.IkeConfigRequest {
-    method @Nullable public java.net.Inet4Address getAddress();
-  }
-
-  public static interface IkeSessionParams.ConfigRequestIpv6PcscfServer extends android.net.ipsec.ike.IkeSessionParams.IkeConfigRequest {
-    method @Nullable public java.net.Inet6Address getAddress();
-  }
-
-  public abstract static class IkeSessionParams.IkeAuthConfig {
-  }
-
-  public static class IkeSessionParams.IkeAuthDigitalSignLocalConfig extends android.net.ipsec.ike.IkeSessionParams.IkeAuthConfig {
-    method @NonNull public java.security.cert.X509Certificate getClientEndCertificate();
-    method @NonNull public java.util.List<java.security.cert.X509Certificate> getIntermediateCertificates();
-    method @NonNull public java.security.PrivateKey getPrivateKey();
-  }
-
-  public static class IkeSessionParams.IkeAuthDigitalSignRemoteConfig extends android.net.ipsec.ike.IkeSessionParams.IkeAuthConfig {
-    method @Nullable public java.security.cert.X509Certificate getRemoteCaCert();
-  }
-
-  public static class IkeSessionParams.IkeAuthEapConfig extends android.net.ipsec.ike.IkeSessionParams.IkeAuthConfig {
-    method @NonNull public android.net.eap.EapSessionConfig getEapConfig();
-  }
-
-  public static class IkeSessionParams.IkeAuthPskConfig extends android.net.ipsec.ike.IkeSessionParams.IkeAuthConfig {
-    method @NonNull public byte[] getPsk();
-  }
-
-  public static interface IkeSessionParams.IkeConfigRequest {
-  }
-
-  public final class IkeTrafficSelector {
-    ctor public IkeTrafficSelector(int, int, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress);
-    field public final int endPort;
-    field @NonNull public final java.net.InetAddress endingAddress;
-    field public final int startPort;
-    field @NonNull public final java.net.InetAddress startingAddress;
-  }
-
-  public abstract class SaProposal {
-    method @NonNull public java.util.List<java.lang.Integer> getDhGroups();
-    method @NonNull public java.util.List<android.util.Pair<java.lang.Integer,java.lang.Integer>> getEncryptionAlgorithms();
-    method @NonNull public java.util.List<java.lang.Integer> getIntegrityAlgorithms();
-    field public static final int DH_GROUP_1024_BIT_MODP = 2; // 0x2
-    field public static final int DH_GROUP_2048_BIT_MODP = 14; // 0xe
-    field public static final int DH_GROUP_NONE = 0; // 0x0
-    field public static final int ENCRYPTION_ALGORITHM_3DES = 3; // 0x3
-    field public static final int ENCRYPTION_ALGORITHM_AES_CBC = 12; // 0xc
-    field public static final int ENCRYPTION_ALGORITHM_AES_GCM_12 = 19; // 0x13
-    field public static final int ENCRYPTION_ALGORITHM_AES_GCM_16 = 20; // 0x14
-    field public static final int ENCRYPTION_ALGORITHM_AES_GCM_8 = 18; // 0x12
-    field public static final int INTEGRITY_ALGORITHM_AES_XCBC_96 = 5; // 0x5
-    field public static final int INTEGRITY_ALGORITHM_HMAC_SHA1_96 = 2; // 0x2
-    field public static final int INTEGRITY_ALGORITHM_HMAC_SHA2_256_128 = 12; // 0xc
-    field public static final int INTEGRITY_ALGORITHM_HMAC_SHA2_384_192 = 13; // 0xd
-    field public static final int INTEGRITY_ALGORITHM_HMAC_SHA2_512_256 = 14; // 0xe
-    field public static final int INTEGRITY_ALGORITHM_NONE = 0; // 0x0
-    field public static final int KEY_LEN_AES_128 = 128; // 0x80
-    field public static final int KEY_LEN_AES_192 = 192; // 0xc0
-    field public static final int KEY_LEN_AES_256 = 256; // 0x100
-    field public static final int KEY_LEN_UNUSED = 0; // 0x0
-    field public static final int PSEUDORANDOM_FUNCTION_AES128_XCBC = 4; // 0x4
-    field public static final int PSEUDORANDOM_FUNCTION_HMAC_SHA1 = 2; // 0x2
-  }
-
-  public final class TransportModeChildSessionParams extends android.net.ipsec.ike.ChildSessionParams {
-  }
-
-  public static final class TransportModeChildSessionParams.Builder {
-    ctor public TransportModeChildSessionParams.Builder();
-    method @NonNull public android.net.ipsec.ike.TransportModeChildSessionParams.Builder addInboundTrafficSelectors(@NonNull android.net.ipsec.ike.IkeTrafficSelector);
-    method @NonNull public android.net.ipsec.ike.TransportModeChildSessionParams.Builder addOutboundTrafficSelectors(@NonNull android.net.ipsec.ike.IkeTrafficSelector);
-    method @NonNull public android.net.ipsec.ike.TransportModeChildSessionParams.Builder addSaProposal(@NonNull android.net.ipsec.ike.ChildSaProposal);
-    method @NonNull public android.net.ipsec.ike.TransportModeChildSessionParams build();
-    method @NonNull public android.net.ipsec.ike.TransportModeChildSessionParams.Builder setLifetime(long, long);
-  }
-
-  public final class TunnelModeChildSessionParams extends android.net.ipsec.ike.ChildSessionParams {
-    method @NonNull public java.util.List<android.net.ipsec.ike.TunnelModeChildSessionParams.TunnelModeChildConfigRequest> getConfigurationRequests();
-  }
-
-  public static final class TunnelModeChildSessionParams.Builder {
-    ctor public TunnelModeChildSessionParams.Builder();
-    method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInboundTrafficSelectors(@NonNull android.net.ipsec.ike.IkeTrafficSelector);
-    method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalAddressRequest(int);
-    method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalAddressRequest(@NonNull java.net.Inet4Address);
-    method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalAddressRequest(@NonNull java.net.Inet6Address, int);
-    method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalDhcpServerRequest(int);
-    method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalDnsServerRequest(int);
-    method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addOutboundTrafficSelectors(@NonNull android.net.ipsec.ike.IkeTrafficSelector);
-    method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addSaProposal(@NonNull android.net.ipsec.ike.ChildSaProposal);
-    method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams build();
-    method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder setLifetime(long, long);
-  }
-
-  public static interface TunnelModeChildSessionParams.ConfigRequestIpv4Address extends android.net.ipsec.ike.TunnelModeChildSessionParams.TunnelModeChildConfigRequest {
-    method @Nullable public java.net.Inet4Address getAddress();
-  }
-
-  public static interface TunnelModeChildSessionParams.ConfigRequestIpv4DhcpServer extends android.net.ipsec.ike.TunnelModeChildSessionParams.TunnelModeChildConfigRequest {
-    method @Nullable public java.net.Inet4Address getAddress();
-  }
-
-  public static interface TunnelModeChildSessionParams.ConfigRequestIpv4DnsServer extends android.net.ipsec.ike.TunnelModeChildSessionParams.TunnelModeChildConfigRequest {
-    method @Nullable public java.net.Inet4Address getAddress();
-  }
-
-  public static interface TunnelModeChildSessionParams.ConfigRequestIpv4Netmask extends android.net.ipsec.ike.TunnelModeChildSessionParams.TunnelModeChildConfigRequest {
-  }
-
-  public static interface TunnelModeChildSessionParams.ConfigRequestIpv6Address extends android.net.ipsec.ike.TunnelModeChildSessionParams.TunnelModeChildConfigRequest {
-    method @Nullable public java.net.Inet6Address getAddress();
-    method public int getPrefixLength();
-  }
-
-  public static interface TunnelModeChildSessionParams.ConfigRequestIpv6DnsServer extends android.net.ipsec.ike.TunnelModeChildSessionParams.TunnelModeChildConfigRequest {
-    method @Nullable public java.net.Inet6Address getAddress();
-  }
-
-  public static interface TunnelModeChildSessionParams.TunnelModeChildConfigRequest {
-  }
-
-}
-
-package android.net.ipsec.ike.exceptions {
-
-  public abstract class IkeException extends java.lang.Exception {
-  }
-
-  public final class IkeInternalException extends android.net.ipsec.ike.exceptions.IkeException {
-  }
-
-  public abstract class IkeProtocolException extends android.net.ipsec.ike.exceptions.IkeException {
-    method @Nullable public byte[] getErrorData();
-    method public int getErrorType();
-    field public static final int ERROR_TYPE_AUTHENTICATION_FAILED = 24; // 0x18
-    field public static final int ERROR_TYPE_CHILD_SA_NOT_FOUND = 44; // 0x2c
-    field public static final int ERROR_TYPE_FAILED_CP_REQUIRED = 37; // 0x25
-    field public static final int ERROR_TYPE_INTERNAL_ADDRESS_FAILURE = 36; // 0x24
-    field public static final int ERROR_TYPE_INVALID_IKE_SPI = 4; // 0x4
-    field public static final int ERROR_TYPE_INVALID_KE_PAYLOAD = 17; // 0x11
-    field public static final int ERROR_TYPE_INVALID_MAJOR_VERSION = 5; // 0x5
-    field public static final int ERROR_TYPE_INVALID_MESSAGE_ID = 9; // 0x9
-    field public static final int ERROR_TYPE_INVALID_SELECTORS = 39; // 0x27
-    field public static final int ERROR_TYPE_INVALID_SYNTAX = 7; // 0x7
-    field public static final int ERROR_TYPE_NO_ADDITIONAL_SAS = 35; // 0x23
-    field public static final int ERROR_TYPE_NO_PROPOSAL_CHOSEN = 14; // 0xe
-    field public static final int ERROR_TYPE_SINGLE_PAIR_REQUIRED = 34; // 0x22
-    field public static final int ERROR_TYPE_TEMPORARY_FAILURE = 43; // 0x2b
-    field public static final int ERROR_TYPE_TS_UNACCEPTABLE = 38; // 0x26
-    field public static final int ERROR_TYPE_UNSUPPORTED_CRITICAL_PAYLOAD = 1; // 0x1
-  }
-
-}
-
 package android.net.metrics {
 
   public final class ApfProgramEvent implements android.net.metrics.IpConnectivityLog.Event {
@@ -9423,6 +9092,7 @@
     method public void fillInNotifierBundle(@NonNull android.os.Bundle);
     method public int getDataNetworkType();
     method public int getDataRegistrationState();
+    method public boolean getDataRoamingFromRegistration();
     method @Nullable public android.telephony.NetworkRegistrationInfo getNetworkRegistrationInfo(int, int);
     method @NonNull public java.util.List<android.telephony.NetworkRegistrationInfo> getNetworkRegistrationInfoListForDomain(int);
     method @NonNull public java.util.List<android.telephony.NetworkRegistrationInfo> getNetworkRegistrationInfoListForTransportType(int);
@@ -9714,6 +9384,7 @@
     method public boolean isDataConnectivityPossible();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isDataEnabledForApn(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isEmergencyAssistanceEnabled();
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isGlobalModeEnabled();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean isIccLockEnabled();
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isIdle();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isInEmergencySmsMode();
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index c9e026b..af4f675 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -320,11 +320,6 @@
                                mAnomalyAlarmMonitor, mPeriodicAlarmMonitor);
     if (newMetricsManager->isConfigValid()) {
         mUidMap->OnConfigUpdated(key);
-        if (newMetricsManager->shouldAddUidMapListener()) {
-            // We have to add listener after the MetricsManager is constructed because it's
-            // not safe to create wp or sp from this pointer inside its constructor.
-            mUidMap->addListener(newMetricsManager.get());
-        }
         newMetricsManager->refreshTtl(timestampNs);
         mMetricsManagers[key] = newMetricsManager;
         VLOG("StatsdConfig valid");
@@ -743,6 +738,32 @@
     }
 }
 
+void StatsLogProcessor::notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk,
+                                         const int uid, const int64_t version) {
+    std::lock_guard<std::mutex> lock(mMetricsMutex);
+    ALOGW("Received app upgrade");
+    for (auto it : mMetricsManagers) {
+        it.second->notifyAppUpgrade(eventTimeNs, apk, uid, version);
+    }
+}
+
+void StatsLogProcessor::notifyAppRemoved(const int64_t& eventTimeNs, const string& apk,
+                                         const int uid) {
+    std::lock_guard<std::mutex> lock(mMetricsMutex);
+    ALOGW("Received app removed");
+    for (auto it : mMetricsManagers) {
+        it.second->notifyAppRemoved(eventTimeNs, apk, uid);
+    }
+}
+
+void StatsLogProcessor::onUidMapReceived(const int64_t& eventTimeNs) {
+    std::lock_guard<std::mutex> lock(mMetricsMutex);
+    ALOGW("Received uid map");
+    for (auto it : mMetricsManagers) {
+        it.second->onUidMapReceived(eventTimeNs);
+    }
+}
+
 void StatsLogProcessor::noteOnDiskData(const ConfigKey& key) {
     std::lock_guard<std::mutex> lock(mMetricsMutex);
     mOnDiskDataConfigs.insert(key);
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 313e16d..0d2b33e 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -32,7 +32,7 @@
 namespace statsd {
 
 
-class StatsLogProcessor : public ConfigListener {
+class StatsLogProcessor : public ConfigListener, public virtual PackageInfoListener {
 public:
     StatsLogProcessor(const sp<UidMap>& uidMap, const sp<StatsPullerManager>& pullerManager,
                       const sp<AlarmMonitor>& anomalyAlarmMonitor,
@@ -91,6 +91,16 @@
     /* Sets the active status/ttl for all configs and metrics to the status in ActiveConfigList. */
     void SetConfigsActiveState(const ActiveConfigList& activeConfigList, int64_t currentTimeNs);
 
+    /* Notify all MetricsManagers of app upgrades */
+    void notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid,
+                          const int64_t version) override;
+
+    /* Notify all MetricsManagers of app removals */
+    void notifyAppRemoved(const int64_t& eventTimeNs, const string& apk, const int uid) override;
+
+    /* Notify all MetricsManagers of uid map snapshots received */
+    void onUidMapReceived(const int64_t& eventTimeNs) override;
+
     // Reset all configs.
     void resetConfigs();
 
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index d6c1380..dd65155 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -200,6 +200,7 @@
                 }
             });
 
+    mUidMap->setListener(mProcessor);
     mConfigManager->AddListener(mProcessor);
 
     init_system_properties();
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 6cd34ae..7a63493 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -355,7 +355,7 @@
         BootTimeEventElapsedTime boot_time_event_elapsed_time_reported = 240;
         BootTimeEventUtcTime boot_time_event_utc_time_reported = 241;
         BootTimeEventErrorCode boot_time_event_error_code_reported = 242;
-        UserspaceRebootReported userspace_reboot_reported = 243;
+        UserspaceRebootReported userspace_reboot_reported = 243 [(log_from_module) = "framework"];
     }
 
     // Pulled events will start at field 10000.
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 09ad290..a0c8224 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -73,7 +73,7 @@
 // writing the report to dropbox. MetricProducers should respond to package changes as required in
 // PackageInfoListener, but if none of the metrics are slicing by package name, then the update can
 // be a no-op.
-class MetricProducer : public virtual PackageInfoListener {
+class MetricProducer : public virtual android::RefBase {
 public:
     MetricProducer(const int64_t& metricId, const ConfigKey& key, const int64_t timeBaseNs,
                    const int conditionIndex, const sp<ConditionWizard>& wizard)
@@ -105,8 +105,8 @@
      * the flush again when the end timestamp is forced to be now, and then after flushing, update
      * the start timestamp to be now.
      */
-    void notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid,
-                          const int64_t version) override {
+    virtual void notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid,
+                          const int64_t version) {
         std::lock_guard<std::mutex> lock(mMutex);
 
         if (eventTimeNs > getCurrentBucketEndTimeNs()) {
@@ -119,16 +119,11 @@
         // is a partial bucket and can merge it with the previous bucket.
     };
 
-    void notifyAppRemoved(const int64_t& eventTimeNs, const string& apk, const int uid) override{
+    void notifyAppRemoved(const int64_t& eventTimeNs, const string& apk, const int uid) {
         // Force buckets to split on removal also.
         notifyAppUpgrade(eventTimeNs, apk, uid, 0);
     };
 
-    void onUidMapReceived(const int64_t& eventTimeNs) override{
-            // Purposefully don't flush partial buckets on a new snapshot.
-            // This occurs if a new user is added/removed or statsd crashes.
-    };
-
     // Consume the parsed stats log entry that already matched the "what" of the metric.
     void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) {
         std::lock_guard<std::mutex> lock(mMutex);
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 7b7d0ca..760e800 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -175,6 +175,10 @@
 
 void MetricsManager::notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid,
                                       const int64_t version) {
+    // Inform all metric producers.
+    for (auto it : mAllMetricProducers) {
+        it->notifyAppUpgrade(eventTimeNs, apk, uid, version);
+    }
     // check if we care this package
     if (std::find(mAllowedPkg.begin(), mAllowedPkg.end(), apk) == mAllowedPkg.end()) {
         return;
@@ -186,6 +190,10 @@
 
 void MetricsManager::notifyAppRemoved(const int64_t& eventTimeNs, const string& apk,
                                       const int uid) {
+    // Inform all metric producers.
+    for (auto it : mAllMetricProducers) {
+        it->notifyAppRemoved(eventTimeNs, apk, uid);
+    }
     // check if we care this package
     if (std::find(mAllowedPkg.begin(), mAllowedPkg.end(), apk) == mAllowedPkg.end()) {
         return;
@@ -196,6 +204,9 @@
 }
 
 void MetricsManager::onUidMapReceived(const int64_t& eventTimeNs) {
+    // Purposefully don't inform metric producers on a new snapshot
+    // because we don't need to flush partial buckets.
+    // This occurs if a new user is added/removed or statsd crashes.
     if (mAllowedPkg.size() == 0) {
         return;
     }
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 8efca1e..34d47d4 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -35,7 +35,7 @@
 namespace statsd {
 
 // A MetricsManager is responsible for managing metrics from one single config source.
-class MetricsManager : public PackageInfoListener {
+class MetricsManager : public virtual android::RefBase {
 public:
     MetricsManager(const ConfigKey& configKey, const StatsdConfig& config, const int64_t timeBaseNs,
                    const int64_t currentTimeNs, const sp<UidMap>& uidMap,
@@ -63,15 +63,11 @@
         unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>>& alarmSet);
 
     void notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid,
-                          const int64_t version) override;
+                          const int64_t version);
 
-    void notifyAppRemoved(const int64_t& eventTimeNs, const string& apk, const int uid) override;
+    void notifyAppRemoved(const int64_t& eventTimeNs, const string& apk, const int uid);
 
-    void onUidMapReceived(const int64_t& eventTimeNs) override;
-
-    bool shouldAddUidMapListener() const {
-        return !mAllowedPkg.empty();
-    }
+    void onUidMapReceived(const int64_t& eventTimeNs);
 
     bool shouldWriteToDisk() const {
         return mNoReportMetricIds.size() != mAllMetricProducers.size();
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 46442b5..f5f2479 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -286,7 +286,7 @@
 }
 
 bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseTimeNs,
-                 const int64_t currentTimeNs, UidMap& uidMap,
+                 const int64_t currentTimeNs,
                  const sp<StatsPullerManager>& pullerManager,
                  const unordered_map<int64_t, int>& logTrackerMap,
                  const unordered_map<int64_t, int>& conditionTrackerMap,
@@ -600,9 +600,6 @@
         }
         noReportMetricIds.insert(no_report_metric);
     }
-    for (const auto& it : allMetricProducers) {
-        uidMap.addListener(it);
-    }
     return true;
 }
 
@@ -807,7 +804,7 @@
         return false;
     }
 
-    if (!initMetrics(key, config, timeBaseNs, currentTimeNs, uidMap, pullerManager, logTrackerMap,
+    if (!initMetrics(key, config, timeBaseNs, currentTimeNs, pullerManager, logTrackerMap,
                      conditionTrackerMap, allAtomMatchers, allConditionTrackers, allMetricProducers,
                      conditionToMetricMap, trackerToMetricMap, metricProducerMap,
                      noReportMetricIds)) {
diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp
index d4b57dd..7e63bbf 100644
--- a/cmds/statsd/src/packages/UidMap.cpp
+++ b/cmds/statsd/src/packages/UidMap.cpp
@@ -119,7 +119,7 @@
 void UidMap::updateMap(const int64_t& timestamp, const vector<int32_t>& uid,
                        const vector<int64_t>& versionCode, const vector<String16>& versionString,
                        const vector<String16>& packageName, const vector<String16>& installer) {
-    vector<wp<PackageInfoListener>> broadcastList;
+    wp<PackageInfoListener> broadcast = NULL;
     {
         lock_guard<mutex> lock(mMutex);  // Exclusively lock for updates.
 
@@ -150,25 +150,22 @@
 
         ensureBytesUsedBelowLimit();
         StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed);
-        getListenerListCopyLocked(&broadcastList);
+        broadcast = mSubscriber;
     }
     // To avoid invoking callback while holding the internal lock. we get a copy of the listener
-    // list and invoke the callback. It's still possible that after we copy the list, a
-    // listener removes itself before we call it. It's then the listener's job to handle it (expect
-    // the callback to be called after listener is removed, and the listener should properly
-    // ignore it).
-    for (const auto& weakPtr : broadcastList) {
-        auto strongPtr = weakPtr.promote();
-        if (strongPtr != NULL) {
-            strongPtr->onUidMapReceived(timestamp);
-        }
+    // and invoke the callback. It's still possible that after we copy the listener, it removes
+    // itself before we call it. It's then the listener's job to handle it (expect the callback to
+    // be called after listener is removed, and the listener should properly ignore it).
+    auto strongPtr = broadcast.promote();
+    if (strongPtr != NULL) {
+        strongPtr->onUidMapReceived(timestamp);
     }
 }
 
 void UidMap::updateApp(const int64_t& timestamp, const String16& app_16, const int32_t& uid,
                        const int64_t& versionCode, const String16& versionString,
                        const String16& installer) {
-    vector<wp<PackageInfoListener>> broadcastList;
+    wp<PackageInfoListener> broadcast = NULL;
     string appName = string(String8(app_16).string());
     {
         lock_guard<mutex> lock(mMutex);
@@ -195,7 +192,7 @@
             // for the first time, then we don't notify the listeners.
             // It's also OK to split again if we're forming a partial bucket after re-installing an
             // app after deletion.
-            getListenerListCopyLocked(&broadcastList);
+            broadcast = mSubscriber;
         }
         mChanges.emplace_back(false, timestamp, appName, uid, versionCode, newVersionString,
                               prevVersion, prevVersionString);
@@ -205,11 +202,9 @@
         StatsdStats::getInstance().setUidMapChanges(mChanges.size());
     }
 
-    for (const auto& weakPtr : broadcastList) {
-        auto strongPtr = weakPtr.promote();
-        if (strongPtr != NULL) {
-            strongPtr->notifyAppUpgrade(timestamp, appName, uid, versionCode);
-        }
+    auto strongPtr = broadcast.promote();
+    if (strongPtr != NULL) {
+        strongPtr->notifyAppUpgrade(timestamp, appName, uid, versionCode);
     }
 }
 
@@ -230,21 +225,8 @@
     }
 }
 
-void UidMap::getListenerListCopyLocked(vector<wp<PackageInfoListener>>* output) {
-    for (auto weakIt = mSubscribers.begin(); weakIt != mSubscribers.end();) {
-        auto strongPtr = weakIt->promote();
-        if (strongPtr != NULL) {
-            output->push_back(*weakIt);
-            weakIt++;
-        } else {
-            weakIt = mSubscribers.erase(weakIt);
-            VLOG("The UidMap listener is gone, remove it now");
-        }
-    }
-}
-
 void UidMap::removeApp(const int64_t& timestamp, const String16& app_16, const int32_t& uid) {
-    vector<wp<PackageInfoListener>> broadcastList;
+    wp<PackageInfoListener> broadcast = NULL;
     string app = string(String8(app_16).string());
     {
         lock_guard<mutex> lock(mMutex);
@@ -271,25 +253,18 @@
         ensureBytesUsedBelowLimit();
         StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed);
         StatsdStats::getInstance().setUidMapChanges(mChanges.size());
-        getListenerListCopyLocked(&broadcastList);
+        broadcast = mSubscriber;
     }
 
-    for (const auto& weakPtr : broadcastList) {
-        auto strongPtr = weakPtr.promote();
-        if (strongPtr != NULL) {
-            strongPtr->notifyAppRemoved(timestamp, app, uid);
-        }
+    auto strongPtr = broadcast.promote();
+    if (strongPtr != NULL) {
+        strongPtr->notifyAppRemoved(timestamp, app, uid);
     }
 }
 
-void UidMap::addListener(wp<PackageInfoListener> producer) {
+void UidMap::setListener(wp<PackageInfoListener> listener) {
     lock_guard<mutex> lock(mMutex);  // Lock for updates
-    mSubscribers.insert(producer);
-}
-
-void UidMap::removeListener(wp<PackageInfoListener> producer) {
-    lock_guard<mutex> lock(mMutex);  // Lock for updates
-    mSubscribers.erase(producer);
+    mSubscriber = listener;
 }
 
 void UidMap::assignIsolatedUid(int isolatedUid, int parentUid) {
diff --git a/cmds/statsd/src/packages/UidMap.h b/cmds/statsd/src/packages/UidMap.h
index a7c5fb2..2d3f6ee 100644
--- a/cmds/statsd/src/packages/UidMap.h
+++ b/cmds/statsd/src/packages/UidMap.h
@@ -118,12 +118,10 @@
     // adb shell cmd stats print-uid-map
     void printUidMap(int outFd) const;
 
-    // Commands for indicating to the map that a producer should be notified if an app is updated.
-    // This allows the metric producer to distinguish when the same uid or app represents a
-    // different version of an app.
-    void addListener(wp<PackageInfoListener> producer);
-    // Remove the listener from the set of metric producers that subscribe to updates.
-    void removeListener(wp<PackageInfoListener> producer);
+    // Command for indicating to the map that StatsLogProcessor should be notified if an app is
+    // updated. This allows metric producers and managers to distinguish when the same uid or app
+    // represents a different version of an app.
+    void setListener(wp<PackageInfoListener> listener);
 
     // Informs uid map that a config is added/updated. Used for keeping mConfigKeys up to date.
     void OnConfigUpdated(const ConfigKey& key);
@@ -167,8 +165,6 @@
     std::set<string> getAppNamesFromUidLocked(const int32_t& uid, bool returnNormalized) const;
     string normalizeAppName(const string& appName) const;
 
-    void getListenerListCopyLocked(std::vector<wp<PackageInfoListener>>* output);
-
     void writeUidMapSnapshotLocked(int64_t timestamp, bool includeVersionStrings,
                                    bool includeInstaller, const std::set<int32_t>& interestingUids,
                                    std::set<string>* str_set, ProtoOutputStream* proto);
@@ -195,8 +191,8 @@
     // Store which uid and apps represent deleted ones.
     std::list<std::pair<int, string>> mDeletedApps;
 
-    // Metric producers that should be notified if there's an upgrade in any app.
-    set<wp<PackageInfoListener>> mSubscribers;
+    // Notify StatsLogProcessor if there's an upgrade/removal in any app.
+    wp<PackageInfoListener> mSubscriber;
 
     // Mapping of config keys we're aware of to the epoch time they last received an update. This
     // lets us know it's safe to delete events older than the oldest update. The value is nanosec.
diff --git a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
index 309d251..0bc3ebb 100644
--- a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
@@ -162,7 +162,10 @@
 
     ConfigMetricsReport report = GetReports(service.mProcessor, start + 4);
     backfillStartEndTimestamp(&report);
-    EXPECT_EQ(1, report.metrics_size());
+
+    ASSERT_EQ(1, report.metrics_size());
+    ASSERT_EQ(1, report.metrics(0).count_metrics().data_size());
+    ASSERT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info_size());
     EXPECT_TRUE(report.metrics(0).count_metrics().data(0).bucket_info(0).
                     has_start_bucket_elapsed_nanos());
     EXPECT_TRUE(report.metrics(0).count_metrics().data(0).bucket_info(0).
@@ -186,7 +189,10 @@
 
     ConfigMetricsReport report = GetReports(service.mProcessor, start + 4);
     backfillStartEndTimestamp(&report);
-    EXPECT_EQ(1, report.metrics_size());
+
+    ASSERT_EQ(1, report.metrics_size());
+    ASSERT_EQ(1, report.metrics(0).count_metrics().data_size());
+    ASSERT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info_size());
     EXPECT_TRUE(report.metrics(0).count_metrics().data(0).bucket_info(0).
                     has_start_bucket_elapsed_nanos());
     EXPECT_TRUE(report.metrics(0).count_metrics().data(0).bucket_info(0).
@@ -228,8 +234,9 @@
     ConfigMetricsReport report =
             GetReports(service.mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC, true);
     backfillStartEndTimestamp(&report);
-    EXPECT_EQ(1, report.metrics_size());
-    EXPECT_EQ(1, report.metrics(0).value_metrics().skipped_size());
+
+    ASSERT_EQ(1, report.metrics_size());
+    ASSERT_EQ(1, report.metrics(0).value_metrics().skipped_size());
     EXPECT_TRUE(report.metrics(0).value_metrics().skipped(0).has_start_bucket_elapsed_nanos());
     // Can't test the start time since it will be based on the actual time when the pulling occurs.
     EXPECT_EQ(MillisToNano(NanoToMillis(endSkipped)),
@@ -270,8 +277,8 @@
     ConfigMetricsReport report =
             GetReports(service.mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC, true);
     backfillStartEndTimestamp(&report);
-    EXPECT_EQ(1, report.metrics_size());
-    EXPECT_EQ(1, report.metrics(0).gauge_metrics().skipped_size());
+    ASSERT_EQ(1, report.metrics_size());
+    ASSERT_EQ(1, report.metrics(0).gauge_metrics().skipped_size());
     // Can't test the start time since it will be based on the actual time when the pulling occurs.
     EXPECT_TRUE(report.metrics(0).gauge_metrics().skipped(0).has_start_bucket_elapsed_nanos());
     EXPECT_EQ(MillisToNano(NanoToMillis(endSkipped)),
diff --git a/core/java/android/accessibilityservice/OWNERS b/core/java/android/accessibilityservice/OWNERS
index 265674a..c6f42f7 100644
--- a/core/java/android/accessibilityservice/OWNERS
+++ b/core/java/android/accessibilityservice/OWNERS
@@ -1,3 +1,4 @@
 svetoslavganov@google.com
 pweaver@google.com
 rhedjao@google.com
+qasid@google.com
diff --git a/core/java/android/app/DexLoadReporter.java b/core/java/android/app/DexLoadReporter.java
index 229bee5..5bc9992 100644
--- a/core/java/android/app/DexLoadReporter.java
+++ b/core/java/android/app/DexLoadReporter.java
@@ -28,9 +28,8 @@
 
 import java.io.File;
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.HashSet;
-import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -87,50 +86,32 @@
     }
 
     @Override
-    public void report(List<ClassLoader> classLoadersChain, List<String> classPaths) {
-        if (classLoadersChain.size() != classPaths.size()) {
-            Slog.wtf(TAG, "Bad call to DexLoadReporter: argument size mismatch");
-            return;
-        }
-        if (classPaths.isEmpty()) {
-            Slog.wtf(TAG, "Bad call to DexLoadReporter: empty dex paths");
-            return;
-        }
-
-        // The first element of classPaths is the list of dex files that should be registered.
-        // The classpath is represented as a list of dex files separated by File.pathSeparator.
-        String[] dexPathsForRegistration = classPaths.get(0).split(File.pathSeparator);
-        if (dexPathsForRegistration.length == 0) {
-            // No dex files to register.
+    public void report(Map<String, String> classLoaderContextMap) {
+        if (classLoaderContextMap.isEmpty()) {
+            Slog.wtf(TAG, "Bad call to DexLoadReporter: empty classLoaderContextMap");
             return;
         }
 
         // Notify the package manager about the dex loads unconditionally.
         // The load might be for either a primary or secondary dex file.
-        notifyPackageManager(classLoadersChain, classPaths);
+        notifyPackageManager(classLoaderContextMap);
         // Check for secondary dex files and register them for profiling if possible.
         // Note that we only register the dex paths belonging to the first class loader.
-        registerSecondaryDexForProfiling(dexPathsForRegistration);
+        registerSecondaryDexForProfiling(classLoaderContextMap.keySet());
     }
 
-    private void notifyPackageManager(List<ClassLoader> classLoadersChain,
-            List<String> classPaths) {
+    private void notifyPackageManager(Map<String, String> classLoaderContextMap) {
         // Get the class loader names for the binder call.
-        List<String> classLoadersNames = new ArrayList<>(classPaths.size());
-        for (ClassLoader classLoader : classLoadersChain) {
-            classLoadersNames.add(classLoader.getClass().getName());
-        }
         String packageName = ActivityThread.currentPackageName();
         try {
-            ActivityThread.getPackageManager().notifyDexLoad(
-                    packageName, classLoadersNames, classPaths,
-                    VMRuntime.getRuntime().vmInstructionSet());
+            ActivityThread.getPackageManager().notifyDexLoad(packageName,
+                    classLoaderContextMap, VMRuntime.getRuntime().vmInstructionSet());
         } catch (RemoteException re) {
             Slog.e(TAG, "Failed to notify PM about dex load for package " + packageName, re);
         }
     }
 
-    private void registerSecondaryDexForProfiling(String[] dexPaths) {
+    private void registerSecondaryDexForProfiling(Set<String> dexPaths) {
         if (!SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false)) {
             return;
         }
diff --git a/core/java/android/app/compat/ChangeIdStateCache.java b/core/java/android/app/compat/ChangeIdStateCache.java
deleted file mode 100644
index 9ef63f6..0000000
--- a/core/java/android/app/compat/ChangeIdStateCache.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2020 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.compat;
-
-import android.app.PropertyInvalidatedCache;
-import android.content.Context;
-import android.os.Binder;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-
-import com.android.internal.compat.IPlatformCompat;
-
-/**
- * Handles caching of calls to {@link com.android.internal.compat.IPlatformCompat}
- * @hide
- */
-public final class ChangeIdStateCache
-        extends PropertyInvalidatedCache<ChangeIdStateQuery, Boolean> {
-    private static final String CACHE_KEY = "cache_key.is_compat_change_enabled";
-    private static final int MAX_ENTRIES = 20;
-    private static boolean sDisabled = false;
-
-    /** @hide */
-    public ChangeIdStateCache() {
-        super(MAX_ENTRIES, CACHE_KEY);
-    }
-
-    /**
-     * Disable cache.
-     *
-     * <p>Should only be used in unit tests.
-     * @hide
-     */
-    public static void disable() {
-        sDisabled = true;
-    }
-
-    /**
-     * Invalidate the cache.
-     *
-     * <p>Can only be called by the system server process.
-     * @hide
-     */
-    public static void invalidate() {
-        if (!sDisabled) {
-            PropertyInvalidatedCache.invalidateCache(CACHE_KEY);
-        }
-    }
-
-    @Override
-    protected Boolean recompute(ChangeIdStateQuery query) {
-        IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
-                ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
-        final long token = Binder.clearCallingIdentity();
-        try {
-            if (query.type == ChangeIdStateQuery.QUERY_BY_PACKAGE_NAME) {
-                return platformCompat.isChangeEnabledByPackageName(query.changeId,
-                                                                   query.packageName,
-                                                                   query.userId);
-            } else if (query.type == ChangeIdStateQuery.QUERY_BY_UID) {
-                return platformCompat.isChangeEnabledByUid(query.changeId, query.uid);
-            } else {
-                throw new IllegalArgumentException("Invalid query type: " + query.type);
-            }
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-        throw new IllegalStateException("Could not recompute value!");
-    }
-}
diff --git a/core/java/android/app/compat/ChangeIdStateQuery.java b/core/java/android/app/compat/ChangeIdStateQuery.java
deleted file mode 100644
index 2c4c120..0000000
--- a/core/java/android/app/compat/ChangeIdStateQuery.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2020 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.compat;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Objects;
-
-
-/**
- * A key type for caching calls to {@link com.android.internal.compat.IPlatformCompat}
- *
- * <p>For {@link com.android.internal.compat.IPlatformCompat#isChangeEnabledByPackageName}
- * and {@link com.android.internal.compat.IPlatformCompat#isChangeEnabledByUid}
- *
- * @hide
- */
-final class ChangeIdStateQuery {
-
-    static final int QUERY_BY_PACKAGE_NAME = 0;
-    static final int QUERY_BY_UID = 1;
-    @IntDef({QUERY_BY_PACKAGE_NAME, QUERY_BY_UID})
-    @Retention(RetentionPolicy.SOURCE)
-    @interface QueryType {}
-
-    public @QueryType int type;
-    public long changeId;
-    public String packageName;
-    public int uid;
-    public int userId;
-
-    private ChangeIdStateQuery(@QueryType int type, long changeId, String packageName,
-                               int uid, int userId) {
-        this.type = type;
-        this.changeId = changeId;
-        this.packageName = packageName;
-        this.uid = uid;
-        this.userId = userId;
-    }
-
-    static ChangeIdStateQuery byPackageName(long changeId, @NonNull String packageName,
-                                            int userId) {
-        return new ChangeIdStateQuery(QUERY_BY_PACKAGE_NAME, changeId, packageName, 0, userId);
-    }
-
-    static ChangeIdStateQuery byUid(long changeId, int uid) {
-        return new ChangeIdStateQuery(QUERY_BY_UID, changeId, null, uid, 0);
-    }
-
-    @Override
-    public boolean equals(Object other) {
-        if (this == other) {
-            return true;
-        }
-        if ((other == null) || !(other instanceof ChangeIdStateQuery)) {
-            return false;
-        }
-        final ChangeIdStateQuery that = (ChangeIdStateQuery) other;
-        return this.type == that.type
-            && this.changeId == that.changeId
-            && Objects.equals(this.packageName, that.packageName)
-            && this.uid == that.uid
-            && this.userId == that.userId;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(type, changeId, packageName, uid, userId);
-    }
-}
diff --git a/core/java/android/app/compat/CompatChanges.java b/core/java/android/app/compat/CompatChanges.java
index 0d5e45f..e289a27 100644
--- a/core/java/android/app/compat/CompatChanges.java
+++ b/core/java/android/app/compat/CompatChanges.java
@@ -19,8 +19,14 @@
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.compat.Compatibility;
+import android.content.Context;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.UserHandle;
 
+import com.android.internal.compat.IPlatformCompat;
+
 /**
  * CompatChanges APIs - to be used by platform code only (including mainline
  * modules).
@@ -29,7 +35,6 @@
  */
 @SystemApi
 public final class CompatChanges {
-    private static final ChangeIdStateCache QUERY_CACHE = new ChangeIdStateCache();
     private CompatChanges() {}
 
     /**
@@ -64,8 +69,17 @@
      */
     public static boolean isChangeEnabled(long changeId, @NonNull String packageName,
             @NonNull UserHandle user) {
-        return QUERY_CACHE.query(ChangeIdStateQuery.byPackageName(changeId, packageName,
-                                                           user.getIdentifier()));
+        IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
+                ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return platformCompat.isChangeEnabledByPackageName(changeId, packageName,
+                    user.getIdentifier());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
     }
 
     /**
@@ -87,7 +101,15 @@
      * @return {@code true} if the change is enabled for the current app.
      */
     public static boolean isChangeEnabled(long changeId, int uid) {
-        return QUERY_CACHE.query(ChangeIdStateQuery.byUid(changeId, uid));
+        IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
+                ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return platformCompat.isChangeEnabledByUid(changeId, uid);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
     }
-
 }
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 4527d8e..8794173 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3356,6 +3356,7 @@
             CONSUMER_IR_SERVICE,
             //@hide: TRUST_SERVICE,
             TV_INPUT_SERVICE,
+            //@hide: TV_TUNER_RESOURCE_MGR_SERVICE,
             //@hide: NETWORK_SCORE_SERVICE,
             USAGE_STATS_SERVICE,
             MEDIA_SESSION_SERVICE,
@@ -4586,6 +4587,17 @@
     public static final String TV_INPUT_SERVICE = "tv_input";
 
     /**
+     * Use with {@link #getSystemService(String)} to retrieve a
+     * {@link android.media.tv.tunerresourcemanager.TunerResourceManager} for interacting with TV
+     * tuner resources on the device.
+     *
+     * @see #getSystemService(String)
+     * @see android.media.tv.TunerResourceManager
+     * @hide
+     */
+    public static final String TV_TUNER_RESOURCE_MGR_SERVICE = "tv_tuner_resource_mgr";
+
+    /**
      * {@link android.net.NetworkScoreManager} for managing network scoring.
      * @see #getSystemService(String)
      * @see android.net.NetworkScoreManager
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 429a6e5..6d051e4 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -529,19 +529,12 @@
      * Notify the package manager that a list of dex files have been loaded.
      *
      * @param loadingPackageName the name of the package who performs the load
-     * @param classLoadersNames the names of the class loaders present in the loading chain. The
-     *    list encodes the class loader chain in the natural order. The first class loader has
-     *    the second one as its parent and so on. The dex files present in the class path of the
-     *    first class loader will be recorded in the usage file.
-     * @param classPaths the class paths corresponding to the class loaders names from
-     *     {@param classLoadersNames}. The the first element corresponds to the first class loader
-     *     and so on. A classpath is represented as a list of dex files separated by
-     *     {@code File.pathSeparator}, or null if the class loader's classpath is not known.
-     *     The dex files found in the first class path will be recorded in the usage file.
+     * @param classLoaderContextMap a map from file paths to dex files that have been loaded to
+     *     the class loader context that was used to load them.
      * @param loaderIsa the ISA of the loader process
      */
-    oneway void notifyDexLoad(String loadingPackageName, in List<String> classLoadersNames,
-            in List<String> classPaths, String loaderIsa);
+    oneway void notifyDexLoad(String loadingPackageName,
+            in Map<String, String> classLoaderContextMap, String loaderIsa);
 
     /**
      * Register an application dex module with the package manager.
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 93b90b4..e17dda9 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2147,6 +2147,23 @@
 
     /**
      * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature(String, int)}: If this feature is supported, the feature version
+     * specifies a date such that the device is known to pass the Vulkan dEQP test suite associated
+     * with that date.  The date is encoded as follows:
+     * <ul>
+     * <li>Year in bits 31-16</li>
+     * <li>Month in bits 15-8</li>
+     * <li>Day in bits 7-0</li>
+     * </ul>
+     * <p>
+     * Example: 2019-03-01 is encoded as 0x07E30301, and would indicate that the device passes the
+     * Vulkan dEQP test suite version that was current on 2019-03-01.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_VULKAN_DEQP_LEVEL = "android.software.vulkan.deqp.level";
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device includes broadcast radio tuner.
      * @hide
      */
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 5405ad3..589b1aa 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -1279,7 +1279,8 @@
     @UnsupportedAppUsage
     public NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser(int userId) {
         try {
-            return mService.getDefaultNetworkCapabilitiesForUser(userId);
+            return mService.getDefaultNetworkCapabilitiesForUser(
+                    userId, mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1361,7 +1362,7 @@
     @Nullable
     public NetworkCapabilities getNetworkCapabilities(@Nullable Network network) {
         try {
-            return mService.getNetworkCapabilities(network);
+            return mService.getNetworkCapabilities(network, mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -2424,14 +2425,14 @@
     /**
      * Get the set of tethered dhcp ranges.
      *
-     * @return an array of 0 or more {@code String} of tethered dhcp ranges.
-     * @deprecated This API just return the default value which is not used in DhcpServer.
+     * @deprecated This method is not supported.
+     * TODO: remove this function when all of clients are removed.
      * {@hide}
      */
     @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
     @Deprecated
     public String[] getTetheredDhcpRanges() {
-        return getTetheringManager().getTetheredDhcpRanges();
+        throw new UnsupportedOperationException("getTetheredDhcpRanges is not supported");
     }
 
     /**
@@ -4035,10 +4036,9 @@
             @NonNull PendingIntent operation) {
         printStackTrace();
         checkPendingIntentNotNull(operation);
-        final String callingPackageName = mContext.getOpPackageName();
         try {
             mService.pendingRequestForNetwork(
-                    request.networkCapabilities, operation, callingPackageName);
+                    request.networkCapabilities, operation, mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         } catch (ServiceSpecificException e) {
@@ -4150,10 +4150,9 @@
             @NonNull PendingIntent operation) {
         printStackTrace();
         checkPendingIntentNotNull(operation);
-        final String callingPackageName = mContext.getOpPackageName();
         try {
             mService.pendingListenForNetwork(
-                    request.networkCapabilities, operation, callingPackageName);
+                    request.networkCapabilities, operation, mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         } catch (ServiceSpecificException e) {
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 3a55461..1434560 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -59,7 +59,8 @@
     NetworkInfo[] getAllNetworkInfo();
     Network getNetworkForType(int networkType);
     Network[] getAllNetworks();
-    NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser(int userId);
+    NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser(
+            int userId, String callingPackageName);
 
     boolean isNetworkSupported(int networkType);
 
@@ -68,7 +69,7 @@
     LinkProperties getLinkPropertiesForType(int networkType);
     LinkProperties getLinkProperties(in Network network);
 
-    NetworkCapabilities getNetworkCapabilities(in Network network);
+    NetworkCapabilities getNetworkCapabilities(in Network network, String callingPackageName);
 
     @UnsupportedAppUsage
     NetworkState[] getAllNetworkState();
diff --git a/core/java/android/net/Ikev2VpnProfile.java b/core/java/android/net/Ikev2VpnProfile.java
index 42b4da1..f19a341 100644
--- a/core/java/android/net/Ikev2VpnProfile.java
+++ b/core/java/android/net/Ikev2VpnProfile.java
@@ -25,7 +25,10 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.os.Process;
 import android.security.Credentials;
+import android.security.KeyStore;
+import android.security.keystore.AndroidKeyStoreProvider;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.net.VpnProfile;
@@ -59,6 +62,11 @@
  *     Exchange, Version 2 (IKEv2)</a>
  */
 public final class Ikev2VpnProfile extends PlatformVpnProfile {
+    /** Prefix for when a Private Key is an alias to look for in KeyStore @hide */
+    public static final String PREFIX_KEYSTORE_ALIAS = "KEYSTORE_ALIAS:";
+    /** Prefix for when a Private Key is stored directly in the profile @hide */
+    public static final String PREFIX_INLINE = "INLINE:";
+
     private static final String MISSING_PARAM_MSG_TMPL = "Required parameter was not provided: %s";
     private static final String EMPTY_CERT = "";
 
@@ -339,7 +347,8 @@
                 break;
             case TYPE_IKEV2_IPSEC_RSA:
                 profile.ipsecUserCert = certificateToPemString(mUserCert);
-                profile.ipsecSecret = encodeForIpsecSecret(mRsaPrivateKey.getEncoded());
+                profile.ipsecSecret =
+                        PREFIX_INLINE + encodeForIpsecSecret(mRsaPrivateKey.getEncoded());
                 profile.ipsecCaCert =
                         mServerRootCaCert == null ? "" : certificateToPemString(mServerRootCaCert);
                 break;
@@ -360,6 +369,22 @@
     @NonNull
     public static Ikev2VpnProfile fromVpnProfile(@NonNull VpnProfile profile)
             throws IOException, GeneralSecurityException {
+        return fromVpnProfile(profile, null);
+    }
+
+    /**
+     * Builds the Ikev2VpnProfile from the given profile.
+     *
+     * @param profile the source VpnProfile to build from
+     * @param keyStore the Android Keystore instance to use to retrieve the private key, or null if
+     *     the private key is PEM-encoded into the profile.
+     * @return The IKEv2/IPsec VPN profile
+     * @hide
+     */
+    @NonNull
+    public static Ikev2VpnProfile fromVpnProfile(
+            @NonNull VpnProfile profile, @Nullable KeyStore keyStore)
+            throws IOException, GeneralSecurityException {
         final Builder builder = new Builder(profile.server, profile.ipsecIdentifier);
         builder.setProxy(profile.proxy);
         builder.setAllowedAlgorithms(profile.getAllowedAlgorithms());
@@ -378,8 +403,21 @@
                 builder.setAuthPsk(decodeFromIpsecSecret(profile.ipsecSecret));
                 break;
             case TYPE_IKEV2_IPSEC_RSA:
+                final PrivateKey key;
+                if (profile.ipsecSecret.startsWith(PREFIX_KEYSTORE_ALIAS)) {
+                    Objects.requireNonNull(keyStore, "Missing Keystore for aliased PrivateKey");
+
+                    final String alias =
+                            profile.ipsecSecret.substring(PREFIX_KEYSTORE_ALIAS.length());
+                    key = AndroidKeyStoreProvider.loadAndroidKeyStorePrivateKeyFromKeystore(
+                            keyStore, alias, Process.myUid());
+                } else if (profile.ipsecSecret.startsWith(PREFIX_INLINE)) {
+                    key = getPrivateKey(profile.ipsecSecret.substring(PREFIX_INLINE.length()));
+                } else {
+                    throw new IllegalArgumentException("Invalid RSA private key prefix");
+                }
+
                 final X509Certificate userCert = certificateFromPemString(profile.ipsecUserCert);
-                final PrivateKey key = getPrivateKey(profile.ipsecSecret);
                 final X509Certificate serverRootCa = certificateFromPemString(profile.ipsecCaCert);
                 builder.setAuthDigitalSignature(userCert, key, serverRootCa);
                 break;
@@ -391,6 +429,39 @@
     }
 
     /**
+     * Validates that the VpnProfile is acceptable for the purposes of an Ikev2VpnProfile.
+     *
+     * @hide
+     */
+    public static boolean isValidVpnProfile(@NonNull VpnProfile profile) {
+        if (profile.server.isEmpty() || profile.ipsecIdentifier.isEmpty()) {
+            return false;
+        }
+
+        switch (profile.type) {
+            case TYPE_IKEV2_IPSEC_USER_PASS:
+                if (profile.username.isEmpty() || profile.password.isEmpty()) {
+                    return false;
+                }
+                break;
+            case TYPE_IKEV2_IPSEC_PSK:
+                if (profile.ipsecSecret.isEmpty()) {
+                    return false;
+                }
+                break;
+            case TYPE_IKEV2_IPSEC_RSA:
+                if (profile.ipsecSecret.isEmpty() || profile.ipsecUserCert.isEmpty()) {
+                    return false;
+                }
+                break;
+            default:
+                return false;
+        }
+
+        return true;
+    }
+
+    /**
      * Converts a X509 Certificate to a PEM-formatted string.
      *
      * <p>Must be public due to runtime-package restrictions.
@@ -432,7 +503,6 @@
 
     /** @hide */
     @NonNull
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
     public static String encodeForIpsecSecret(@NonNull byte[] secret) {
         checkNotNull(secret, MISSING_PARAM_MSG_TMPL, "secret");
 
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index ef4a9e5..873d6e914 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -830,6 +830,23 @@
      * <p>This field keeps track of the UID of the app that created this network and is in charge of
      * its lifecycle. This could be the UID of apps such as the Wifi network suggestor, the running
      * VPN, or Carrier Service app managing a cellular data connection.
+     *
+     * <p>For NetworkCapability instances being sent from ConnectivityService, this value MUST be
+     * reset to Process.INVALID_UID unless all the following conditions are met:
+     *
+     * <ol>
+     *   <li>The destination app is the network owner
+     *   <li>The destination app has the ACCESS_FINE_LOCATION permission granted
+     *   <li>The user's location toggle is on
+     * </ol>
+     *
+     * This is because the owner UID is location-sensitive. The apps that request a network could
+     * know where the device is if they can tell for sure the system has connected to the network
+     * they requested.
+     *
+     * <p>This is populated by the network agents and for the NetworkCapabilities instance sent by
+     * an app to the System Server, the value MUST be reset to Process.INVALID_UID by the system
+     * server.
      */
     private int mOwnerUid = Process.INVALID_UID;
 
@@ -842,7 +859,16 @@
     }
 
     /**
-     * Retrieves the UID of the owner app.
+     * Retrieves the UID of the app that owns this network.
+     *
+     * <p>For user privacy reasons, this field will only be populated if:
+     *
+     * <ol>
+     *   <li>The calling app is the network owner
+     *   <li>The calling app has the ACCESS_FINE_LOCATION permission granted
+     *   <li>The user's location toggle is on
+     * </ol>
+     *
      */
     public int getOwnerUid() {
         return mOwnerUid;
@@ -880,8 +906,9 @@
      * @param administratorUids the UIDs to be set as administrators of this Network.
      * @hide
      */
+    @NonNull
     @SystemApi
-    public @NonNull NetworkCapabilities setAdministratorUids(
+    public NetworkCapabilities setAdministratorUids(
             @NonNull final List<Integer> administratorUids) {
         mAdministratorUids.clear();
         mAdministratorUids.addAll(administratorUids);
diff --git a/core/java/android/net/VpnManager.java b/core/java/android/net/VpnManager.java
index f19ba0f..2041cfb 100644
--- a/core/java/android/net/VpnManager.java
+++ b/core/java/android/net/VpnManager.java
@@ -126,7 +126,11 @@
         return getIntentForConfirmation();
     }
 
-    /** Delete the VPN profile configuration that was provisioned by the calling app */
+    /**
+     * Delete the VPN profile configuration that was provisioned by the calling app
+     *
+     * @throws SecurityException if this would violate user settings
+     */
     public void deleteProvisionedVpnProfile() {
         try {
             mService.deleteVpnProfile(mContext.getOpPackageName());
diff --git a/core/java/android/os/image/DynamicSystemClient.java b/core/java/android/os/image/DynamicSystemClient.java
index 5cb3361..50d8d80 100644
--- a/core/java/android/os/image/DynamicSystemClient.java
+++ b/core/java/android/os/image/DynamicSystemClient.java
@@ -96,9 +96,6 @@
 
     private static final String TAG = "DynSystemClient";
 
-    private static final long DEFAULT_USERDATA_SIZE = (10L << 30);
-
-
     /** Listener for installation status updates. */
     public interface OnStatusChangedListener {
         /**
@@ -386,7 +383,7 @@
     @SystemApi
     @TestApi
     public void start(@NonNull Uri systemUrl, @BytesLong long systemSize) {
-        start(systemUrl, systemSize, DEFAULT_USERDATA_SIZE);
+        start(systemUrl, systemSize, 0 /* Use the default userdata size */);
     }
 
     /**
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 3842def..dcbbb70 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9739,11 +9739,17 @@
         private static final Validator BUGREPORT_IN_POWER_MENU_VALIDATOR = BOOLEAN_VALIDATOR;
 
         /**
-         * Whether ADB is enabled.
+         * Whether ADB over USB is enabled.
          */
         public static final String ADB_ENABLED = "adb_enabled";
 
         /**
+         * Whether ADB over Wifi is enabled.
+         * @hide
+         */
+        public static final String ADB_WIFI_ENABLED = "adb_wifi_enabled";
+
+        /**
          * Whether Views are allowed to save their attribute data.
          * @hide
          */
diff --git a/core/java/android/view/accessibility/OWNERS b/core/java/android/view/accessibility/OWNERS
index 265674a..c6f42f7 100644
--- a/core/java/android/view/accessibility/OWNERS
+++ b/core/java/android/view/accessibility/OWNERS
@@ -1,3 +1,4 @@
 svetoslavganov@google.com
 pweaver@google.com
 rhedjao@google.com
+qasid@google.com
diff --git a/core/java/com/android/internal/net/VpnProfile.java b/core/java/com/android/internal/net/VpnProfile.java
index bbae0273..23b1ab5 100644
--- a/core/java/com/android/internal/net/VpnProfile.java
+++ b/core/java/com/android/internal/net/VpnProfile.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.net.Ikev2VpnProfile;
 import android.net.ProxyInfo;
 import android.os.Build;
 import android.os.Parcel;
@@ -332,15 +333,38 @@
         return builder.toString().getBytes(StandardCharsets.UTF_8);
     }
 
+    /** Checks if this profile specifies a LegacyVpn type. */
+    public static boolean isLegacyType(int type) {
+        switch (type) {
+            case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS: // fall through
+            case VpnProfile.TYPE_IKEV2_IPSEC_RSA: // fall through
+            case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
+                return false;
+            default:
+                return true;
+        }
+    }
+
+    private boolean isValidLockdownLegacyVpnProfile() {
+        return isLegacyType(type) && isServerAddressNumeric() && hasDns()
+                && areDnsAddressesNumeric();
+    }
+
+    private boolean isValidLockdownPlatformVpnProfile() {
+        return Ikev2VpnProfile.isValidVpnProfile(this);
+    }
+
     /**
-     * Tests if profile is valid for lockdown, which requires IPv4 address for both server and DNS.
-     * Server hostnames would require using DNS before connection.
+     * Tests if profile is valid for lockdown.
+     *
+     * <p>For LegacyVpn profiles, this requires an IPv4 address for both the server and DNS.
+     *
+     * <p>For PlatformVpn profiles, this requires a server, an identifier and the relevant fields to
+     * be non-null.
      */
     public boolean isValidLockdownProfile() {
         return isTypeValidForLockdown()
-                && isServerAddressNumeric()
-                && hasDns()
-                && areDnsAddressesNumeric();
+                && (isValidLockdownLegacyVpnProfile() || isValidLockdownPlatformVpnProfile());
     }
 
     /** Returns {@code true} if the VPN type is valid for lockdown. */
diff --git a/core/java/com/android/internal/os/KernelCpuUidBpfMapReader.java b/core/java/com/android/internal/os/KernelCpuUidBpfMapReader.java
new file mode 100644
index 0000000..26f81d9
--- /dev/null
+++ b/core/java/com/android/internal/os/KernelCpuUidBpfMapReader.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2020 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.os;
+
+import android.os.StrictMode;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * Reads cpu time bpf maps.
+ *
+ * It is implemented as singletons for each separate set of per-UID times. Get___Instance() method
+ * returns the corresponding reader instance. In order to prevent frequent GC, it reuses the same
+ * SparseArray to store data read from BPF maps.
+ *
+ * A KernelCpuUidBpfMapReader instance keeps an error counter. When the number of read errors within
+ * that instance accumulates to 5, this instance will reject all further read requests.
+ *
+ * Data fetched within last 500ms is considered fresh, since the reading lifecycle can take up to
+ * 25ms. KernelCpuUidBpfMapReader always tries to use cache if it is fresh and valid, but it can
+ * be disabled through a parameter.
+ *
+ * A KernelCpuUidBpfMapReader instance is thread-safe. It acquires a write lock when reading the bpf
+ * map, releases it right after, then acquires a read lock before returning a BpfMapIterator. Caller
+ * is responsible for closing BpfMapIterator (also auto-closable) after reading, otherwise deadlock
+ * will occur.
+ */
+public abstract class KernelCpuUidBpfMapReader {
+    private static final int ERROR_THRESHOLD = 5;
+    private static final long FRESHNESS_MS = 500L;
+
+    private static final KernelCpuUidBpfMapReader FREQ_TIME_READER =
+        new KernelCpuUidFreqTimeBpfMapReader();
+
+    private static final KernelCpuUidBpfMapReader ACTIVE_TIME_READER =
+        new KernelCpuUidActiveTimeBpfMapReader();
+
+    private static final KernelCpuUidBpfMapReader CLUSTER_TIME_READER =
+        new KernelCpuUidClusterTimeBpfMapReader();
+
+    static KernelCpuUidBpfMapReader getFreqTimeReaderInstance() {
+        return FREQ_TIME_READER;
+    }
+
+    static KernelCpuUidBpfMapReader getActiveTimeReaderInstance() {
+        return ACTIVE_TIME_READER;
+    }
+
+    static KernelCpuUidBpfMapReader getClusterTimeReaderInstance() {
+        return CLUSTER_TIME_READER;
+    }
+
+    final String mTag = this.getClass().getSimpleName();
+    private int mErrors = 0;
+    private boolean mTracking = false;
+    protected SparseArray<long[]> mData = new SparseArray<>();
+    private long mLastReadTime = 0;
+    protected final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock();
+    protected final ReentrantReadWriteLock.ReadLock mReadLock = mLock.readLock();
+    protected final ReentrantReadWriteLock.WriteLock mWriteLock = mLock.writeLock();
+
+    public native boolean startTrackingBpfTimes();
+
+    protected abstract boolean readBpfData();
+
+    /**
+     * Returns an array of metadata used to inform the caller of 1) the size of array required by
+     * getNextUid and 2) how to interpret the raw data copied to that array.
+     */
+    public abstract long[] getDataDimensions();
+
+    public void removeUidsInRange(int startUid, int endUid) {
+        if (mErrors > ERROR_THRESHOLD) {
+            return;
+        }
+        mWriteLock.lock();
+        int firstIndex = mData.indexOfKey(startUid);
+        int lastIndex = mData.indexOfKey(endUid);
+        mData.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
+        mWriteLock.unlock();
+    }
+
+    public BpfMapIterator open() {
+        return open(false);
+    }
+
+    public BpfMapIterator open(boolean ignoreCache) {
+        if (mErrors > ERROR_THRESHOLD) {
+            return null;
+        }
+        if (!mTracking && !startTrackingBpfTimes()) {
+            Slog.w(mTag, "Failed to start tracking");
+            mErrors++;
+            return null;
+        }
+        if (ignoreCache) {
+            mWriteLock.lock();
+        } else {
+            mReadLock.lock();
+            if (dataValid()) {
+                return new BpfMapIterator();
+            }
+            mReadLock.unlock();
+            mWriteLock.lock();
+            if (dataValid()) {
+                mReadLock.lock();
+                mWriteLock.unlock();
+                return new BpfMapIterator();
+            }
+        }
+        if (readBpfData()) {
+            mLastReadTime = SystemClock.elapsedRealtime();
+            mReadLock.lock();
+            mWriteLock.unlock();
+            return new BpfMapIterator();
+        }
+
+        mWriteLock.unlock();
+        mErrors++;
+        Slog.w(mTag, "Failed to read bpf times");
+        return null;
+    }
+
+    private boolean dataValid() {
+        return mData.size() > 0 && (SystemClock.elapsedRealtime() - mLastReadTime < FRESHNESS_MS);
+    }
+
+    public class BpfMapIterator implements AutoCloseable {
+        private int mPos;
+
+        public BpfMapIterator() {
+        };
+
+        public boolean getNextUid(long[] buf) {
+            if (mPos >= mData.size()) {
+                return false;
+            }
+            buf[0] = mData.keyAt(mPos);
+            System.arraycopy(mData.valueAt(mPos), 0, buf, 1, mData.valueAt(mPos).length);
+            mPos++;
+            return true;
+        }
+
+        public void close() {
+            mReadLock.unlock();
+        }
+    }
+
+    public static class KernelCpuUidFreqTimeBpfMapReader extends KernelCpuUidBpfMapReader {
+
+        private final native boolean removeUidRange(int startUid, int endUid);
+
+        @Override
+        protected final native boolean readBpfData();
+
+        @Override
+        public final native long[] getDataDimensions();
+
+        @Override
+        public void removeUidsInRange(int startUid, int endUid) {
+            mWriteLock.lock();
+            super.removeUidsInRange(startUid, endUid);
+            removeUidRange(startUid, endUid);
+            mWriteLock.unlock();
+        }
+    }
+
+    public static class KernelCpuUidActiveTimeBpfMapReader extends KernelCpuUidBpfMapReader {
+
+        @Override
+        protected final native boolean readBpfData();
+
+        @Override
+        public final native long[] getDataDimensions();
+    }
+
+    public static class KernelCpuUidClusterTimeBpfMapReader extends KernelCpuUidBpfMapReader {
+
+        @Override
+        protected final native boolean readBpfData();
+
+        @Override
+        public final native long[] getDataDimensions();
+    }
+}
diff --git a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
index e6d044f..34e75fe 100644
--- a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
+++ b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
@@ -28,6 +28,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.KernelCpuProcStringReader.ProcFileIterator;
+import com.android.internal.os.KernelCpuUidBpfMapReader.BpfMapIterator;
 
 import java.io.BufferedReader;
 import java.io.FileWriter;
@@ -57,6 +58,8 @@
     final SparseArray<T> mLastTimes = new SparseArray<>();
     final KernelCpuProcStringReader mReader;
     final boolean mThrottle;
+    protected boolean mBpfTimesAvailable;
+    final KernelCpuUidBpfMapReader mBpfReader;
     private long mMinTimeBetweenRead = DEFAULT_MIN_TIME_BETWEEN_READ;
     private long mLastReadTimeMs = 0;
 
@@ -73,9 +76,15 @@
         void onUidCpuTime(int uid, T time);
     }
 
-    KernelCpuUidTimeReader(KernelCpuProcStringReader reader, boolean throttle) {
+    KernelCpuUidTimeReader(KernelCpuProcStringReader reader, @Nullable KernelCpuUidBpfMapReader bpfReader, boolean throttle) {
         mReader = reader;
         mThrottle = throttle;
+        mBpfReader = bpfReader;
+        mBpfTimesAvailable = (mBpfReader != null);
+    }
+
+    KernelCpuUidTimeReader(KernelCpuProcStringReader reader, boolean throttle) {
+        this(reader, null, throttle);
     }
 
     /**
@@ -151,9 +160,13 @@
         }
         mLastTimes.put(startUid, null);
         mLastTimes.put(endUid, null);
-        final int firstIndex = mLastTimes.indexOfKey(startUid);
-        final int lastIndex = mLastTimes.indexOfKey(endUid);
+        int firstIndex = mLastTimes.indexOfKey(startUid);
+        int lastIndex = mLastTimes.indexOfKey(endUid);
         mLastTimes.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
+
+        if (mBpfTimesAvailable) {
+            mBpfReader.removeUidsInRange(startUid, endUid);
+        }
     }
 
     /**
@@ -323,13 +336,13 @@
 
         public KernelCpuUidFreqTimeReader(boolean throttle) {
             this(UID_TIMES_PROC_FILE, KernelCpuProcStringReader.getFreqTimeReaderInstance(),
-                    throttle);
+                 KernelCpuUidBpfMapReader.getFreqTimeReaderInstance(), throttle);
         }
 
         @VisibleForTesting
         public KernelCpuUidFreqTimeReader(String procFile, KernelCpuProcStringReader reader,
-                boolean throttle) {
-            super(reader, throttle);
+                KernelCpuUidBpfMapReader bpfReader, boolean throttle) {
+            super(reader, bpfReader, throttle);
             mProcFilePath = Paths.get(procFile);
         }
 
@@ -370,19 +383,24 @@
             if (!mAllUidTimesAvailable) {
                 return null;
             }
-            final int oldMask = StrictMode.allowThreadDiskReadsMask();
-            try (BufferedReader reader = Files.newBufferedReader(mProcFilePath)) {
-                if (readFreqs(reader.readLine()) == null) {
+            if (mBpfTimesAvailable) {
+                readFreqsThroughBpf();
+            }
+            if (mCpuFreqs == null) {
+                final int oldMask = StrictMode.allowThreadDiskReadsMask();
+                try (BufferedReader reader = Files.newBufferedReader(mProcFilePath)) {
+                    if (readFreqs(reader.readLine()) == null) {
+                        return null;
+                    }
+                } catch (IOException e) {
+                    if (++mErrors >= MAX_ERROR_COUNT) {
+                        mAllUidTimesAvailable = false;
+                    }
+                    Slog.e(mTag, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
                     return null;
+                } finally {
+                    StrictMode.setThreadPolicyMask(oldMask);
                 }
-            } catch (IOException e) {
-                if (++mErrors >= MAX_ERROR_COUNT) {
-                    mAllUidTimesAvailable = false;
-                }
-                Slog.e(mTag, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
-                return null;
-            } finally {
-                StrictMode.setThreadPolicyMask(oldMask);
             }
             // Check if the freqs in the proc file correspond to per-cluster freqs.
             final IntArray numClusterFreqs = extractClusterInfoFromProcFileFreqs();
@@ -402,6 +420,21 @@
             return mCpuFreqs;
         }
 
+        private long[] readFreqsThroughBpf() {
+            if (!mBpfTimesAvailable || mBpfReader == null) {
+                return null;
+            }
+            mCpuFreqs = mBpfReader.getDataDimensions();
+            if (mCpuFreqs == null) {
+                return null;
+            }
+            mFreqCount = mCpuFreqs.length;
+            mCurTimes = new long[mFreqCount];
+            mDeltaTimes = new long[mFreqCount];
+            mBuffer = new long[mFreqCount + 1];
+            return mCpuFreqs;
+        }
+
         private long[] readFreqs(String line) {
             if (line == null) {
                 return null;
@@ -422,8 +455,45 @@
             return mCpuFreqs;
         }
 
+        private void processUidDelta(@Nullable Callback<long[]> cb) {
+            final int uid = (int) mBuffer[0];
+            long[] lastTimes = mLastTimes.get(uid);
+            if (lastTimes == null) {
+                lastTimes = new long[mFreqCount];
+                mLastTimes.put(uid, lastTimes);
+            }
+            copyToCurTimes();
+            boolean notify = false;
+            boolean valid = true;
+            for (int i = 0; i < mFreqCount; i++) {
+                // Unit is 10ms.
+                mDeltaTimes[i] = mCurTimes[i] - lastTimes[i];
+                if (mDeltaTimes[i] < 0) {
+                    Slog.e(mTag, "Negative delta from freq time proc: " + mDeltaTimes[i]);
+                    valid = false;
+                }
+                notify |= mDeltaTimes[i] > 0;
+            }
+            if (notify && valid) {
+                System.arraycopy(mCurTimes, 0, lastTimes, 0, mFreqCount);
+                if (cb != null) {
+                    cb.onUidCpuTime(uid, mDeltaTimes);
+                }
+            }
+        }
+
         @Override
         void readDeltaImpl(@Nullable Callback<long[]> cb) {
+            if (mBpfTimesAvailable) {
+                try (BpfMapIterator iter = mBpfReader.open(!mThrottle)) {
+                    if (checkPrecondition(iter)) {
+                        while (iter.getNextUid(mBuffer)) {
+                            processUidDelta(cb);
+                        }
+                        return;
+                    }
+                }
+            }
             try (ProcFileIterator iter = mReader.open(!mThrottle)) {
                 if (!checkPrecondition(iter)) {
                     return;
@@ -434,36 +504,24 @@
                         Slog.wtf(mTag, "Invalid line: " + buf.toString());
                         continue;
                     }
-                    final int uid = (int) mBuffer[0];
-                    long[] lastTimes = mLastTimes.get(uid);
-                    if (lastTimes == null) {
-                        lastTimes = new long[mFreqCount];
-                        mLastTimes.put(uid, lastTimes);
-                    }
-                    copyToCurTimes();
-                    boolean notify = false;
-                    boolean valid = true;
-                    for (int i = 0; i < mFreqCount; i++) {
-                        // Unit is 10ms.
-                        mDeltaTimes[i] = mCurTimes[i] - lastTimes[i];
-                        if (mDeltaTimes[i] < 0) {
-                            Slog.e(mTag, "Negative delta from freq time proc: " + mDeltaTimes[i]);
-                            valid = false;
-                        }
-                        notify |= mDeltaTimes[i] > 0;
-                    }
-                    if (notify && valid) {
-                        System.arraycopy(mCurTimes, 0, lastTimes, 0, mFreqCount);
-                        if (cb != null) {
-                            cb.onUidCpuTime(uid, mDeltaTimes);
-                        }
-                    }
+                    processUidDelta(cb);
                 }
             }
         }
 
         @Override
         void readAbsoluteImpl(Callback<long[]> cb) {
+            if (mBpfTimesAvailable) {
+                try (BpfMapIterator iter = mBpfReader.open(!mThrottle)) {
+                    if (checkPrecondition(iter)) {
+                        while (iter.getNextUid(mBuffer)) {
+                            copyToCurTimes();
+                            cb.onUidCpuTime((int) mBuffer[0], mCurTimes);
+                        }
+                        return;
+                    }
+                }
+            }
             try (ProcFileIterator iter = mReader.open(!mThrottle)) {
                 if (!checkPrecondition(iter)) {
                     return;
@@ -481,11 +539,24 @@
         }
 
         private void copyToCurTimes() {
+            long factor = mBpfTimesAvailable ? 1 : 10;
             for (int i = 0; i < mFreqCount; i++) {
-                mCurTimes[i] = mBuffer[i + 1] * 10;
+                mCurTimes[i] = mBuffer[i + 1] * factor;
             }
         }
 
+        private boolean checkPrecondition(BpfMapIterator iter) {
+            if (iter == null) {
+                mBpfTimesAvailable = false;
+                return false;
+            }
+            if (mCpuFreqs != null) {
+                return true;
+            }
+            mBpfTimesAvailable = (readFreqsThroughBpf() != null);
+            return mBpfTimesAvailable;
+        }
+
         private boolean checkPrecondition(ProcFileIterator iter) {
             if (iter == null || !iter.hasNextLine()) {
                 // Error logged in KernelCpuProcStringReader.
@@ -544,16 +615,43 @@
         private long[] mBuffer;
 
         public KernelCpuUidActiveTimeReader(boolean throttle) {
-            super(KernelCpuProcStringReader.getActiveTimeReaderInstance(), throttle);
+            super(KernelCpuProcStringReader.getActiveTimeReaderInstance(),
+                  KernelCpuUidBpfMapReader.getActiveTimeReaderInstance(), throttle);
         }
 
         @VisibleForTesting
-        public KernelCpuUidActiveTimeReader(KernelCpuProcStringReader reader, boolean throttle) {
-            super(reader, throttle);
+        public KernelCpuUidActiveTimeReader(KernelCpuProcStringReader reader, KernelCpuUidBpfMapReader bpfReader, boolean throttle) {
+            super(reader, bpfReader, throttle);
+        }
+
+        private void processUidDelta(@Nullable Callback<Long> cb) {
+            int uid = (int) mBuffer[0];
+            long cpuActiveTime = sumActiveTime(mBuffer, mBpfTimesAvailable ? 1 : 10);
+            if (cpuActiveTime > 0) {
+                long delta = cpuActiveTime - mLastTimes.get(uid, 0L);
+                if (delta > 0) {
+                    mLastTimes.put(uid, cpuActiveTime);
+                    if (cb != null) {
+                        cb.onUidCpuTime(uid, delta);
+                    }
+                } else if (delta < 0) {
+                    Slog.e(mTag, "Negative delta from active time proc: " + delta);
+                }
+            }
         }
 
         @Override
         void readDeltaImpl(@Nullable Callback<Long> cb) {
+            if (mBpfTimesAvailable) {
+                try (BpfMapIterator iter = mBpfReader.open(!mThrottle)) {
+                    if (checkPrecondition(iter)) {
+                        while (iter.getNextUid(mBuffer)) {
+                            processUidDelta(cb);
+                        }
+                        return;
+                    }
+                }
+            }
             try (ProcFileIterator iter = mReader.open(!mThrottle)) {
                 if (!checkPrecondition(iter)) {
                     return;
@@ -564,25 +662,30 @@
                         Slog.wtf(mTag, "Invalid line: " + buf.toString());
                         continue;
                     }
-                    int uid = (int) mBuffer[0];
-                    long cpuActiveTime = sumActiveTime(mBuffer);
-                    if (cpuActiveTime > 0) {
-                        long delta = cpuActiveTime - mLastTimes.get(uid, 0L);
-                        if (delta > 0) {
-                            mLastTimes.put(uid, cpuActiveTime);
-                            if (cb != null) {
-                                cb.onUidCpuTime(uid, delta);
-                            }
-                        } else if (delta < 0) {
-                            Slog.e(mTag, "Negative delta from active time proc: " + delta);
-                        }
-                    }
+                    processUidDelta(cb);
                 }
             }
         }
 
+        private void processUidAbsolute(@Nullable Callback<Long> cb) {
+            long cpuActiveTime = sumActiveTime(mBuffer, mBpfTimesAvailable ? 1 : 10);
+            if (cpuActiveTime > 0) {
+                cb.onUidCpuTime((int) mBuffer[0], cpuActiveTime);
+            }
+        }
+
         @Override
         void readAbsoluteImpl(Callback<Long> cb) {
+            if (mBpfTimesAvailable) {
+                try (BpfMapIterator iter = mBpfReader.open(!mThrottle)) {
+                    if (checkPrecondition(iter)) {
+                        while (iter.getNextUid(mBuffer)) {
+                            processUidAbsolute(cb);
+                        }
+                        return;
+                    }
+                }
+            }
             try (ProcFileIterator iter = mReader.open(!mThrottle)) {
                 if (!checkPrecondition(iter)) {
                     return;
@@ -593,23 +696,38 @@
                         Slog.wtf(mTag, "Invalid line: " + buf.toString());
                         continue;
                     }
-                    long cpuActiveTime = sumActiveTime(mBuffer);
-                    if (cpuActiveTime > 0) {
-                        cb.onUidCpuTime((int) mBuffer[0], cpuActiveTime);
-                    }
+                    processUidAbsolute(cb);
                 }
             }
         }
 
-        private static long sumActiveTime(long[] times) {
+        private static long sumActiveTime(long[] times, double factor) {
             // UID is stored at times[0].
             double sum = 0;
             for (int i = 1; i < times.length; i++) {
-                sum += (double) times[i] * 10 / i; // Unit is 10ms.
+                sum += (double) times[i] * factor / i; // Unit is 10ms.
             }
             return (long) sum;
         }
 
+        private boolean checkPrecondition(BpfMapIterator iter) {
+            if (iter == null) {
+                mBpfTimesAvailable = false;
+                return false;
+            }
+            if (mCores > 0) {
+                return true;
+            }
+            long[] cores = mBpfReader.getDataDimensions();
+            if (cores == null || cores.length < 1) {
+                mBpfTimesAvailable = false;
+                return false;
+            }
+            mCores = (int) cores[0];
+            mBuffer = new long[mCores + 1];
+            return true;
+        }
+
         private boolean checkPrecondition(ProcFileIterator iter) {
             if (iter == null || !iter.hasNextLine()) {
                 // Error logged in KernelCpuProcStringReader.
@@ -664,16 +782,54 @@
         private long[] mDeltaTime;
 
         public KernelCpuUidClusterTimeReader(boolean throttle) {
-            super(KernelCpuProcStringReader.getClusterTimeReaderInstance(), throttle);
+            super(KernelCpuProcStringReader.getClusterTimeReaderInstance(),
+                  KernelCpuUidBpfMapReader.getClusterTimeReaderInstance(), throttle);
         }
 
         @VisibleForTesting
-        public KernelCpuUidClusterTimeReader(KernelCpuProcStringReader reader, boolean throttle) {
-            super(reader, throttle);
+        public KernelCpuUidClusterTimeReader(KernelCpuProcStringReader reader,
+                                             KernelCpuUidBpfMapReader bpfReader, boolean throttle) {
+            super(reader, bpfReader, throttle);
+        }
+
+        void processUidDelta(@Nullable Callback<long[]> cb) {
+            int uid = (int) mBuffer[0];
+            long[] lastTimes = mLastTimes.get(uid);
+            if (lastTimes == null) {
+                lastTimes = new long[mNumClusters];
+                mLastTimes.put(uid, lastTimes);
+            }
+            sumClusterTime();
+            boolean valid = true;
+            boolean notify = false;
+            for (int i = 0; i < mNumClusters; i++) {
+                mDeltaTime[i] = mCurTime[i] - lastTimes[i];
+                if (mDeltaTime[i] < 0) {
+                    Slog.e(mTag, "Negative delta from cluster time proc: " + mDeltaTime[i]);
+                    valid = false;
+                }
+                notify |= mDeltaTime[i] > 0;
+            }
+            if (notify && valid) {
+                System.arraycopy(mCurTime, 0, lastTimes, 0, mNumClusters);
+                if (cb != null) {
+                    cb.onUidCpuTime(uid, mDeltaTime);
+                }
+            }
         }
 
         @Override
         void readDeltaImpl(@Nullable Callback<long[]> cb) {
+            if (mBpfTimesAvailable) {
+                try (BpfMapIterator iter = mBpfReader.open(!mThrottle)) {
+                    if (checkPrecondition(iter)) {
+                        while (iter.getNextUid(mBuffer)) {
+                            processUidDelta(cb);
+                        }
+                        return;
+                    }
+                }
+            }
             try (ProcFileIterator iter = mReader.open(!mThrottle)) {
                 if (!checkPrecondition(iter)) {
                     return;
@@ -684,35 +840,24 @@
                         Slog.wtf(mTag, "Invalid line: " + buf.toString());
                         continue;
                     }
-                    int uid = (int) mBuffer[0];
-                    long[] lastTimes = mLastTimes.get(uid);
-                    if (lastTimes == null) {
-                        lastTimes = new long[mNumClusters];
-                        mLastTimes.put(uid, lastTimes);
-                    }
-                    sumClusterTime();
-                    boolean valid = true;
-                    boolean notify = false;
-                    for (int i = 0; i < mNumClusters; i++) {
-                        mDeltaTime[i] = mCurTime[i] - lastTimes[i];
-                        if (mDeltaTime[i] < 0) {
-                            Slog.e(mTag, "Negative delta from cluster time proc: " + mDeltaTime[i]);
-                            valid = false;
-                        }
-                        notify |= mDeltaTime[i] > 0;
-                    }
-                    if (notify && valid) {
-                        System.arraycopy(mCurTime, 0, lastTimes, 0, mNumClusters);
-                        if (cb != null) {
-                            cb.onUidCpuTime(uid, mDeltaTime);
-                        }
-                    }
+                    processUidDelta(cb);
                 }
             }
         }
 
         @Override
         void readAbsoluteImpl(Callback<long[]> cb) {
+            if (mBpfTimesAvailable) {
+                try (BpfMapIterator iter = mBpfReader.open(!mThrottle)) {
+                    if (checkPrecondition(iter)) {
+                        while (iter.getNextUid(mBuffer)) {
+                            sumClusterTime();
+                            cb.onUidCpuTime((int) mBuffer[0], mCurTime);
+                        }
+                        return;
+                    }
+                }
+            }
             try (ProcFileIterator iter = mReader.open(!mThrottle)) {
                 if (!checkPrecondition(iter)) {
                     return;
@@ -730,17 +875,45 @@
         }
 
         private void sumClusterTime() {
+            double factor = mBpfTimesAvailable ? 1 : 10;
             // UID is stored at mBuffer[0].
             int core = 1;
             for (int i = 0; i < mNumClusters; i++) {
                 double sum = 0;
                 for (int j = 1; j <= mCoresOnClusters[i]; j++) {
-                    sum += (double) mBuffer[core++] * 10 / j; // Unit is 10ms.
+                    sum += (double) mBuffer[core++] * factor / j; // Unit is 10ms.
                 }
                 mCurTime[i] = (long) sum;
             }
         }
 
+        private boolean checkPrecondition(BpfMapIterator iter) {
+            if (iter == null) {
+                mBpfTimesAvailable = false;
+                return false;
+            }
+            if (mNumClusters > 0) {
+                return true;
+            }
+            long[] coresOnClusters = mBpfReader.getDataDimensions();
+            if (coresOnClusters == null || coresOnClusters.length < 1) {
+                mBpfTimesAvailable = false;
+                return false;
+            }
+            mNumClusters = coresOnClusters.length;
+            mCoresOnClusters = new int[mNumClusters];
+            int cores = 0;
+            for (int i = 0; i < mNumClusters; i++) {
+                mCoresOnClusters[i] = (int) coresOnClusters[i];
+                cores += mCoresOnClusters[i];
+            }
+            mNumCores = cores;
+            mBuffer = new long[cores + 1];
+            mCurTime = new long[mNumClusters];
+            mDeltaTime = new long[mNumClusters];
+            return true;
+        }
+
         private boolean checkPrecondition(ProcFileIterator iter) {
             if (iter == null || !iter.hasNextLine()) {
                 // Error logged in KernelCpuProcStringReader.
diff --git a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
index 3c43a11..fc0ce6f 100644
--- a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
+++ b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
@@ -53,6 +53,8 @@
     private int mReadErrorCounter;
     @GuardedBy("this")
     private boolean mSingleUidCpuTimesAvailable = true;
+    @GuardedBy("this")
+    private boolean mBpfTimesAvailable = true;
     // We use the freq count obtained from /proc/uid_time_in_state to decide how many longs
     // to read from each /proc/uid/<uid>/time_in_state. On the first read, verify if this is
     // correct and if not, set {@link #mSingleUidCpuTimesAvailable} to false. This flag will
@@ -62,6 +64,8 @@
 
     private final Injector mInjector;
 
+    private static final native boolean canReadBpfTimes();
+
     KernelSingleUidTimeReader(int cpuFreqsCount) {
         this(cpuFreqsCount, new Injector());
     }
@@ -83,6 +87,18 @@
             if (!mSingleUidCpuTimesAvailable) {
                 return null;
             }
+            if (mBpfTimesAvailable) {
+                final long[] cpuTimesMs = mInjector.readBpfData(uid);
+                if (cpuTimesMs.length == 0) {
+                    mBpfTimesAvailable = false;
+                } else if (!mCpuFreqsCountVerified && cpuTimesMs.length != mCpuFreqsCount) {
+                    mSingleUidCpuTimesAvailable = false;
+                    return null;
+                } else {
+                    mCpuFreqsCountVerified = true;
+                    return computeDelta(uid, cpuTimesMs);
+                }
+            }
             // Read total cpu times from the proc file.
             final String procFile = new StringBuilder(PROC_FILE_DIR)
                     .append(uid)
@@ -230,6 +246,8 @@
         public byte[] readData(String procFile) throws IOException {
             return Files.readAllBytes(Paths.get(procFile));
         }
+
+        public native long[] readBpfData(int uid);
     }
 
     @VisibleForTesting
diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java
index d3fe582..9d0cb01 100644
--- a/core/java/com/android/internal/os/PowerProfile.java
+++ b/core/java/com/android/internal/os/PowerProfile.java
@@ -393,6 +393,9 @@
     }
 
     public int getNumCoresInCpuCluster(int cluster) {
+        if (cluster < 0 || cluster >= mCpuClusters.length) {
+            return 0; // index out of bound
+        }
         return mCpuClusters[cluster].numCpus;
     }
 
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 43df2e4..872f26d1 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -207,6 +207,8 @@
         "com_android_internal_os_AtomicDirectory.cpp",
         "com_android_internal_os_ClassLoaderFactory.cpp",
         "com_android_internal_os_FuseAppLoop.cpp",
+        "com_android_internal_os_KernelCpuUidBpfMapReader.cpp",
+        "com_android_internal_os_KernelSingleUidTimeReader.cpp",
         "com_android_internal_os_Zygote.cpp",
         "com_android_internal_os_ZygoteInit.cpp",
         "com_android_internal_util_VirtualRefBasePtr.cpp",
@@ -303,6 +305,7 @@
         "libdl",
         "libdl_android",
         "libstatslog",
+        "libtimeinstate",
         "server_configurable_flags",
     ],
 
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 5c3640e..a2a6716 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -230,6 +230,8 @@
 extern int register_com_android_internal_os_AtomicDirectory(JNIEnv *env);
 extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env);
 extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env);
+extern int register_com_android_internal_os_KernelCpuUidBpfMapReader(JNIEnv *env);
+extern int register_com_android_internal_os_KernelSingleUidTimeReader(JNIEnv *env);
 extern int register_com_android_internal_os_Zygote(JNIEnv *env);
 extern int register_com_android_internal_os_ZygoteInit(JNIEnv *env);
 extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env);
@@ -1640,6 +1642,8 @@
     REG_JNI(register_com_android_internal_content_NativeLibraryHelper),
     REG_JNI(register_com_android_internal_os_AtomicDirectory),
     REG_JNI(register_com_android_internal_os_FuseAppLoop),
+    REG_JNI(register_com_android_internal_os_KernelCpuUidBpfMapReader),
+    REG_JNI(register_com_android_internal_os_KernelSingleUidTimeReader),
 };
 
 /*
diff --git a/core/jni/com_android_internal_os_KernelCpuUidBpfMapReader.cpp b/core/jni/com_android_internal_os_KernelCpuUidBpfMapReader.cpp
new file mode 100644
index 0000000..7c68de5
--- /dev/null
+++ b/core/jni/com_android_internal_os_KernelCpuUidBpfMapReader.cpp
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#include "core_jni_helpers.h"
+
+#include <sys/sysinfo.h>
+
+#include <android-base/stringprintf.h>
+#include <cputimeinstate.h>
+
+namespace android {
+
+static constexpr uint64_t NSEC_PER_MSEC = 1000000;
+
+static struct {
+    jclass clazz;
+    jmethodID put;
+    jmethodID get;
+} gSparseArrayClassInfo;
+
+static jfieldID gmData;
+
+static jlongArray getUidArray(JNIEnv *env, jobject sparseAr, uint32_t uid, jsize sz) {
+    jlongArray ar = (jlongArray)env->CallObjectMethod(sparseAr, gSparseArrayClassInfo.get, uid);
+    if (!ar) {
+        ar = env->NewLongArray(sz);
+        if (ar == NULL) return ar;
+        env->CallVoidMethod(sparseAr, gSparseArrayClassInfo.put, uid, ar);
+    }
+    return ar;
+}
+
+static void copy2DVecToArray(JNIEnv *env, jlongArray ar, std::vector<std::vector<uint64_t>> &vec) {
+    jsize start = 0;
+    for (auto &subVec : vec) {
+        for (uint32_t i = 0; i < subVec.size(); ++i) subVec[i] /= NSEC_PER_MSEC;
+        env->SetLongArrayRegion(ar, start, subVec.size(),
+                                reinterpret_cast<const jlong *>(subVec.data()));
+        start += subVec.size();
+    }
+}
+
+static jboolean KernelCpuUidFreqTimeBpfMapReader_removeUidRange(JNIEnv *env, jclass, jint startUid,
+                                                                jint endUid) {
+    for (uint32_t uid = startUid; uid <= endUid; ++uid) {
+        if (!android::bpf::clearUidTimes(uid)) return false;
+    }
+    return true;
+}
+
+static jboolean KernelCpuUidFreqTimeBpfMapReader_readBpfData(JNIEnv *env, jobject thiz) {
+    static uint64_t lastUpdate = 0;
+    uint64_t newLastUpdate = lastUpdate;
+    auto sparseAr = env->GetObjectField(thiz, gmData);
+    if (sparseAr == NULL) return false;
+    auto data = android::bpf::getUidsUpdatedCpuFreqTimes(&newLastUpdate);
+    if (!data.has_value()) return false;
+
+    jsize s = 0;
+    for (auto &[uid, times] : *data) {
+        if (s == 0) {
+            for (const auto &subVec : times) s += subVec.size();
+        }
+        jlongArray ar = getUidArray(env, sparseAr, uid, s);
+        if (ar == NULL) return false;
+        copy2DVecToArray(env, ar, times);
+    }
+    lastUpdate = newLastUpdate;
+    return true;
+}
+
+static jlongArray KernelCpuUidFreqTimeBpfMapReader_getDataDimensions(JNIEnv *env, jobject) {
+    auto freqs = android::bpf::getCpuFreqs();
+    if (!freqs) return NULL;
+
+    std::vector<uint64_t> allFreqs;
+    for (const auto &vec : *freqs) std::copy(vec.begin(), vec.end(), std::back_inserter(allFreqs));
+
+    auto ar = env->NewLongArray(allFreqs.size());
+    if (ar != NULL) {
+        env->SetLongArrayRegion(ar, 0, allFreqs.size(),
+                                reinterpret_cast<const jlong *>(allFreqs.data()));
+    }
+    return ar;
+}
+
+static const JNINativeMethod gFreqTimeMethods[] = {
+        {"removeUidRange", "(II)Z", (void *)KernelCpuUidFreqTimeBpfMapReader_removeUidRange},
+        {"readBpfData", "()Z", (void *)KernelCpuUidFreqTimeBpfMapReader_readBpfData},
+        {"getDataDimensions", "()[J", (void *)KernelCpuUidFreqTimeBpfMapReader_getDataDimensions},
+};
+
+static jboolean KernelCpuUidActiveTimeBpfMapReader_readBpfData(JNIEnv *env, jobject thiz) {
+    static uint64_t lastUpdate = 0;
+    uint64_t newLastUpdate = lastUpdate;
+    auto sparseAr = env->GetObjectField(thiz, gmData);
+    if (sparseAr == NULL) return false;
+    auto data = android::bpf::getUidsUpdatedConcurrentTimes(&newLastUpdate);
+    if (!data.has_value()) return false;
+
+    for (auto &[uid, times] : *data) {
+        // TODO: revise calling code so we can divide by NSEC_PER_MSEC here instead
+        for (auto &time : times.active) time /= NSEC_PER_MSEC;
+        jlongArray ar = getUidArray(env, sparseAr, uid, times.active.size());
+        if (ar == NULL) return false;
+        env->SetLongArrayRegion(ar, 0, times.active.size(),
+                                reinterpret_cast<const jlong *>(times.active.data()));
+    }
+    lastUpdate = newLastUpdate;
+    return true;
+}
+
+static jlongArray KernelCpuUidActiveTimeBpfMapReader_getDataDimensions(JNIEnv *env, jobject) {
+    jlong nCpus = get_nprocs_conf();
+
+    auto ar = env->NewLongArray(1);
+    if (ar != NULL) env->SetLongArrayRegion(ar, 0, 1, &nCpus);
+    return ar;
+}
+
+static const JNINativeMethod gActiveTimeMethods[] = {
+        {"readBpfData", "()Z", (void *)KernelCpuUidActiveTimeBpfMapReader_readBpfData},
+        {"getDataDimensions", "()[J", (void *)KernelCpuUidActiveTimeBpfMapReader_getDataDimensions},
+};
+
+static jboolean KernelCpuUidClusterTimeBpfMapReader_readBpfData(JNIEnv *env, jobject thiz) {
+    static uint64_t lastUpdate = 0;
+    uint64_t newLastUpdate = lastUpdate;
+    auto sparseAr = env->GetObjectField(thiz, gmData);
+    if (sparseAr == NULL) return false;
+    auto data = android::bpf::getUidsUpdatedConcurrentTimes(&newLastUpdate);
+    if (!data.has_value()) return false;
+
+    jsize s = 0;
+    for (auto &[uid, times] : *data) {
+        if (s == 0) {
+            for (const auto &subVec : times.policy) s += subVec.size();
+        }
+        jlongArray ar = getUidArray(env, sparseAr, uid, s);
+        if (ar == NULL) return false;
+        copy2DVecToArray(env, ar, times.policy);
+    }
+    lastUpdate = newLastUpdate;
+    return true;
+}
+
+static jlongArray KernelCpuUidClusterTimeBpfMapReader_getDataDimensions(JNIEnv *env, jobject) {
+    auto times = android::bpf::getUidConcurrentTimes(0);
+    if (!times.has_value()) return NULL;
+
+    std::vector<jlong> clusterCores;
+    for (const auto &vec : times->policy) clusterCores.push_back(vec.size());
+    auto ar = env->NewLongArray(clusterCores.size());
+    if (ar != NULL) env->SetLongArrayRegion(ar, 0, clusterCores.size(), clusterCores.data());
+    return ar;
+}
+
+static const JNINativeMethod gClusterTimeMethods[] = {
+        {"readBpfData", "()Z", (void *)KernelCpuUidClusterTimeBpfMapReader_readBpfData},
+        {"getDataDimensions", "()[J",
+         (void *)KernelCpuUidClusterTimeBpfMapReader_getDataDimensions},
+};
+
+struct readerMethods {
+    const char *name;
+    const JNINativeMethod *methods;
+    int numMethods;
+};
+
+static const readerMethods gAllMethods[] = {
+        {"KernelCpuUidFreqTimeBpfMapReader", gFreqTimeMethods, NELEM(gFreqTimeMethods)},
+        {"KernelCpuUidActiveTimeBpfMapReader", gActiveTimeMethods, NELEM(gActiveTimeMethods)},
+        {"KernelCpuUidClusterTimeBpfMapReader", gClusterTimeMethods, NELEM(gClusterTimeMethods)},
+};
+
+static jboolean KernelCpuUidBpfMapReader_startTrackingBpfTimes(JNIEnv *, jobject) {
+    return android::bpf::startTrackingUidTimes();
+}
+
+int register_com_android_internal_os_KernelCpuUidBpfMapReader(JNIEnv *env) {
+    gSparseArrayClassInfo.clazz = FindClassOrDie(env, "android/util/SparseArray");
+    gSparseArrayClassInfo.clazz = MakeGlobalRefOrDie(env, gSparseArrayClassInfo.clazz);
+    gSparseArrayClassInfo.put =
+            GetMethodIDOrDie(env, gSparseArrayClassInfo.clazz, "put", "(ILjava/lang/Object;)V");
+    gSparseArrayClassInfo.get =
+            GetMethodIDOrDie(env, gSparseArrayClassInfo.clazz, "get", "(I)Ljava/lang/Object;");
+    constexpr auto readerName = "com/android/internal/os/KernelCpuUidBpfMapReader";
+    constexpr JNINativeMethod method = {"startTrackingBpfTimes", "()Z",
+                                        (void *)KernelCpuUidBpfMapReader_startTrackingBpfTimes};
+
+    int ret = RegisterMethodsOrDie(env, readerName, &method, 1);
+    if (ret < 0) return ret;
+    auto c = FindClassOrDie(env, readerName);
+    gmData = GetFieldIDOrDie(env, c, "mData", "Landroid/util/SparseArray;");
+
+    for (const auto &m : gAllMethods) {
+        auto fullName = android::base::StringPrintf("%s$%s", readerName, m.name);
+        ret = RegisterMethodsOrDie(env, fullName.c_str(), m.methods, m.numMethods);
+        if (ret < 0) break;
+    }
+    return ret;
+}
+
+} // namespace android
diff --git a/core/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp b/core/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp
new file mode 100644
index 0000000..c0ecf33
--- /dev/null
+++ b/core/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#include "core_jni_helpers.h"
+
+#include <cputimeinstate.h>
+
+namespace android {
+
+static constexpr uint64_t NSEC_PER_MSEC = 1000000;
+
+static jlongArray copyVecsToArray(JNIEnv *env, std::vector<std::vector<uint64_t>> &vec) {
+    jsize s = 0;
+    for (const auto &subVec : vec) s += subVec.size();
+    jlongArray ar = env->NewLongArray(s);
+    jsize start = 0;
+    for (auto &subVec : vec) {
+        for (uint32_t i = 0; i < subVec.size(); ++i) subVec[i] /= NSEC_PER_MSEC;
+        env->SetLongArrayRegion(ar, start, subVec.size(),
+                                reinterpret_cast<const jlong*>(subVec.data()));
+        start += subVec.size();
+    }
+    return ar;
+}
+
+static jlongArray getUidCpuFreqTimeMs(JNIEnv *env, jclass, jint uid) {
+    auto out = android::bpf::getUidCpuFreqTimes(uid);
+    if (!out) return env->NewLongArray(0);
+    return copyVecsToArray(env, out.value());
+}
+
+static const JNINativeMethod g_single_methods[] = {
+    {"readBpfData", "(I)[J", (void *)getUidCpuFreqTimeMs},
+};
+
+int register_com_android_internal_os_KernelSingleUidTimeReader(JNIEnv *env) {
+    return RegisterMethodsOrDie(env, "com/android/internal/os/KernelSingleUidTimeReader$Injector",
+                                g_single_methods, NELEM(g_single_methods));
+}
+
+}
diff --git a/core/proto/android/stats/dnsresolver/dns_resolver.proto b/core/proto/android/stats/dnsresolver/dns_resolver.proto
index 9eaabfb..15ef122 100644
--- a/core/proto/android/stats/dnsresolver/dns_resolver.proto
+++ b/core/proto/android/stats/dnsresolver/dns_resolver.proto
@@ -169,12 +169,20 @@
     NT_BLUETOOTH = 3;
     // Indicates this network uses an Ethernet transport.
     NT_ETHERNET = 4;
-    // Indicates this network uses a VPN transport.
-    NT_VPN = 5;
+    // Indicates this network uses a VPN transport, now deprecated.
+    NT_VPN = 5 [deprecated=true];
     // Indicates this network uses a Wi-Fi Aware transport.
     NT_WIFI_AWARE = 6;
     // Indicates this network uses a LoWPAN transport.
     NT_LOWPAN = 7;
+    // Indicates this network uses a Cellular+VPN transport.
+    NT_CELLULAR_VPN = 8;
+    // Indicates this network uses a Wi-Fi+VPN transport.
+    NT_WIFI_VPN = 9;
+    // Indicates this network uses a Bluetooth+VPN transport.
+    NT_BLUETOOTH_VPN = 10;
+    // Indicates this network uses an Ethernet+VPN transport.
+    NT_ETHERNET_VPN = 11;
 }
 
 enum CacheStatus{
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index a5f6564..e8cfa27 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1786,6 +1786,9 @@
         android:protectionLevel="normal" />
 
     <!-- @SystemApi Allows an internal user to use privileged SecureElement APIs.
+         Applications holding this permission can access OMAPI reset system API
+         and bypass OMAPI AccessControlEnforcer.
+         <p>Not for use by third-party applications.
          @hide -->
     <permission android:name="android.permission.SECURE_ELEMENT_PRIVILEGED"
         android:protectionLevel="signature|privileged" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 05419558..c52c53f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2718,6 +2718,18 @@
     <string name="config_customAdbPublicKeyConfirmationSecondaryUserComponent"
             >com.android.systemui/com.android.systemui.usb.UsbDebuggingSecondaryUserActivity</string>
 
+    <!-- Name of the activity or service that prompts the user to reject, accept, or whitelist
+         a wireless network for wireless debugging.
+         Can be customized for other product types -->
+    <string name="config_customAdbWifiNetworkConfirmationComponent"
+            >com.android.systemui/com.android.systemui.wifi.WifiDebuggingActivity</string>
+
+    <!-- Name of the activity that prompts the secondary user to acknowledge she/he needs to
+         switch to the primary user to enable wireless debugging.
+         Can be customized for other product types -->
+    <string name="config_customAdbWifiNetworkConfirmationSecondaryUserComponent"
+            >com.android.systemui/com.android.systemui.wifi.WifiDebuggingSecondaryUserActivity</string>
+
     <!-- Name of the dialog that is used to request the user's consent for a Platform VPN -->
     <string name="config_platformVpnConfirmDialogComponent" translatable="false"
             >com.android.vpndialogs/com.android.vpndialogs.PlatformVpnConfirmDialog</string>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 9bc0d96..1758702 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3642,6 +3642,12 @@
     <!-- Message of notification shown when ADB is actively connected to the phone. -->
     <string name="adb_active_notification_message">Tap to turn off USB debugging</string>
     <string name="adb_active_notification_message" product="tv">Select to disable USB debugging.</string>
+    <!-- Title of notification shown when ADB Wireless is actively connected to the phone. [CHAR LIMIT=NONE] -->
+    <string name="adbwifi_active_notification_title">Wireless debugging connected</string>
+    <!-- Message of notification shown when ADB Wireless is actively connected to the phone. [CHAR LIMIT=NONE] -->
+    <string name="adbwifi_active_notification_message">Tap to turn off wireless debugging</string>
+    <!-- Message of notification shown when ADB Wireless is actively connected to the TV. [CHAR LIMIT=NONE] -->
+    <string name="adbwifi_active_notification_message" product="tv">Select to disable wireless debugging.</string>
 
     <!-- Title of notification shown when Test Harness Mode is enabled. [CHAR LIMIT=NONE] -->
     <string name="test_harness_mode_notification_title">Test Harness Mode enabled</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d1e449e..62cc7b3 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2090,6 +2090,8 @@
   <java-symbol type="string" name="accessibility_binding_label" />
   <java-symbol type="string" name="adb_active_notification_message" />
   <java-symbol type="string" name="adb_active_notification_title" />
+  <java-symbol type="string" name="adbwifi_active_notification_message" />
+  <java-symbol type="string" name="adbwifi_active_notification_title" />
   <java-symbol type="string" name="test_harness_mode_notification_title" />
   <java-symbol type="string" name="test_harness_mode_notification_message" />
   <java-symbol type="string" name="taking_remote_bugreport_notification_title" />
@@ -2232,6 +2234,8 @@
   <java-symbol type="fraction" name="config_maximumScreenDimRatio" />
   <java-symbol type="string" name="config_customAdbPublicKeyConfirmationComponent" />
   <java-symbol type="string" name="config_customAdbPublicKeyConfirmationSecondaryUserComponent" />
+  <java-symbol type="string" name="config_customAdbWifiNetworkConfirmationComponent" />
+  <java-symbol type="string" name="config_customAdbWifiNetworkConfirmationSecondaryUserComponent" />
   <java-symbol type="string" name="config_customVpnConfirmDialogComponent" />
   <java-symbol type="string" name="config_customVpnAlwaysOnDisconnectedDialogComponent" />
   <java-symbol type="string" name="config_platformVpnConfirmDialogComponent" />
diff --git a/core/res/res/xml/power_profile.xml b/core/res/res/xml/power_profile.xml
index 899d630..d8ec72f 100644
--- a/core/res/res/xml/power_profile.xml
+++ b/core/res/res/xml/power_profile.xml
@@ -51,6 +51,12 @@
       <value>0.1</value> <!-- ~1mA -->
   </array>
 
+  <!-- Additional power consumption by CPU excluding cluster and core when
+       running -->
+  <array name="cpu.active">
+      <value>0.1</value>
+  </array>
+
   <!-- A list of heterogeneous CPU clusters, where the value for each cluster represents the
        number of CPU cores for that cluster.
 
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index e60e555..4b1691d 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -104,6 +104,7 @@
                     Settings.Global.ADAPTIVE_BATTERY_MANAGEMENT_ENABLED,
                     Settings.Global.ADB_ALLOWED_CONNECTION_TIME,
                     Settings.Global.ADB_ENABLED,
+                    Settings.Global.ADB_WIFI_ENABLED,
                     Settings.Global.ADD_USERS_WHEN_LOCKED,
                     Settings.Global.AIRPLANE_MODE_ON,
                     Settings.Global.AIRPLANE_MODE_RADIOS,
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index a6329298..2ad8e18 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -40,6 +40,7 @@
         BatteryStatsUserLifecycleTests.class,
         KernelCpuProcStringReaderTest.class,
         KernelCpuUidActiveTimeReaderTest.class,
+        KernelCpuUidBpfMapReaderTest.class,
         KernelCpuUidClusterTimeReaderTest.class,
         KernelCpuUidFreqTimeReaderTest.class,
         KernelCpuUidUserSysTimeReaderTest.class,
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidActiveTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidActiveTimeReaderTest.java
index 1b13a99..2ccd74e 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidActiveTimeReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidActiveTimeReaderTest.java
@@ -21,11 +21,11 @@
 
 import android.content.Context;
 import android.os.FileUtils;
+import android.util.SparseArray;
 import android.util.SparseLongArray;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader;
 
@@ -33,11 +33,15 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 import java.io.BufferedWriter;
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.Random;
 
 /**
@@ -46,14 +50,16 @@
  * $ atest FrameworksCoreTests:com.android.internal.os.KernelCpuUidActiveTimeReaderTest
  */
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
 public class KernelCpuUidActiveTimeReaderTest {
     private File mTestDir;
     private File mTestFile;
     private KernelCpuUidActiveTimeReader mReader;
+    private KernelCpuUidTestBpfMapReader mBpfMapReader;
     private VerifiableCallback mCallback;
 
     private Random mRand = new Random(12345);
+    protected boolean mUseBpf;
     private final int mCpus = 4;
     private final String mHeadline = "cpus: 4\n";
     private final int[] mUids = {0, 1, 22, 333, 4444, 55555};
@@ -62,12 +68,22 @@
         return InstrumentationRegistry.getContext();
     }
 
+    @Parameters(name="useBpf={0}")
+    public static Collection<Object[]> data() {
+        return Arrays.asList(new Object[][] { {true}, {false} });
+    }
+
+    public KernelCpuUidActiveTimeReaderTest(boolean useBpf) {
+        mUseBpf = useBpf;
+    }
+
     @Before
     public void setUp() {
         mTestDir = getContext().getDir("test", Context.MODE_PRIVATE);
         mTestFile = new File(mTestDir, "test.file");
+        mBpfMapReader = new KernelCpuUidTestBpfMapReader();
         mReader = new KernelCpuUidActiveTimeReader(
-                new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), false);
+                new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), mBpfMapReader, false);
         mCallback = new VerifiableCallback();
     }
 
@@ -80,7 +96,7 @@
     @Test
     public void testReadDelta() throws Exception {
         final long[][] times = increaseTime(new long[mUids.length][mCpus]);
-        writeToFile(mHeadline + uidLines(mUids, times));
+        setCpusAndData(times);
         mReader.readDelta(mCallback);
         for (int i = 0; i < mUids.length; ++i) {
             mCallback.verify(mUids[i], getActiveTime(times[i]));
@@ -90,7 +106,7 @@
         // Verify that a second call will only return deltas.
         mCallback.clear();
         final long[][] newTimes1 = increaseTime(times);
-        writeToFile(mHeadline + uidLines(mUids, newTimes1));
+        setCpusAndData(newTimes1);
         mReader.readDelta(mCallback);
         for (int i = 0; i < mUids.length; ++i) {
             mCallback.verify(mUids[i], getActiveTime(newTimes1[i]) - getActiveTime(times[i]));
@@ -105,7 +121,7 @@
         // Verify that calling with a null callback doesn't result in any crashes
         mCallback.clear();
         final long[][] newTimes2 = increaseTime(newTimes1);
-        writeToFile(mHeadline + uidLines(mUids, newTimes2));
+        setCpusAndData(newTimes2);
         mReader.readDelta(null);
         mCallback.verifyNoMoreInteractions();
 
@@ -113,19 +129,20 @@
         // the previous call had null callback.
         mCallback.clear();
         final long[][] newTimes3 = increaseTime(newTimes2);
+        setCpusAndData(newTimes3);
         writeToFile(mHeadline + uidLines(mUids, newTimes3));
         mReader.readDelta(mCallback);
         for (int i = 0; i < mUids.length; ++i) {
             mCallback.verify(mUids[i], getActiveTime(newTimes3[i]) - getActiveTime(newTimes2[i]));
         }
         mCallback.verifyNoMoreInteractions();
-        assertTrue(mTestFile.delete());
+        clearCpusAndData();
     }
 
     @Test
     public void testReadAbsolute() throws Exception {
         final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
-        writeToFile(mHeadline + uidLines(mUids, times1));
+        setCpusAndData(times1);
         mReader.readAbsolute(mCallback);
         for (int i = 0; i < mUids.length; i++) {
             mCallback.verify(mUids[i], getActiveTime(times1[i]));
@@ -135,19 +152,19 @@
         // Verify that a second call should still return absolute values
         mCallback.clear();
         final long[][] times2 = increaseTime(times1);
-        writeToFile(mHeadline + uidLines(mUids, times2));
+        setCpusAndData(times2);
         mReader.readAbsolute(mCallback);
         for (int i = 0; i < mUids.length; i++) {
             mCallback.verify(mUids[i], getActiveTime(times2[i]));
         }
         mCallback.verifyNoMoreInteractions();
-        assertTrue(mTestFile.delete());
+        clearCpusAndData();
     }
 
     @Test
     public void testReadDeltaDecreasedTime() throws Exception {
         final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
-        writeToFile(mHeadline + uidLines(mUids, times1));
+        setCpusAndData(times1);
         mReader.readDelta(mCallback);
 
         // Verify that there should not be a callback for a particular UID if its time decreases.
@@ -155,19 +172,19 @@
         final long[][] times2 = increaseTime(times1);
         System.arraycopy(times1[0], 0, times2[0], 0, mCpus);
         times2[0][0] = 100;
-        writeToFile(mHeadline + uidLines(mUids, times2));
+        setCpusAndData(times2);
         mReader.readDelta(mCallback);
         for (int i = 1; i < mUids.length; i++) {
             mCallback.verify(mUids[i], getActiveTime(times2[i]) - getActiveTime(times1[i]));
         }
         mCallback.verifyNoMoreInteractions();
-        assertTrue(mTestFile.delete());
+        clearCpusAndData();
 
         // Verify that the internal state was not modified.
         mCallback.clear();
         final long[][] times3 = increaseTime(times2);
         times3[0] = increaseTime(times1)[0];
-        writeToFile(mHeadline + uidLines(mUids, times3));
+        setCpusAndData(times3);
         mReader.readDelta(mCallback);
         mCallback.verify(mUids[0], getActiveTime(times3[0]) - getActiveTime(times1[0]));
         for (int i = 1; i < mUids.length; i++) {
@@ -179,26 +196,26 @@
     @Test
     public void testReadDeltaNegativeTime() throws Exception {
         final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
-        writeToFile(mHeadline + uidLines(mUids, times1));
+        setCpusAndData(times1);
         mReader.readDelta(mCallback);
 
         // Verify that there should not be a callback for a particular UID if its time is -ve.
         mCallback.clear();
         final long[][] times2 = increaseTime(times1);
         times2[0][0] *= -1;
-        writeToFile(mHeadline + uidLines(mUids, times2));
+        setCpusAndData(times2);
         mReader.readDelta(mCallback);
         for (int i = 1; i < mUids.length; i++) {
             mCallback.verify(mUids[i], getActiveTime(times2[i]) - getActiveTime(times1[i]));
         }
         mCallback.verifyNoMoreInteractions();
-        assertTrue(mTestFile.delete());
+        clearCpusAndData();
 
         // Verify that the internal state was not modified.
         mCallback.clear();
         final long[][] times3 = increaseTime(times2);
         times3[0] = increaseTime(times1)[0];
-        writeToFile(mHeadline + uidLines(mUids, times3));
+        setCpusAndData(times3);
         mReader.readDelta(mCallback);
         mCallback.verify(mUids[0], getActiveTime(times3[0]) - getActiveTime(times1[0]));
         for (int i = 1; i < mUids.length; i++) {
@@ -207,6 +224,28 @@
         mCallback.verifyNoMoreInteractions();
     }
 
+    private void setCpusAndData(long[][] times) throws IOException {
+        if (mUseBpf) {
+            mBpfMapReader.setCpus(new long[]{ mCpus });
+            SparseArray<long[]> data = new SparseArray<>();
+            for (int i = 0; i < mUids.length; i++) {
+                data.put(mUids[i], times[i]);
+            }
+            mBpfMapReader.setData(data);
+        } else {
+            writeToFile(mHeadline + uidLines(mUids, times));
+        }
+    }
+
+    private void clearCpusAndData() {
+        if (mUseBpf) {
+            mBpfMapReader.setCpus(null);
+            mBpfMapReader.setData(new SparseArray<>());
+        } else {
+            assertTrue(mTestFile.delete());
+        }
+    }
+
     private String uidLines(int[] uids, long[][] times) {
         StringBuffer sb = new StringBuffer();
         for (int i = 0; i < uids.length; i++) {
@@ -261,4 +300,36 @@
             assertEquals(0, mData.size());
         }
     }
+
+    private class KernelCpuUidTestBpfMapReader extends KernelCpuUidBpfMapReader {
+        private long[] mCpus;
+        private SparseArray<long[]> mNewData = new SparseArray<>();
+
+        public void setData(SparseArray<long[]> data) {
+            mNewData = data;
+        }
+
+        public void setCpus(long[] cpus) {
+            mCpus = cpus;
+        }
+
+        @Override
+        public final boolean startTrackingBpfTimes() {
+            return true;
+        }
+
+        @Override
+        protected final boolean readBpfData() {
+            if (!mUseBpf) {
+                return false;
+            }
+            mData = mNewData;
+            return true;
+        }
+
+        @Override
+        public final long[] getDataDimensions() {
+            return mCpus;
+        }
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidBpfMapReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidBpfMapReaderTest.java
new file mode 100644
index 0000000..257b388
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidBpfMapReaderTest.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2020 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.os;
+
+import static org.junit.Assert.assertArrayEquals;
+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.content.Context;
+import android.util.SparseArray;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+import org.junit.runner.RunWith;
+
+import com.android.internal.os.KernelCpuUidBpfMapReader.BpfMapIterator;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.IntStream;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class KernelCpuUidBpfMapReaderTest {
+    private Random mRand = new Random(12345);
+    private KernelCpuUidTestBpfMapReader mReader;
+
+    private Context getContext() {
+        return InstrumentationRegistry.getContext();
+    }
+
+    @Before
+    public void setUp() {
+        mReader =  new KernelCpuUidTestBpfMapReader();
+    }
+
+    /**
+     * Tests that reading returns null if readBpfData() fails.
+     */
+    @Test
+    public void testUnsuccessfulRead() {
+        assertEquals(null, mReader.open());
+    }
+
+    /**
+     * Tests that reading will always return null after 5 failures.
+     */
+    @Test
+    public void testReadErrorsLimit() {
+        for (int i = 0; i < 3; i++) {
+            try (BpfMapIterator iter = mReader.open()) {
+                assertNull(iter);
+            }
+        }
+
+        SparseArray<long[]> data = new SparseArray<>();
+        long[] times = {2};
+        data.put(1, new long[]{2});
+        mReader.setData(data);
+        testOpenAndReadData(data);
+
+        mReader.setData(null);
+        for (int i = 0; i < 3; i++) {
+            try (BpfMapIterator iter = mReader.open(true)) {
+                assertNull(iter);
+            }
+        }
+        mReader.setData(data);
+        try (BpfMapIterator iter = mReader.open(true)) {
+            assertNull(iter);
+        }
+    }
+
+    /** Tests getNextUid functionality. */
+    @Test
+    public void testGetNextUid() {
+        final SparseArray<long[]> data = getTestSparseArray(800, 50);
+        mReader.setData(data);
+        testOpenAndReadData(data);
+    }
+
+    @Test
+    public void testConcurrent() throws Exception {
+        final SparseArray<long[]> data = getTestSparseArray(200, 50);
+        final SparseArray<long[]> data1 = getTestSparseArray(180, 70);
+        final List<Throwable> errs = Collections.synchronizedList(new ArrayList<>());
+        mReader.setData(data);
+        // An additional thread for modifying the data.
+        ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(11);
+        final CountDownLatch ready = new CountDownLatch(10);
+        final CountDownLatch start = new CountDownLatch(1);
+        final CountDownLatch modify = new CountDownLatch(1);
+        final CountDownLatch done = new CountDownLatch(10);
+
+        for (int i = 0; i < 5; i++) {
+            threadPool.submit(() -> {
+                    ready.countDown();
+                    try {
+                        start.await();
+                        testOpenAndReadData(data);
+                    } catch (Throwable e) {
+                        errs.add(e);
+                    } finally {
+                        done.countDown();
+                    }
+            });
+            threadPool.submit(() -> {
+                    ready.countDown();
+                    try {
+                        start.await();
+                        // Wait for data modification.
+                        modify.await();
+                        testOpenAndReadData(data1);
+                    } catch (Throwable e) {
+                        errs.add(e);
+                    } finally {
+                        done.countDown();
+                    }
+             });
+        }
+
+        assertTrue("Prep timed out", ready.await(100, TimeUnit.MILLISECONDS));
+        start.countDown();
+
+        threadPool.schedule(() -> {
+                mReader.setData(data1);
+                modify.countDown();
+        }, 600, TimeUnit.MILLISECONDS);
+
+        assertTrue("Execution timed out", done.await(3, TimeUnit.SECONDS));
+        threadPool.shutdownNow();
+
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        errs.forEach(e -> e.printStackTrace(pw));
+
+        assertTrue("All Exceptions:\n" + sw.toString(), errs.isEmpty());
+    }
+
+    @Test
+    public void testRemoveUidsInRange() {
+        final SparseArray<long[]> data = getTestSparseArray(200, 50);
+        mReader.setData(data);
+        testOpenAndReadData(data);
+        SparseArray<long[]> changedData = new SparseArray<>();
+        for (int i = 6; i < 200; i++) {
+            changedData.put(i, data.get(i));
+        }
+        mReader.removeUidsInRange(0, 5);
+        testOpenAndReadData(changedData);
+    }
+
+    private void testOpenAndReadData(SparseArray<long[]> expectedData) {
+        try (BpfMapIterator iter = mReader.open()) {
+            long[] actual;
+            if (expectedData.size() > 0) {
+                actual = new long[expectedData.valueAt(0).length + 1];
+            } else {
+                actual = new long[0];
+            }
+            for (int i = 0; i < expectedData.size(); i++) {
+                assertTrue(iter.getNextUid(actual));
+                assertEquals(expectedData.keyAt(i), actual[0]);
+                assertArrayEquals(expectedData.valueAt(i), Arrays.copyOfRange(actual, 1, actual.length));
+            }
+            assertFalse(iter.getNextUid(actual));
+            assertFalse(iter.getNextUid(actual));
+        }
+    }
+
+
+    private SparseArray<long[]> getTestSparseArray(int uids, int numPerUid) {
+        SparseArray<long[]> data = new SparseArray<>();
+        for (int i = 0; i < uids; i++) {
+            data.put(i, mRand.longs(numPerUid, 0, Long.MAX_VALUE).toArray());
+        }
+        return data;
+    }
+
+
+    private class KernelCpuUidTestBpfMapReader extends KernelCpuUidBpfMapReader {
+        private SparseArray<long[]> mNewData;
+
+        public final void setData(SparseArray<long[]> newData) {
+            mNewData = newData;
+        }
+
+        @Override
+        public final boolean startTrackingBpfTimes() {
+            return true;
+        }
+
+        @Override
+        public final boolean readBpfData() {
+            if (mNewData == null) {
+                return false;
+            }
+            mData = mNewData;
+            return true;
+        }
+
+        @Override
+        public final long[] getDataDimensions() {
+            return null;
+        }
+
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidClusterTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidClusterTimeReaderTest.java
index 2ea80da..a0dab28 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidClusterTimeReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidClusterTimeReaderTest.java
@@ -27,7 +27,6 @@
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader;
 
@@ -35,11 +34,15 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 import java.io.BufferedWriter;
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.Random;
 
 /**
@@ -48,28 +51,42 @@
  * $ atest FrameworksCoreTests:com.android.internal.os.KernelCpuUidClusterTimeReaderTest
  */
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
 public class KernelCpuUidClusterTimeReaderTest {
     private File mTestDir;
     private File mTestFile;
     private KernelCpuUidClusterTimeReader mReader;
+    private KernelCpuUidTestBpfMapReader mBpfMapReader;
     private VerifiableCallback mCallback;
 
     private Random mRand = new Random(12345);
+    protected boolean mUseBpf;
     private final int mCpus = 6;
     private final String mHeadline = "policy0: 4 policy4: 2\n";
+    private final long[] mCores = {4, 2};
     private final int[] mUids = {0, 1, 22, 333, 4444, 55555};
 
     private Context getContext() {
         return InstrumentationRegistry.getContext();
     }
 
+    @Parameters(name="useBpf={0}")
+    public static Collection<Object[]> data() {
+        return Arrays.asList(new Object[][] { {true}, {false} });
+    }
+
+    public KernelCpuUidClusterTimeReaderTest(boolean useBpf) {
+        mUseBpf = useBpf;
+    }
+
     @Before
     public void setUp() {
         mTestDir = getContext().getDir("test", Context.MODE_PRIVATE);
         mTestFile = new File(mTestDir, "test.file");
+        mBpfMapReader = new KernelCpuUidTestBpfMapReader();
         mReader = new KernelCpuUidClusterTimeReader(
-                new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), false);
+                new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), mBpfMapReader,
+                false);
         mCallback = new VerifiableCallback();
     }
 
@@ -82,7 +99,7 @@
     @Test
     public void testReadDelta() throws Exception {
         final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
-        writeToFile(mHeadline + uidLines(mUids, times1));
+        setCoresAndData(times1);
         mReader.readDelta(mCallback);
         for (int i = 0; i < mUids.length; ++i) {
             mCallback.verify(mUids[i], clusterTime(times1[i]));
@@ -92,7 +109,7 @@
         // Verify that a second call will only return deltas.
         mCallback.clear();
         final long[][] times2 = increaseTime(times1);
-        writeToFile(mHeadline + uidLines(mUids, times2));
+        setCoresAndData(times2);
         mReader.readDelta(mCallback);
         for (int i = 0; i < mUids.length; ++i) {
             mCallback.verify(mUids[i], subtract(clusterTime(times2[i]), clusterTime(times1[i])));
@@ -107,7 +124,7 @@
         // Verify that calling with a null callback doesn't result in any crashes
         mCallback.clear();
         final long[][] times3 = increaseTime(times2);
-        writeToFile(mHeadline + uidLines(mUids, times3));
+        setCoresAndData(times3);
         mReader.readDelta(null);
         mCallback.verifyNoMoreInteractions();
 
@@ -115,19 +132,19 @@
         // the previous call had null callback.
         mCallback.clear();
         final long[][] times4 = increaseTime(times3);
-        writeToFile(mHeadline + uidLines(mUids, times4));
+        setCoresAndData(times4);
         mReader.readDelta(mCallback);
         for (int i = 0; i < mUids.length; ++i) {
             mCallback.verify(mUids[i], subtract(clusterTime(times4[i]), clusterTime(times3[i])));
         }
         mCallback.verifyNoMoreInteractions();
-        assertTrue(mTestFile.delete());
+        clearCoresAndData();
     }
 
     @Test
     public void testReadAbsolute() throws Exception {
         final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
-        writeToFile(mHeadline + uidLines(mUids, times1));
+        setCoresAndData(times1);
         mReader.readAbsolute(mCallback);
         for (int i = 0; i < mUids.length; i++) {
             mCallback.verify(mUids[i], clusterTime(times1[i]));
@@ -137,19 +154,19 @@
         // Verify that a second call should still return absolute values
         mCallback.clear();
         final long[][] times2 = increaseTime(times1);
-        writeToFile(mHeadline + uidLines(mUids, times2));
+        setCoresAndData(times2);
         mReader.readAbsolute(mCallback);
         for (int i = 0; i < mUids.length; i++) {
             mCallback.verify(mUids[i], clusterTime(times2[i]));
         }
         mCallback.verifyNoMoreInteractions();
-        assertTrue(mTestFile.delete());
+        clearCoresAndData();
     }
 
     @Test
     public void testReadDeltaDecreasedTime() throws Exception {
         final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
-        writeToFile(mHeadline + uidLines(mUids, times1));
+        setCoresAndData(times1);
         mReader.readDelta(mCallback);
 
         // Verify that there should not be a callback for a particular UID if its time decreases.
@@ -157,19 +174,19 @@
         final long[][] times2 = increaseTime(times1);
         System.arraycopy(times1[0], 0, times2[0], 0, mCpus);
         times2[0][0] = 100;
-        writeToFile(mHeadline + uidLines(mUids, times2));
+        setCoresAndData(times2);
         mReader.readDelta(mCallback);
         for (int i = 1; i < mUids.length; i++) {
             mCallback.verify(mUids[i], subtract(clusterTime(times2[i]), clusterTime(times1[i])));
         }
         mCallback.verifyNoMoreInteractions();
-        assertTrue(mTestFile.delete());
+        clearCoresAndData();
 
         // Verify that the internal state was not modified.
         mCallback.clear();
         final long[][] times3 = increaseTime(times2);
         times3[0] = increaseTime(times1)[0];
-        writeToFile(mHeadline + uidLines(mUids, times3));
+        setCoresAndData(times3);
         mReader.readDelta(mCallback);
         mCallback.verify(mUids[0], subtract(clusterTime(times3[0]), clusterTime(times1[0])));
         for (int i = 1; i < mUids.length; i++) {
@@ -181,26 +198,26 @@
     @Test
     public void testReadDeltaNegativeTime() throws Exception {
         final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
-        writeToFile(mHeadline + uidLines(mUids, times1));
+        setCoresAndData(times1);
         mReader.readDelta(mCallback);
 
         // Verify that there should not be a callback for a particular UID if its time decreases.
         mCallback.clear();
         final long[][] times2 = increaseTime(times1);
         times2[0][0] *= -1;
-        writeToFile(mHeadline + uidLines(mUids, times2));
+        setCoresAndData(times2);
         mReader.readDelta(mCallback);
         for (int i = 1; i < mUids.length; i++) {
             mCallback.verify(mUids[i], subtract(clusterTime(times2[i]), clusterTime(times1[i])));
         }
         mCallback.verifyNoMoreInteractions();
-        assertTrue(mTestFile.delete());
+        clearCoresAndData();
 
         // Verify that the internal state was not modified.
         mCallback.clear();
         final long[][] times3 = increaseTime(times2);
         times3[0] = increaseTime(times1)[0];
-        writeToFile(mHeadline + uidLines(mUids, times3));
+        setCoresAndData(times3);
         mReader.readDelta(mCallback);
         mCallback.verify(mUids[0], subtract(clusterTime(times3[0]), clusterTime(times1[0])));
         for (int i = 1; i < mUids.length; i++) {
@@ -209,6 +226,28 @@
         mCallback.verifyNoMoreInteractions();
     }
 
+    private void setCoresAndData(long[][] times) throws IOException {
+        if (mUseBpf) {
+            mBpfMapReader.setClusterCores(mCores);
+            SparseArray<long[]> data = new SparseArray<>();
+            for (int i = 0; i < mUids.length; i++) {
+                data.put(mUids[i], times[i]);
+            }
+            mBpfMapReader.setData(data);
+        } else {
+            writeToFile(mHeadline + uidLines(mUids, times));
+        }
+    }
+
+    private void clearCoresAndData() {
+        if (mUseBpf) {
+            mBpfMapReader.setClusterCores(null);
+            mBpfMapReader.setData(new SparseArray<>());
+        } else {
+            assertTrue(mTestFile.delete());
+        }
+    }
+
     private long[] clusterTime(long[] times) {
         // Assumes 4 + 2 cores
         return new long[]{times[0] + times[1] / 2 + times[2] / 3 + times[3] / 4,
@@ -277,4 +316,36 @@
             assertEquals(0, mData.size());
         }
     }
+
+    private class KernelCpuUidTestBpfMapReader extends KernelCpuUidBpfMapReader {
+        private long[] mClusterCores;
+        private SparseArray<long[]> mNewData = new SparseArray<>();
+
+        public void setData(SparseArray<long[]> data) {
+            mNewData = data;
+        }
+
+        public void setClusterCores(long[] cores) {
+            mClusterCores = cores;
+        }
+
+        @Override
+        public final boolean startTrackingBpfTimes() {
+            return true;
+        }
+
+        @Override
+        protected final boolean readBpfData() {
+            if (!mUseBpf) {
+                return false;
+            }
+            mData = mNewData;
+            return true;
+        }
+
+        @Override
+        public final long[] getDataDimensions() {
+            return mClusterCores;
+        }
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java
index 0b6fed3..c60a6d6 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java
@@ -29,7 +29,6 @@
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
 
@@ -37,6 +36,8 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -45,6 +46,7 @@
 import java.io.IOException;
 import java.nio.file.Files;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Random;
 
 /**
@@ -53,14 +55,16 @@
  * $ atest FrameworksCoreTests:com.android.internal.os.KernelCpuUidFreqTimeReaderTest
  */
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
 public class KernelCpuUidFreqTimeReaderTest {
     private File mTestDir;
     private File mTestFile;
     private KernelCpuUidFreqTimeReader mReader;
+    private KernelCpuUidTestBpfMapReader mBpfMapReader;
     private VerifiableCallback mCallback;
     @Mock
     private PowerProfile mPowerProfile;
+    private boolean mUseBpf;
 
     private Random mRand = new Random(12345);
     private final int[] mUids = {0, 1, 22, 333, 4444, 55555};
@@ -69,13 +73,23 @@
         return InstrumentationRegistry.getContext();
     }
 
+    @Parameters(name="useBpf={0}")
+    public static Collection<Object[]> data() {
+        return Arrays.asList(new Object[][] { {true}, {false} });
+    }
+
+    public KernelCpuUidFreqTimeReaderTest(boolean useBpf) {
+        mUseBpf = useBpf;
+    }
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mTestDir = getContext().getDir("test", Context.MODE_PRIVATE);
         mTestFile = new File(mTestDir, "test.file");
+        mBpfMapReader = new KernelCpuUidTestBpfMapReader();
         mReader = new KernelCpuUidFreqTimeReader(mTestFile.getAbsolutePath(),
-                new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), false);
+                new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), mBpfMapReader, false);
         mCallback = new VerifiableCallback();
     }
 
@@ -97,17 +111,17 @@
         final int[][] numFreqs = {{3, 6}, {4, 5}, {3, 5, 4}, {3}};
         for (int i = 0; i < freqs.length; ++i) {
             mReader = new KernelCpuUidFreqTimeReader(mTestFile.getAbsolutePath(),
-                    new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), false);
+                    new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), mBpfMapReader, false);
             setCpuClusterFreqs(numClusters[i], numFreqs[i]);
-            writeToFile(freqsLine(freqs[i]));
+            setFreqs(freqs[i]);
             long[] actualFreqs = mReader.readFreqs(mPowerProfile);
             assertArrayEquals(freqs[i], actualFreqs);
             final String errMsg = String.format("Freqs=%s, nClusters=%d, nFreqs=%s",
                     Arrays.toString(freqs[i]), numClusters[i], Arrays.toString(numFreqs[i]));
             assertFalse(errMsg, mReader.perClusterTimesAvailable());
 
-            // Verify that a second call won't read the proc file again
-            assertTrue(mTestFile.delete());
+            // Verify that a second call won't re-read the freqs
+            clearFreqsAndData();
             actualFreqs = mReader.readFreqs(mPowerProfile);
             assertArrayEquals(freqs[i], actualFreqs);
             assertFalse(errMsg, mReader.perClusterTimesAvailable());
@@ -125,17 +139,17 @@
         final int[][] numFreqs = {{4}, {3, 5}, {3, 5, 4}};
         for (int i = 0; i < freqs.length; ++i) {
             mReader = new KernelCpuUidFreqTimeReader(mTestFile.getAbsolutePath(),
-                    new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), false);
+                    new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), mBpfMapReader, false);
             setCpuClusterFreqs(numClusters[i], numFreqs[i]);
-            writeToFile(freqsLine(freqs[i]));
+            setFreqs(freqs[i]);
             long[] actualFreqs = mReader.readFreqs(mPowerProfile);
             assertArrayEquals(freqs[i], actualFreqs);
             final String errMsg = String.format("Freqs=%s, nClusters=%d, nFreqs=%s",
                     Arrays.toString(freqs[i]), numClusters[i], Arrays.toString(numFreqs[i]));
             assertTrue(errMsg, mReader.perClusterTimesAvailable());
 
-            // Verify that a second call won't read the proc file again
-            assertTrue(mTestFile.delete());
+            // Verify that a second call won't re-read the freqs
+            clearFreqsAndData();
             actualFreqs = mReader.readFreqs(mPowerProfile);
             assertArrayEquals(freqs[i], actualFreqs);
             assertTrue(errMsg, mReader.perClusterTimesAvailable());
@@ -147,7 +161,7 @@
         final long[] freqs = {110, 123, 145, 167, 289, 997};
         final long[][] times = increaseTime(new long[mUids.length][freqs.length]);
 
-        writeToFile(freqsLine(freqs) + uidLines(mUids, times));
+        setFreqsAndData(freqs, times);
         mReader.readDelta(mCallback);
         for (int i = 0; i < mUids.length; ++i) {
             mCallback.verify(mUids[i], times[i]);
@@ -155,14 +169,14 @@
         mCallback.verifyNoMoreInteractions();
 
         // Verify that readDelta also reads the frequencies if not already available.
-        assertTrue(mTestFile.delete());
+        clearFreqsAndData();
         long[] actualFreqs = mReader.readFreqs(mPowerProfile);
         assertArrayEquals(freqs, actualFreqs);
 
         // Verify that a second call will only return deltas.
         mCallback.clear();
         final long[][] newTimes1 = increaseTime(times);
-        writeToFile(freqsLine(freqs) + uidLines(mUids, newTimes1));
+        setFreqsAndData(freqs, newTimes1);
         mReader.readDelta(mCallback);
         for (int i = 0; i < mUids.length; ++i) {
             mCallback.verify(mUids[i], subtract(newTimes1[i], times[i]));
@@ -177,7 +191,7 @@
         // Verify that calling with a null callback doesn't result in any crashes
         mCallback.clear();
         final long[][] newTimes2 = increaseTime(newTimes1);
-        writeToFile(freqsLine(freqs) + uidLines(mUids, newTimes2));
+        setFreqsAndData(freqs, newTimes2);
         mReader.readDelta(null);
         mCallback.verifyNoMoreInteractions();
 
@@ -185,13 +199,13 @@
         // the previous call had null callback.
         mCallback.clear();
         final long[][] newTimes3 = increaseTime(newTimes2);
-        writeToFile(freqsLine(freqs) + uidLines(mUids, newTimes3));
+        setFreqsAndData(freqs, newTimes3);
         mReader.readDelta(mCallback);
         for (int i = 0; i < mUids.length; ++i) {
             mCallback.verify(mUids[i], subtract(newTimes3[i], newTimes2[i]));
         }
         mCallback.verifyNoMoreInteractions();
-        assertTrue(mTestFile.delete());
+        clearFreqsAndData();
     }
 
     @Test
@@ -199,7 +213,7 @@
         final long[] freqs = {110, 123, 145, 167, 289, 997};
         final long[][] times1 = increaseTime(new long[mUids.length][freqs.length]);
 
-        writeToFile(freqsLine(freqs) + uidLines(mUids, times1));
+        setFreqsAndData(freqs, times1);
         mReader.readAbsolute(mCallback);
         for (int i = 0; i < mUids.length; i++) {
             mCallback.verify(mUids[i], times1[i]);
@@ -207,20 +221,20 @@
         mCallback.verifyNoMoreInteractions();
 
         // Verify that readDelta also reads the frequencies if not already available.
-        assertTrue(mTestFile.delete());
+        clearFreqsAndData();
         long[] actualFreqs = mReader.readFreqs(mPowerProfile);
         assertArrayEquals(freqs, actualFreqs);
 
         // Verify that a second call should still return absolute values
         mCallback.clear();
         final long[][] times2 = increaseTime(times1);
-        writeToFile(freqsLine(freqs) + uidLines(mUids, times2));
+        setFreqsAndData(freqs, times2);
         mReader.readAbsolute(mCallback);
         for (int i = 0; i < mUids.length; i++) {
             mCallback.verify(mUids[i], times2[i]);
         }
         mCallback.verifyNoMoreInteractions();
-        assertTrue(mTestFile.delete());
+        clearFreqsAndData();
     }
 
     @Test
@@ -228,14 +242,14 @@
         final long[] freqs = {110, 123, 145, 167, 289, 997};
         final long[][] times1 = increaseTime(new long[mUids.length][freqs.length]);
 
-        writeToFile(freqsLine(freqs) + uidLines(mUids, times1));
+        setFreqsAndData(freqs, times1);
         mReader.readDelta(mCallback);
 
         // Verify that there should not be a callback for a particular UID if its time decreases.
         mCallback.clear();
         final long[][] times2 = increaseTime(times1);
         times2[0][0] = 1000;
-        writeToFile(freqsLine(freqs) + uidLines(mUids, times2));
+        setFreqsAndData(freqs, times2);
         mReader.readDelta(mCallback);
         for (int i = 1; i < mUids.length; i++) {
             mCallback.verify(mUids[i], subtract(times2[i], times1[i]));
@@ -246,7 +260,7 @@
         mCallback.clear();
         final long[][] times3 = increaseTime(times2);
         times3[0] = increaseTime(times1)[0];
-        writeToFile(freqsLine(freqs) + uidLines(mUids, times3));
+        setFreqsAndData(freqs, times3);
         mReader.readDelta(mCallback);
         mCallback.verify(mUids[0], subtract(times3[0], times1[0]));
         for (int i = 1; i < mUids.length; i++) {
@@ -258,7 +272,7 @@
         mCallback.clear();
         final long[][] times4 = increaseTime(times3);
         times4[0][0] *= -1;
-        writeToFile(freqsLine(freqs) + uidLines(mUids, times4));
+        setFreqsAndData(freqs, times4);
         mReader.readDelta(mCallback);
         for (int i = 1; i < mUids.length; ++i) {
             mCallback.verify(mUids[i], subtract(times4[i], times3[i]));
@@ -269,14 +283,44 @@
         mCallback.clear();
         final long[][] times5 = increaseTime(times4);
         times5[0] = increaseTime(times3)[0];
-        writeToFile(freqsLine(freqs) + uidLines(mUids, times5));
+        setFreqsAndData(freqs, times5);
         mReader.readDelta(mCallback);
         mCallback.verify(mUids[0], subtract(times5[0], times3[0]));
         for (int i = 1; i < mUids.length; i++) {
             mCallback.verify(mUids[i], subtract(times5[i], times4[i]));
         }
 
-        assertTrue(mTestFile.delete());
+        clearFreqsAndData();
+    }
+
+    private void setFreqs(long[] freqs) throws IOException {
+        if (mUseBpf) {
+            mBpfMapReader.setFreqs(freqs);
+        } else {
+            writeToFile(freqsLine(freqs));
+        }
+    }
+
+    private void setFreqsAndData(long[] freqs, long[][] times) throws IOException {
+        if (mUseBpf) {
+            mBpfMapReader.setFreqs(freqs);
+            SparseArray<long[]> data = new SparseArray<>();
+            for (int i = 0; i < mUids.length; i++) {
+                data.put(mUids[i], times[i]);
+            }
+            mBpfMapReader.setData(data);
+        } else {
+            writeToFile(freqsLine(freqs) + uidLines(mUids, times));
+        }
+    }
+
+    private void clearFreqsAndData() {
+        if (mUseBpf) {
+            mBpfMapReader.setFreqs(null);
+            mBpfMapReader.setData(new SparseArray<>());
+        } else {
+            assertTrue(mTestFile.delete());
+        }
     }
 
     private String freqsLine(long[] freqs) {
@@ -358,4 +402,36 @@
             assertEquals(0, mData.size());
         }
     }
+
+    private class KernelCpuUidTestBpfMapReader extends KernelCpuUidBpfMapReader {
+        private long[] mCpuFreqs;
+        private SparseArray<long[]> mNewData = new SparseArray<>();
+
+        public void setData(SparseArray<long[]> data) {
+            mNewData = data;
+        }
+
+        public void setFreqs(long[] freqs) {
+            mCpuFreqs = freqs;
+        }
+
+        @Override
+        public final boolean startTrackingBpfTimes() {
+            return true;
+        }
+
+        @Override
+        protected final boolean readBpfData() {
+            if (!mUseBpf) {
+                return false;
+            }
+            mData = mNewData;
+            return true;
+        }
+
+        @Override
+        public final long[] getDataDimensions() {
+            return mCpuFreqs;
+        }
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
index 479e19e..dac35e5 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
@@ -31,20 +31,33 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.util.Arrays;
+import java.util.Collection;
 
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
 public class KernelSingleUidTimeReaderTest {
     private final static int TEST_UID = 2222;
     private final static int TEST_FREQ_COUNT = 5;
 
     private KernelSingleUidTimeReader mReader;
     private TestInjector mInjector;
+    protected boolean mUseBpf;
+
+    @Parameters(name="useBpf={0}")
+    public static Collection<Object[]> data() {
+        return Arrays.asList(new Object[][] { {true}, {false} });
+    }
+
+    public KernelSingleUidTimeReaderTest(boolean useBpf) {
+        mUseBpf = useBpf;
+    }
 
     @Before
     public void setUp() {
@@ -273,6 +286,7 @@
 
     class TestInjector extends Injector {
         private byte[] mData;
+        private long[] mBpfData;
         private boolean mThrowExcpetion;
 
         @Override
@@ -284,6 +298,14 @@
             }
         }
 
+        @Override
+        public long[] readBpfData(int uid) {
+            if (!mUseBpf || mBpfData == null) {
+                return new long[0];
+            }
+            return mBpfData;
+        }
+
         public void setData(long[] cpuTimes) {
             final ByteBuffer buffer = ByteBuffer.allocate(cpuTimes.length * Long.BYTES);
             buffer.order(ByteOrder.nativeOrder());
@@ -291,6 +313,7 @@
                 buffer.putLong(time / 10);
             }
             mData = buffer.array();
+            mBpfData = cpuTimes.clone();
         }
 
         public void letReadDataThrowException(boolean throwException) {
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 821909d..e3700d4 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -377,6 +377,7 @@
     <privapp-permissions package="com.android.dynsystem">
         <permission name="android.permission.REBOOT"/>
         <permission name="android.permission.MANAGE_DYNAMIC_SYSTEM"/>
+        <permission name="android.permission.READ_OEM_UNLOCK_STATE"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.settings">
diff --git a/libs/usb/Android.bp b/libs/usb/Android.bp
index 027a748..e752b55 100644
--- a/libs/usb/Android.bp
+++ b/libs/usb/Android.bp
@@ -19,5 +19,3 @@
     srcs: ["src/**/*.java"],
     api_packages: ["com.android.future.usb"],
 }
-
-subdirs = ["tests/*"]
diff --git a/libs/usb/tests/AccessoryChat/Android.bp b/libs/usb/tests/AccessoryChat/Android.bp
index 63a670c..19ed3d3 100644
--- a/libs/usb/tests/AccessoryChat/Android.bp
+++ b/libs/usb/tests/AccessoryChat/Android.bp
@@ -1,4 +1,3 @@
-subdirs = ["accessorychat"]
 //
 // Copyright (C) 2011 The Android Open Source Project
 //
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index f873eeb..27660db 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -124,8 +124,3 @@
         "-Wunreachable-code",
     ],
 }
-
-subdirs = [
-    "audioeffect",
-    "soundpool",
-]
diff --git a/media/native/Android.bp b/media/native/Android.bp
deleted file mode 100644
index b44c296..0000000
--- a/media/native/Android.bp
+++ /dev/null
@@ -1 +0,0 @@
-subdirs = ["*"]
diff --git a/packages/DynamicSystemInstallationService/AndroidManifest.xml b/packages/DynamicSystemInstallationService/AndroidManifest.xml
index d718eae..b4d520d 100644
--- a/packages/DynamicSystemInstallationService/AndroidManifest.xml
+++ b/packages/DynamicSystemInstallationService/AndroidManifest.xml
@@ -7,6 +7,7 @@
     <uses-permission android:name="android.permission.MANAGE_DYNAMIC_SYSTEM" />
     <uses-permission android:name="android.permission.REBOOT" />
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+    <uses-permission android:name="android.permission.READ_OEM_UNLOCK_STATE" />
 
     <application
         android:allowBackup="false"
diff --git a/packages/DynamicSystemInstallationService/res/values/strings.xml b/packages/DynamicSystemInstallationService/res/values/strings.xml
index 7595d2b..e124be6 100644
--- a/packages/DynamicSystemInstallationService/res/values/strings.xml
+++ b/packages/DynamicSystemInstallationService/res/values/strings.xml
@@ -18,6 +18,8 @@
     <string name="notification_install_inprogress">Install in progress</string>
     <!-- Displayed on notification: Dynamic System installation failed [CHAR LIMIT=128] -->
     <string name="notification_install_failed">Install failed</string>
+    <!-- Displayed on notification: Image validation failed [CHAR LIMIT=128] -->
+    <string name="notification_image_validation_failed">Image validation failed. Abort installation.</string>
     <!-- Displayed on notification: We are running in Dynamic System [CHAR LIMIT=128] -->
     <string name="notification_dynsystem_in_use">Currently running a dynamic system. Restart to use the original Android version.</string>
 
@@ -25,10 +27,11 @@
     <string name="notification_action_cancel">Cancel</string>
     <!-- Action on notification: Discard installation [CHAR LIMIT=16] -->
     <string name="notification_action_discard">Discard</string>
-    <!-- Action on notification: Uninstall Dynamic System [CHAR LIMIT=16] -->
-    <string name="notification_action_uninstall">Uninstall</string>
     <!-- Action on notification: Restart to Dynamic System [CHAR LIMIT=16] -->
     <string name="notification_action_reboot_to_dynsystem">Restart</string>
+    <!-- Action on notification: Restart to original Android version [CHAR LIMIT=16] -->
+    <string name="notification_action_reboot_to_origin">Restart</string>
+
 
     <!-- Toast when installed Dynamic System is discarded [CHAR LIMIT=64] -->
     <string name="toast_dynsystem_discarded">Discarded dynamic system</string>
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
index 9bae223..f36f97d 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
@@ -80,6 +80,10 @@
     static final String KEY_ENABLE_WHEN_COMPLETED = "KEY_ENABLE_WHEN_COMPLETED";
     static final String KEY_DSU_SLOT = "KEY_DSU_SLOT";
     static final String DEFAULT_DSU_SLOT = "dsu";
+    static final String KEY_PUBKEY = "KEY_PUBKEY";
+
+    // Default userdata partition size is 2GiB.
+    private static final long DEFAULT_USERDATA_SIZE = 2L << 30;
 
     /*
      * Intent actions
@@ -267,6 +271,11 @@
         long userdataSize = intent.getLongExtra(DynamicSystemClient.KEY_USERDATA_SIZE, 0);
         mEnableWhenCompleted = intent.getBooleanExtra(KEY_ENABLE_WHEN_COMPLETED, false);
         String dsuSlot = intent.getStringExtra(KEY_DSU_SLOT);
+        String publicKey = intent.getStringExtra(KEY_PUBKEY);
+
+        if (userdataSize == 0) {
+            userdataSize = DEFAULT_USERDATA_SIZE;
+        }
 
         if (TextUtils.isEmpty(dsuSlot)) {
             dsuSlot = DEFAULT_DSU_SLOT;
@@ -274,7 +283,7 @@
         // TODO: better constructor or builder
         mInstallTask =
                 new InstallationAsyncTask(
-                        url, dsuSlot, systemSize, userdataSize, this, mDynSystem, this);
+                        url, dsuSlot, publicKey, systemSize, userdataSize, this, mDynSystem, this);
 
         mInstallTask.execute();
 
@@ -408,6 +417,10 @@
     }
 
     private Notification buildNotification(int status, int cause) {
+        return buildNotification(status, cause, null);
+    }
+
+    private Notification buildNotification(int status, int cause, Throwable detail) {
         Notification.Builder builder = new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
                 .setSmallIcon(R.drawable.ic_system_update_googblue_24dp)
                 .setProgress(0, 0, false);
@@ -456,14 +469,19 @@
                         .setStyle(new Notification.BigTextStyle().bigText(msgInUse));
 
                 builder.addAction(new Notification.Action.Builder(
-                        null, getString(R.string.notification_action_uninstall),
+                        null, getString(R.string.notification_action_reboot_to_origin),
                         createPendingIntent(ACTION_REBOOT_TO_NORMAL)).build());
 
                 break;
 
             case STATUS_NOT_STARTED:
                 if (cause != CAUSE_NOT_SPECIFIED && cause != CAUSE_INSTALL_CANCELLED) {
-                    builder.setContentText(getString(R.string.notification_install_failed));
+                    if (detail instanceof InstallationAsyncTask.ImageValidationException) {
+                        builder.setContentText(
+                                getString(R.string.notification_image_validation_failed));
+                    } else {
+                        builder.setContentText(getString(R.string.notification_install_failed));
+                    }
                 } else {
                     // no need to notify the user if the task is not started, or cancelled.
                 }
@@ -525,7 +543,7 @@
                 break;
         }
 
-        Log.d(TAG, "status=" + statusString + ", cause=" + causeString);
+        Log.d(TAG, "status=" + statusString + ", cause=" + causeString + ", detail=" + detail);
 
         boolean notifyOnNotificationBar = true;
 
@@ -538,7 +556,7 @@
         }
 
         if (notifyOnNotificationBar) {
-            mNM.notify(NOTIFICATION_ID, buildNotification(status, cause));
+            mNM.notify(NOTIFICATION_ID, buildNotification(status, cause, detail));
         }
 
         for (int i = mClients.size() - 1; i >= 0; i--) {
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
index 438c435..f8952ac 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
@@ -17,11 +17,13 @@
 package com.android.dynsystem;
 
 import android.content.Context;
+import android.gsi.AvbPublicKey;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.MemoryFile;
 import android.os.ParcelFileDescriptor;
 import android.os.image.DynamicSystemManager;
+import android.service.persistentdata.PersistentDataBlockManager;
 import android.util.Log;
 import android.webkit.URLUtil;
 
@@ -51,18 +53,46 @@
     private static final List<String> UNSUPPORTED_PARTITIONS =
             Arrays.asList("vbmeta", "boot", "userdata", "dtbo", "super_empty", "system_other");
 
-    private class UnsupportedUrlException extends RuntimeException {
+    private class UnsupportedUrlException extends Exception {
         private UnsupportedUrlException(String message) {
             super(message);
         }
     }
 
-    private class UnsupportedFormatException extends RuntimeException {
+    private class UnsupportedFormatException extends Exception {
         private UnsupportedFormatException(String message) {
             super(message);
         }
     }
 
+    static class ImageValidationException extends Exception {
+        ImageValidationException(String message) {
+            super(message);
+        }
+
+        ImageValidationException(Throwable cause) {
+            super(cause);
+        }
+    }
+
+    static class RevocationListFetchException extends ImageValidationException {
+        RevocationListFetchException(Throwable cause) {
+            super(cause);
+        }
+    }
+
+    static class KeyRevokedException extends ImageValidationException {
+        KeyRevokedException(String message) {
+            super(message);
+        }
+    }
+
+    static class PublicKeyException extends ImageValidationException {
+        PublicKeyException(String message) {
+            super(message);
+        }
+    }
+
     /** UNSET means the installation is not completed */
     static final int RESULT_UNSET = 0;
     static final int RESULT_OK = 1;
@@ -97,12 +127,14 @@
 
     private final String mUrl;
     private final String mDsuSlot;
+    private final String mPublicKey;
     private final long mSystemSize;
     private final long mUserdataSize;
     private final Context mContext;
     private final DynamicSystemManager mDynSystem;
     private final ProgressListener mListener;
     private final boolean mIsNetworkUrl;
+    private final boolean mIsDeviceBootloaderUnlocked;
     private DynamicSystemManager.Session mInstallationSession;
     private KeyRevocationList mKeyRevocationList;
 
@@ -115,6 +147,7 @@
     InstallationAsyncTask(
             String url,
             String dsuSlot,
+            String publicKey,
             long systemSize,
             long userdataSize,
             Context context,
@@ -122,12 +155,20 @@
             ProgressListener listener) {
         mUrl = url;
         mDsuSlot = dsuSlot;
+        mPublicKey = publicKey;
         mSystemSize = systemSize;
         mUserdataSize = userdataSize;
         mContext = context;
         mDynSystem = dynSystem;
         mListener = listener;
         mIsNetworkUrl = URLUtil.isNetworkUrl(mUrl);
+        PersistentDataBlockManager pdbManager =
+                (PersistentDataBlockManager)
+                        mContext.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
+        mIsDeviceBootloaderUnlocked =
+                (pdbManager != null)
+                        && (pdbManager.getFlashLockState()
+                                == PersistentDataBlockManager.FLASH_LOCK_UNLOCKED);
     }
 
     @Override
@@ -157,8 +198,6 @@
                 return null;
             }
 
-            // TODO(yochiang): do post-install public key check (revocation list / boot-ramdisk)
-
             mDynSystem.finishInstallation();
         } catch (Exception e) {
             Log.e(TAG, e.toString(), e);
@@ -242,23 +281,26 @@
                     String.format(Locale.US, "Unsupported URL: %s", mUrl));
         }
 
-        // TODO(yochiang): Bypass this check if device is unlocked
         try {
             String listUrl = mContext.getString(R.string.key_revocation_list_url);
             mKeyRevocationList = KeyRevocationList.fromUrl(new URL(listUrl));
         } catch (IOException | JSONException e) {
-            Log.d(TAG, "Failed to fetch Dynamic System Key Revocation List");
             mKeyRevocationList = new KeyRevocationList();
-            keyRevocationThrowOrWarning(e);
+            imageValidationThrowOrWarning(new RevocationListFetchException(e));
+        }
+        if (mKeyRevocationList.isRevoked(mPublicKey)) {
+            imageValidationThrowOrWarning(new KeyRevokedException(mPublicKey));
         }
     }
 
-    private void keyRevocationThrowOrWarning(Exception e) throws Exception {
-        if (mIsNetworkUrl) {
-            throw e;
-        } else {
-            // If DSU is being installed from a local file URI, then be permissive
+    private void imageValidationThrowOrWarning(ImageValidationException e)
+            throws ImageValidationException {
+        if (mIsDeviceBootloaderUnlocked || !mIsNetworkUrl) {
+            // If device is OEM unlocked or DSU is being installed from a local file URI,
+            // then be permissive.
             Log.w(TAG, e.toString());
+        } else {
+            throw e;
         }
     }
 
@@ -294,7 +336,8 @@
         }
     }
 
-    private void installImages() throws IOException, InterruptedException {
+    private void installImages()
+            throws IOException, InterruptedException, ImageValidationException {
         if (mStream != null) {
             if (mIsZip) {
                 installStreamingZipUpdate();
@@ -306,12 +349,14 @@
         }
     }
 
-    private void installStreamingGzUpdate() throws IOException, InterruptedException {
+    private void installStreamingGzUpdate()
+            throws IOException, InterruptedException, ImageValidationException {
         Log.d(TAG, "To install a streaming GZ update");
         installImage("system", mSystemSize, new GZIPInputStream(mStream), 1);
     }
 
-    private void installStreamingZipUpdate() throws IOException, InterruptedException {
+    private void installStreamingZipUpdate()
+            throws IOException, InterruptedException, ImageValidationException {
         Log.d(TAG, "To install a streaming ZIP update");
 
         ZipInputStream zis = new ZipInputStream(mStream);
@@ -330,7 +375,8 @@
         }
     }
 
-    private void installLocalZipUpdate() throws IOException, InterruptedException {
+    private void installLocalZipUpdate()
+            throws IOException, InterruptedException, ImageValidationException {
         Log.d(TAG, "To install a local ZIP update");
 
         Enumeration<? extends ZipEntry> entries = mZipFile.entries();
@@ -349,8 +395,9 @@
         }
     }
 
-    private boolean installImageFromAnEntry(ZipEntry entry, InputStream is,
-            int numInstalledPartitions) throws IOException, InterruptedException {
+    private boolean installImageFromAnEntry(
+            ZipEntry entry, InputStream is, int numInstalledPartitions)
+            throws IOException, InterruptedException, ImageValidationException {
         String name = entry.getName();
 
         Log.d(TAG, "ZipEntry: " + name);
@@ -373,8 +420,9 @@
         return true;
     }
 
-    private void installImage(String partitionName, long uncompressedSize, InputStream is,
-            int numInstalledPartitions) throws IOException, InterruptedException {
+    private void installImage(
+            String partitionName, long uncompressedSize, InputStream is, int numInstalledPartitions)
+            throws IOException, InterruptedException, ImageValidationException {
 
         SparseInputStream sis = new SparseInputStream(new BufferedInputStream(is));
 
@@ -445,6 +493,24 @@
                 publishProgress(progress);
             }
         }
+
+        AvbPublicKey avbPublicKey = new AvbPublicKey();
+        if (!mInstallationSession.getAvbPublicKey(avbPublicKey)) {
+            imageValidationThrowOrWarning(new PublicKeyException("getAvbPublicKey() failed"));
+        } else {
+            String publicKey = toHexString(avbPublicKey.sha1);
+            if (mKeyRevocationList.isRevoked(publicKey)) {
+                imageValidationThrowOrWarning(new KeyRevokedException(publicKey));
+            }
+        }
+    }
+
+    private static String toHexString(byte[] bytes) {
+        StringBuilder sb = new StringBuilder();
+        for (byte b : bytes) {
+            sb.append(String.format("%02x", b));
+        }
+        return sb.toString();
     }
 
     private void close() {
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java
index e42ded7..82ea744 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java
@@ -84,7 +84,6 @@
         // retrieve data from calling intent
         Intent callingIntent = getIntent();
         Uri url = callingIntent.getData();
-        Bundle extras = callingIntent.getExtras();
 
         if (url != null) {
             sVerifiedUrl = url.toString();
@@ -96,7 +95,7 @@
             intent.setData(url);
         }
         intent.setAction(DynamicSystemClient.ACTION_START_INSTALL);
-        intent.putExtras(extras);
+        intent.putExtras(callingIntent);
 
         Log.d(TAG, "Starting Installation Service");
         startServiceAsUser(intent, UserHandle.SYSTEM);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index e75ad13..6c1d279 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -1009,6 +1009,11 @@
                         String value = setting != null ? setting.getValue() : null;
                         updateGlobalSetting(Settings.Global.ADB_ENABLED,
                                 value, null, true, userId, true);
+
+                        setting = getGlobalSetting(Settings.Global.ADB_WIFI_ENABLED);
+                        value = setting != null ? setting.getValue() : null;
+                        updateGlobalSetting(Settings.Global.ADB_WIFI_ENABLED,
+                                value, null, true, userId, true);
                     }
                 } finally {
                     Binder.restoreCallingIdentity(identity);
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 262365d..a9fa2ac 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -430,6 +430,25 @@
             android:excludeFromRecents="true">
         </activity>
 
+        <!-- started from WirelessDebuggingManager -->
+        <activity android:name=".wifi.WifiDebuggingActivity"
+            android:permission="android.permission.MANAGE_DEBUGGING"
+            android:theme="@style/Theme.SystemUI.Dialog.Alert"
+            android:finishOnCloseSystemDialogs="true"
+            android:excludeFromRecents="true">
+        </activity>
+        <activity-alias
+            android:name=".WifiDebuggingActivityAlias"
+            android:permission="android.permission.DUMP"
+            android:targetActivity=".wifi.WifiDebuggingActivity"
+            android:exported="true">
+        </activity-alias>
+        <activity android:name=".wifi.WifiDebuggingSecondaryUserActivity"
+            android:theme="@style/Theme.SystemUI.Dialog.Alert"
+            android:finishOnCloseSystemDialogs="true"
+            android:excludeFromRecents="true">
+        </activity>
+
         <!-- started from NetworkPolicyManagerService -->
         <activity
             android:name=".net.NetworkOverLimitActivity"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d58f83f..f5ccc53 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -162,6 +162,24 @@
     <!-- Message of notification shown when trying to enable USB debugging but a secondary user is the current foreground user. -->
     <string name="usb_debugging_secondary_user_message">The user currently signed in to this device can\'t turn on USB debugging. To use this feature, switch to the primary user.</string>
 
+    <!-- Title of confirmation dialog for wireless debugging [CHAR LIMIT=NONE] -->
+    <string name="wifi_debugging_title">Allow wireless debugging on this network?</string>
+
+    <!-- Message of confirmation dialog for wireless debugging [CHAR LIMIT=NONE] -->
+    <string name="wifi_debugging_message">Network Name (SSID)\n<xliff:g id="ssid" example="My wifi">%1$s</xliff:g>\n\nWi\u2011Fi Address (BSSID)\n<xliff:g id="bssid" example="AB:CD:EF:12:34:56">%2$s</xliff:g></string>
+
+    <!-- Option to always allow wireless debugging on this network [CHAR LIMIT=NONE] -->
+    <string name="wifi_debugging_always">Always allow on this network</string>
+
+    <!-- Button label for confirming acceptance of enabling wireless debugging [CHAR LIMIT=15] -->
+    <string name="wifi_debugging_allow">Allow</string>
+
+    <!-- Title of notification shown when trying to enable wireless debugging but a secondary user is the current foreground user. [CHAR LIMIT=NONE] -->
+    <string name="wifi_debugging_secondary_user_title">Wireless debugging not allowed</string>
+
+    <!-- Message of notification shown when trying to enable wireless debugging but a secondary user is the current foreground user. [CHAR LIMIT=NONE] -->
+    <string name="wifi_debugging_secondary_user_message">The user currently signed in to this device can\u2019t turn on wireless debugging. To use this feature, switch to the primary user.</string>
+
     <!-- Title of USB contaminant presence dialog [CHAR LIMIT=NONE] -->
     <string name="usb_contaminant_title">USB port disabled</string>
 
diff --git a/packages/SystemUI/src/com/android/systemui/wifi/WifiDebuggingActivity.java b/packages/SystemUI/src/com/android/systemui/wifi/WifiDebuggingActivity.java
new file mode 100644
index 0000000..a94af24
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wifi/WifiDebuggingActivity.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2012 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.systemui.wifi;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.debug.IAdbManager;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.ServiceManager;
+import android.util.EventLog;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.CheckBox;
+import android.widget.Toast;
+
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+import com.android.systemui.R;
+
+/**
+ * Alerts the user of an untrusted network when enabling wireless debugging.
+ * The user can either deny, allow, or allow with the "always allow on this
+ * network" checked.
+ */
+public class WifiDebuggingActivity extends AlertActivity
+                                  implements DialogInterface.OnClickListener {
+    private static final String TAG = "WifiDebuggingActivity";
+
+    private CheckBox mAlwaysAllow;
+    // Notifies when wifi is disabled, or the network changed
+    private WifiChangeReceiver mWifiChangeReceiver;
+    private WifiManager mWifiManager;
+    private String mBssid;
+    private boolean mClicked = false;
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        Window window = getWindow();
+        window.addSystemFlags(
+                WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+        window.setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
+
+        super.onCreate(icicle);
+
+
+        mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
+        mWifiChangeReceiver = new WifiChangeReceiver(this);
+
+        Intent intent = getIntent();
+        String ssid = intent.getStringExtra("ssid");
+        mBssid = intent.getStringExtra("bssid");
+
+        if (ssid == null || mBssid == null) {
+            finish();
+            return;
+        }
+
+        final AlertController.AlertParams ap = mAlertParams;
+        ap.mTitle = getString(R.string.wifi_debugging_title);
+        ap.mMessage = getString(R.string.wifi_debugging_message, ssid, mBssid);
+        ap.mPositiveButtonText = getString(R.string.wifi_debugging_allow);
+        ap.mNegativeButtonText = getString(android.R.string.cancel);
+        ap.mPositiveButtonListener = this;
+        ap.mNegativeButtonListener = this;
+
+        // add "always allow" checkbox
+        LayoutInflater inflater = LayoutInflater.from(ap.mContext);
+        View checkbox = inflater.inflate(com.android.internal.R.layout.always_use_checkbox, null);
+        mAlwaysAllow = (CheckBox) checkbox.findViewById(com.android.internal.R.id.alwaysUse);
+        mAlwaysAllow.setText(getString(R.string.wifi_debugging_always));
+        ap.mView = checkbox;
+        window.setCloseOnTouchOutside(false);
+
+        setupAlert();
+
+        // adding touch listener on affirmative button - checks if window is obscured
+        // if obscured, do not let user give permissions (could be tapjacking involved)
+        final View.OnTouchListener filterTouchListener = (View v, MotionEvent event) -> {
+            // Filter obscured touches by consuming them.
+            if (((event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0)
+                    || ((event.getFlags() & MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED) != 0)) {
+                if (event.getAction() == MotionEvent.ACTION_UP) {
+                    // TODO: need a different value for safety net?
+                    EventLog.writeEvent(0x534e4554, "62187985"); // safety net logging
+                    Toast.makeText(v.getContext(),
+                            R.string.touch_filtered_warning,
+                            Toast.LENGTH_SHORT).show();
+                }
+                return true;
+            }
+            return false;
+        };
+        mAlert.getButton(BUTTON_POSITIVE).setOnTouchListener(filterTouchListener);
+
+    }
+
+    @Override
+    public void onWindowAttributesChanged(WindowManager.LayoutParams params) {
+        super.onWindowAttributesChanged(params);
+    }
+
+    private class WifiChangeReceiver extends BroadcastReceiver {
+        private final Activity mActivity;
+        WifiChangeReceiver(Activity activity) {
+            mActivity = activity;
+        }
+
+        @Override
+        public void onReceive(Context content, Intent intent) {
+            String action = intent.getAction();
+            if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
+                int state = intent.getIntExtra(
+                        WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_DISABLED);
+                if (state == WifiManager.WIFI_STATE_DISABLED) {
+                    mActivity.finish();
+                }
+            } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
+                NetworkInfo networkInfo = (NetworkInfo) intent.getParcelableExtra(
+                        WifiManager.EXTRA_NETWORK_INFO);
+                if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
+                    if (!networkInfo.isConnected()) {
+                        mActivity.finish();
+                        return;
+                    }
+                    WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
+                    if (wifiInfo == null || wifiInfo.getNetworkId() == -1) {
+                        mActivity.finish();
+                        return;
+                    }
+                    String bssid = wifiInfo.getBSSID();
+                    if (bssid == null || bssid.isEmpty()) {
+                        mActivity.finish();
+                        return;
+                    }
+                    if (!bssid.equals(mBssid)) {
+                        mActivity.finish();
+                        return;
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        IntentFilter filter = new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION);
+        filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+        registerReceiver(mWifiChangeReceiver, filter);
+    }
+
+    @Override
+    protected void onStop() {
+        if (mWifiChangeReceiver != null) {
+            unregisterReceiver(mWifiChangeReceiver);
+        }
+        super.onStop();
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        // In the case where user dismissed the dialog, we don't get an onClick event.
+        // In that case, tell adb to deny the network connection.
+        if (!mClicked) {
+            try {
+                IBinder b = ServiceManager.getService(ADB_SERVICE);
+                IAdbManager service = IAdbManager.Stub.asInterface(b);
+                service.denyWirelessDebugging();
+            } catch (Exception e) {
+                Log.e(TAG, "Unable to notify Adb service", e);
+            }
+        }
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        mClicked = true;
+        boolean allow = (which == AlertDialog.BUTTON_POSITIVE);
+        boolean alwaysAllow = allow && mAlwaysAllow.isChecked();
+        try {
+            IBinder b = ServiceManager.getService(ADB_SERVICE);
+            IAdbManager service = IAdbManager.Stub.asInterface(b);
+            if (allow) {
+                service.allowWirelessDebugging(alwaysAllow, mBssid);
+            } else {
+                service.denyWirelessDebugging();
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Unable to notify Adb service", e);
+        }
+        finish();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wifi/WifiDebuggingSecondaryUserActivity.java b/packages/SystemUI/src/com/android/systemui/wifi/WifiDebuggingSecondaryUserActivity.java
new file mode 100644
index 0000000..0266a84
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wifi/WifiDebuggingSecondaryUserActivity.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2015 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.systemui.wifi;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+import com.android.systemui.R;
+
+/**
+ * Alerts the user that wireless debugging cannot be enabled by a secondary user.
+ */
+public class WifiDebuggingSecondaryUserActivity extends AlertActivity
+        implements DialogInterface.OnClickListener {
+    private WifiChangeReceiver mWifiChangeReceiver;
+    private WifiManager mWifiManager;
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
+        mWifiChangeReceiver = new WifiChangeReceiver(this);
+
+        final AlertController.AlertParams ap = mAlertParams;
+        ap.mTitle = getString(R.string.wifi_debugging_secondary_user_title);
+        ap.mMessage = getString(R.string.wifi_debugging_secondary_user_message);
+        ap.mPositiveButtonText = getString(android.R.string.ok);
+        ap.mPositiveButtonListener = this;
+
+        setupAlert();
+    }
+
+    private class WifiChangeReceiver extends BroadcastReceiver {
+        private final Activity mActivity;
+        WifiChangeReceiver(Activity activity) {
+            mActivity = activity;
+        }
+
+        @Override
+        public void onReceive(Context content, Intent intent) {
+            String action = intent.getAction();
+            if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
+                int state = intent.getIntExtra(
+                        WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_DISABLED);
+                if (state == WifiManager.WIFI_STATE_DISABLED) {
+                    mActivity.finish();
+                }
+            } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
+                NetworkInfo networkInfo = (NetworkInfo) intent.getParcelableExtra(
+                        WifiManager.EXTRA_NETWORK_INFO);
+                if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
+                    if (!networkInfo.isConnected()) {
+                        mActivity.finish();
+                        return;
+                    }
+                    WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
+                    if (wifiInfo == null || wifiInfo.getNetworkId() == -1) {
+                        mActivity.finish();
+                        return;
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+
+        IntentFilter filter = new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION);
+        filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+        registerReceiver(mWifiChangeReceiver, filter);
+    }
+
+    @Override
+    protected void onStop() {
+        if (mWifiChangeReceiver != null) {
+            unregisterReceiver(mWifiChangeReceiver);
+        }
+        super.onStop();
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        finish();
+    }
+}
diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp
index 4efe934..6589c84 100644
--- a/packages/Tethering/Android.bp
+++ b/packages/Tethering/Android.bp
@@ -16,7 +16,9 @@
 
 java_defaults {
     name: "TetheringAndroidLibraryDefaults",
-    sdk_version: "system_current",
+    // TODO (b/146757305): change to module API once available
+    // TODO (b/148190005): change to module-libs-api-stubs-current once it is ready.
+    sdk_version: "core_platform",
     srcs: [
         "src/**/*.java",
         ":framework-tethering-shared-srcs",
@@ -33,8 +35,15 @@
         "net-utils-framework-common",
     ],
     libs: [
+        // Order matters: framework-tethering needs to be before the system stubs, otherwise
+        // hidden fields in the framework-tethering classes (which are also used to generate stubs)
+        // will not be found.
         "framework-tethering",
+        "android_system_stubs_current",
+        "framework-res",
         "unsupportedappusage",
+        "android_system_stubs_current",
+        "framework-res",
     ],
     plugins: ["java_api_finder"],
     manifest: "AndroidManifestBase.xml",
@@ -82,12 +91,13 @@
 // Common defaults for compiling the actual APK.
 java_defaults {
     name: "TetheringAppDefaults",
-    sdk_version: "system_current",
+    // TODO (b/146757305): change to module API once available
+    // TODO (b/148190005): change to module-libs-api-stubs-current once it is ready.
+    sdk_version: "core_platform",
     privileged: true,
     // Build system doesn't track transitive dependeicies for jni_libs, list all the dependencies
     // explicitly.
     jni_libs: [
-        "liblog",
         "libnativehelper_compat_libc++",
         "libtetherutilsjni",
     ],
@@ -95,7 +105,12 @@
         "res",
     ],
     libs: [
+        // Order matters: framework-tethering needs to be before the system stubs, otherwise
+        // hidden fields in the framework-tethering classes (which are also used to generate stubs)
+        // will not be found.
         "framework-tethering",
+        "android_system_stubs_current",
+        "framework-res",
     ],
     jarjar_rules: "jarjar-rules.txt",
     optimize: {
diff --git a/packages/Tethering/TEST_MAPPING b/packages/Tethering/TEST_MAPPING
new file mode 100644
index 0000000..73254cd
--- /dev/null
+++ b/packages/Tethering/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "postsubmit": [
+    {
+      "name": "TetheringTests"
+    }
+  ]
+}
diff --git a/packages/Tethering/common/TetheringLib/Android.bp b/packages/Tethering/common/TetheringLib/Android.bp
index e0adb34d..5b73dd5 100644
--- a/packages/Tethering/common/TetheringLib/Android.bp
+++ b/packages/Tethering/common/TetheringLib/Android.bp
@@ -41,12 +41,11 @@
 
 java_library {
     name: "framework-tethering",
-    sdk_version: "system_current",
+    sdk_version: "module_current",
     srcs: [
         "src/android/net/TetheredClient.java",
         "src/android/net/TetheringManager.java",
         "src/android/net/TetheringConstants.java",
-        ":framework-tethering-annotations",
     ],
     static_libs: [
         "tethering-aidl-interfaces-java",
@@ -55,20 +54,36 @@
     installable: true,
 
     libs: [
-        "android_system_stubs_current",
+        "framework-annotations-lib",
     ],
 
     hostdex: true, // for hiddenapi check
-    visibility: [
-        "//frameworks/base/packages/Tethering:__subpackages__",
-        //TODO(b/147200698) remove below lines when the platform is built with stubs
-        "//frameworks/base",
-        "//frameworks/base/services",
-        "//frameworks/base/services/core",
-    ],
+    visibility: ["//frameworks/base/packages/Tethering:__subpackages__"],
     apex_available: ["com.android.tethering"],
 }
 
+droidstubs {
+    name: "framework-tethering-stubs-sources",
+    defaults: ["framework-module-stubs-defaults-module_libs_api"],
+    srcs: [
+        "src/android/net/TetheredClient.java",
+        "src/android/net/TetheringManager.java",
+        "src/android/net/TetheringConstants.java",
+    ],
+    libs: [
+        "tethering-aidl-interfaces-java",
+        "framework-all",
+    ],
+    sdk_version: "core_platform",
+}
+
+java_library {
+    name: "framework-tethering-stubs",
+    srcs: [":framework-tethering-stubs-sources"],
+    libs: ["framework-all"],
+    sdk_version: "core_platform",
+}
+
 filegroup {
     name: "framework-tethering-srcs",
     srcs: [
diff --git a/packages/Tethering/common/TetheringLib/jarjar-rules.txt b/packages/Tethering/common/TetheringLib/jarjar-rules.txt
index 1403bba..e459fad 100644
--- a/packages/Tethering/common/TetheringLib/jarjar-rules.txt
+++ b/packages/Tethering/common/TetheringLib/jarjar-rules.txt
@@ -1,2 +1 @@
-rule android.annotation.** com.android.networkstack.tethering.annotation.@1
-rule com.android.internal.annotations.** com.android.networkstack.tethering.annotation.@1
\ No newline at end of file
+# jarjar rules for the bootclasspath tethering framework library here
\ No newline at end of file
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl b/packages/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl
index 5febe73..8be7964 100644
--- a/packages/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl
+++ b/packages/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl
@@ -1,16 +1,16 @@
-/**
- * Copyright (c) 2019, The Android Open Source Project
+/*
+ * Copyright (C) 2020 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
+ *      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 perNmissions and
+ * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 package android.net;
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl b/packages/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl
index 28a810d..a554193 100644
--- a/packages/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl
+++ b/packages/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl
@@ -17,6 +17,7 @@
 package android.net;
 
 import android.net.Network;
+import android.net.TetheredClient;
 import android.net.TetheringConfigurationParcel;
 import android.net.TetheringCallbackStartedParcel;
 import android.net.TetherStatesParcel;
@@ -33,4 +34,5 @@
     void onUpstreamChanged(in Network network);
     void onConfigurationChanged(in TetheringConfigurationParcel config);
     void onTetherStatesChanged(in TetherStatesParcel states);
+    void onTetherClientsChanged(in List<TetheredClient> clients);
 }
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheredClient.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheredClient.java
index f5c9664..8b8b9e5 100644
--- a/packages/Tethering/common/TetheringLib/src/android/net/TetheredClient.java
+++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheredClient.java
@@ -16,6 +16,8 @@
 
 package android.net;
 
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
@@ -34,6 +36,7 @@
  * @hide
  */
 @SystemApi
+@SystemApi(client = MODULE_LIBRARIES)
 @TestApi
 public final class TetheredClient implements Parcelable {
     @NonNull
@@ -188,6 +191,15 @@
                 return new AddressInfo[size];
             }
         };
+
+        @NonNull
+        @Override
+        public String toString() {
+            return "AddressInfo {"
+                    + mAddress
+                    + (mHostname != null ? ", hostname " + mHostname : "")
+                    + "}";
+        }
     }
 
     @Override
@@ -209,4 +221,13 @@
             return new TetheredClient[size];
         }
     };
+
+    @NonNull
+    @Override
+    public String toString() {
+        return "TetheredClient {hwAddr " + mMacAddress
+                + ", addresses " + mAddresses
+                + ", tetheringType " + mTetheringType
+                + "}";
+    }
 }
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl b/packages/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl
index 14ee2d3..c064aa4 100644
--- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl
+++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl
@@ -17,6 +17,7 @@
 package android.net;
 
 import android.net.Network;
+import android.net.TetheredClient;
 import android.net.TetheringConfigurationParcel;
 import android.net.TetherStatesParcel;
 
@@ -29,4 +30,5 @@
     Network upstreamNetwork;
     TetheringConfigurationParcel config;
     TetherStatesParcel states;
+    List<TetheredClient> tetheredClients;
 }
\ No newline at end of file
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java
index 00cf98e..a18f5da 100644
--- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java
+++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java
@@ -16,6 +16,9 @@
 
 package android.net;
 
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
+import android.annotation.SystemApi;
 import android.os.ResultReceiver;
 
 /**
@@ -28,39 +31,33 @@
  * symbols from framework-tethering even when they are in a non-hidden class.
  * @hide
  */
+@SystemApi(client = MODULE_LIBRARIES)
 public class TetheringConstants {
+    /** An explicit private class to avoid exposing constructor.*/
+    private TetheringConstants() { }
+
     /**
      * Extra used for communicating with the TetherService. Includes the type of tethering to
      * enable if any.
-     *
-     * {@hide}
      */
     public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType";
     /**
      * Extra used for communicating with the TetherService. Includes the type of tethering for
      * which to cancel provisioning.
-     *
-     * {@hide}
      */
     public static final String EXTRA_REM_TETHER_TYPE = "extraRemTetherType";
     /**
      * Extra used for communicating with the TetherService. True to schedule a recheck of tether
      * provisioning.
-     *
-     * {@hide}
      */
     public static final String EXTRA_SET_ALARM = "extraSetAlarm";
     /**
      * Tells the TetherService to run a provision check now.
-     *
-     * {@hide}
      */
     public static final String EXTRA_RUN_PROVISION = "extraRunProvision";
     /**
      * Extra used for communicating with the TetherService. Contains the {@link ResultReceiver}
      * which will receive provisioning results. Can be left empty.
-     *
-     * {@hide}
      */
     public static final String EXTRA_PROVISION_CALLBACK = "extraProvisionCallback";
 }
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index 53a358f..bfa962a 100644
--- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -15,6 +15,8 @@
  */
 package android.net;
 
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -50,6 +52,7 @@
  * @hide
  */
 @SystemApi
+@SystemApi(client = MODULE_LIBRARIES)
 @TestApi
 public class TetheringManager {
     private static final String TAG = TetheringManager.class.getSimpleName();
@@ -177,6 +180,7 @@
      *                          service is not connected.
      * {@hide}
      */
+    @SystemApi(client = MODULE_LIBRARIES)
     public TetheringManager(@NonNull final Context context,
             @NonNull Supplier<IBinder> connectorSupplier) {
         mContext = context;
@@ -371,6 +375,9 @@
             mTetherStatesParcel = states;
         }
 
+        @Override
+        public void onTetherClientsChanged(List<TetheredClient> clients) { }
+
         public void waitForStarted() {
             mWaitForCallback.block(DEFAULT_TIMEOUT_MS);
             throwIfPermissionFailure(mError);
@@ -395,6 +402,7 @@
      * {@hide}
      */
     @Deprecated
+    @SystemApi(client = MODULE_LIBRARIES)
     public int tether(@NonNull final String iface) {
         final String callerPkg = mContext.getOpPackageName();
         Log.i(TAG, "tether caller:" + callerPkg);
@@ -418,6 +426,7 @@
      * {@hide}
      */
     @Deprecated
+    @SystemApi(client = MODULE_LIBRARIES)
     public int untether(@NonNull final String iface) {
         final String callerPkg = mContext.getOpPackageName();
         Log.i(TAG, "untether caller:" + callerPkg);
@@ -444,6 +453,7 @@
      * {@hide}
      */
     @Deprecated
+    @SystemApi(client = MODULE_LIBRARIES)
     public int setUsbTethering(final boolean enable) {
         final String callerPkg = mContext.getOpPackageName();
         Log.i(TAG, "setUsbTethering caller:" + callerPkg);
@@ -702,6 +712,7 @@
      * {@hide}
      */
     // TODO: improve the usage of ResultReceiver, b/145096122
+    @SystemApi(client = MODULE_LIBRARIES)
     public void requestLatestTetheringEntitlementResult(final int type,
             @NonNull final ResultReceiver receiver, final boolean showEntitlementUi) {
         final String callerPkg = mContext.getOpPackageName();
@@ -913,6 +924,7 @@
                         sendRegexpsChanged(parcel.config);
                         maybeSendTetherableIfacesChangedCallback(parcel.states);
                         maybeSendTetheredIfacesChangedCallback(parcel.states);
+                        callback.onClientsChanged(parcel.tetheredClients);
                     });
                 }
 
@@ -943,6 +955,11 @@
                         maybeSendTetheredIfacesChangedCallback(states);
                     });
                 }
+
+                @Override
+                public void onTetherClientsChanged(final List<TetheredClient> clients) {
+                    executor.execute(() -> callback.onClientsChanged(clients));
+                }
             };
             getConnector(c -> c.registerTetheringEventCallback(remoteCallback, callerPkg));
             mTetheringEventCallbacks.put(callback, remoteCallback);
@@ -982,6 +999,7 @@
      *               interface
      * @hide
      */
+    @SystemApi(client = MODULE_LIBRARIES)
     public int getLastTetherError(@NonNull final String iface) {
         mCallback.waitForStarted();
         if (mTetherStatesParcel == null) return TETHER_ERROR_NO_ERROR;
@@ -1004,6 +1022,7 @@
      *        what interfaces are considered tetherable usb interfaces.
      * @hide
      */
+    @SystemApi(client = MODULE_LIBRARIES)
     public @NonNull String[] getTetherableUsbRegexs() {
         mCallback.waitForStarted();
         return mTetheringConfiguration.tetherableUsbRegexs;
@@ -1018,6 +1037,7 @@
      *        what interfaces are considered tetherable wifi interfaces.
      * @hide
      */
+    @SystemApi(client = MODULE_LIBRARIES)
     public @NonNull String[] getTetherableWifiRegexs() {
         mCallback.waitForStarted();
         return mTetheringConfiguration.tetherableWifiRegexs;
@@ -1032,6 +1052,7 @@
      *        what interfaces are considered tetherable bluetooth interfaces.
      * @hide
      */
+    @SystemApi(client = MODULE_LIBRARIES)
     public @NonNull String[] getTetherableBluetoothRegexs() {
         mCallback.waitForStarted();
         return mTetheringConfiguration.tetherableBluetoothRegexs;
@@ -1044,6 +1065,7 @@
      * @return an array of 0 or more Strings of tetherable interface names.
      * @hide
      */
+    @SystemApi(client = MODULE_LIBRARIES)
     public @NonNull String[] getTetherableIfaces() {
         mCallback.waitForStarted();
         if (mTetherStatesParcel == null) return new String[0];
@@ -1057,6 +1079,7 @@
      * @return an array of 0 or more String of currently tethered interface names.
      * @hide
      */
+    @SystemApi(client = MODULE_LIBRARIES)
     public @NonNull String[] getTetheredIfaces() {
         mCallback.waitForStarted();
         if (mTetherStatesParcel == null) return new String[0];
@@ -1076,6 +1099,7 @@
      *        which failed to tether.
      * @hide
      */
+    @SystemApi(client = MODULE_LIBRARIES)
     public @NonNull String[] getTetheringErroredIfaces() {
         mCallback.waitForStarted();
         if (mTetherStatesParcel == null) return new String[0];
@@ -1103,6 +1127,7 @@
      * @return a boolean - {@code true} indicating Tethering is supported.
      * @hide
      */
+    @SystemApi(client = MODULE_LIBRARIES)
     public boolean isTetheringSupported() {
         final String callerPkg = mContext.getOpPackageName();
 
diff --git a/packages/Tethering/src/android/net/ip/IpServer.java b/packages/Tethering/src/android/net/ip/IpServer.java
index 2653b6d..38f8609 100644
--- a/packages/Tethering/src/android/net/ip/IpServer.java
+++ b/packages/Tethering/src/android/net/ip/IpServer.java
@@ -19,6 +19,7 @@
 import static android.net.InetAddresses.parseNumericAddress;
 import static android.net.RouteInfo.RTN_UNICAST;
 import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
+import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
 import static android.net.util.NetworkConstants.FF;
 import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH;
 import static android.net.util.NetworkConstants.asByte;
@@ -29,18 +30,24 @@
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
+import android.net.MacAddress;
 import android.net.RouteInfo;
+import android.net.TetheredClient;
 import android.net.TetheringManager;
+import android.net.dhcp.DhcpLeaseParcelable;
 import android.net.dhcp.DhcpServerCallbacks;
 import android.net.dhcp.DhcpServingParamsParcel;
 import android.net.dhcp.DhcpServingParamsParcelExt;
+import android.net.dhcp.IDhcpLeaseCallbacks;
 import android.net.dhcp.IDhcpServer;
+import android.net.ip.IpNeighborMonitor.NeighborEvent;
 import android.net.ip.RouterAdvertisementDaemon.RaParams;
 import android.net.shared.NetdUtils;
 import android.net.shared.RouteUtils;
 import android.net.util.InterfaceParams;
 import android.net.util.InterfaceSet;
 import android.net.util.SharedLog;
+import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
@@ -48,16 +55,24 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.util.MessageUtils;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 
+import java.io.IOException;
 import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
+import java.net.NetworkInterface;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Objects;
 import java.util.Random;
 import java.util.Set;
@@ -130,10 +145,21 @@
          * @param newLp the new LinkProperties to report
          */
         public void updateLinkProperties(IpServer who, LinkProperties newLp) { }
+
+        /**
+         * Notify that the DHCP leases changed in one of the IpServers.
+         */
+        public void dhcpLeasesChanged() { }
     }
 
     /** Capture IpServer dependencies, for injection. */
     public abstract static class Dependencies {
+        /** Create an IpNeighborMonitor to be used by this IpServer */
+        public IpNeighborMonitor getIpNeighborMonitor(Handler handler, SharedLog log,
+                IpNeighborMonitor.NeighborEventConsumer consumer) {
+            return new IpNeighborMonitor(handler, log, consumer);
+        }
+
         /** Create a RouterAdvertisementDaemon instance to be used by IpServer.*/
         public RouterAdvertisementDaemon getRouterAdvertisementDaemon(InterfaceParams ifParams) {
             return new RouterAdvertisementDaemon(ifParams);
@@ -144,6 +170,15 @@
             return InterfaceParams.getByName(ifName);
         }
 
+        /** Get |ifName|'s interface index. */
+        public int getIfindex(String ifName) {
+            try {
+                return NetworkInterface.getByName(ifName).getIndex();
+            } catch (IOException | NullPointerException e) {
+                Log.e(TAG, "Can't determine interface index for interface " + ifName);
+                return 0;
+            }
+        }
         /** Create a DhcpServer instance to be used by IpServer. */
         public abstract void makeDhcpServer(String ifName, DhcpServingParamsParcel params,
                 DhcpServerCallbacks cb);
@@ -169,6 +204,8 @@
     public static final int CMD_TETHER_CONNECTION_CHANGED   = BASE_IPSERVER + 9;
     // new IPv6 tethering parameters need to be processed
     public static final int CMD_IPV6_TETHER_UPDATE          = BASE_IPSERVER + 10;
+    // new neighbor cache entry on our interface
+    public static final int CMD_NEIGHBOR_EVENT              = BASE_IPSERVER + 11;
 
     private final State mInitialState;
     private final State mLocalHotspotState;
@@ -205,6 +242,42 @@
     private IDhcpServer mDhcpServer;
     private RaParams mLastRaParams;
     private LinkAddress mIpv4Address;
+    @NonNull
+    private List<TetheredClient> mDhcpLeases = Collections.emptyList();
+
+    private int mLastIPv6UpstreamIfindex = 0;
+
+    private class MyNeighborEventConsumer implements IpNeighborMonitor.NeighborEventConsumer {
+        public void accept(NeighborEvent e) {
+            sendMessage(CMD_NEIGHBOR_EVENT, e);
+        }
+    }
+
+    static class Ipv6ForwardingRule {
+        public final int upstreamIfindex;
+        public final int downstreamIfindex;
+        public final Inet6Address address;
+        public final MacAddress srcMac;
+        public final MacAddress dstMac;
+
+        Ipv6ForwardingRule(int upstreamIfindex, int downstreamIfIndex, Inet6Address address,
+                MacAddress srcMac, MacAddress dstMac) {
+            this.upstreamIfindex = upstreamIfindex;
+            this.downstreamIfindex = downstreamIfIndex;
+            this.address = address;
+            this.srcMac = srcMac;
+            this.dstMac = dstMac;
+        }
+
+        public Ipv6ForwardingRule onNewUpstream(int newUpstreamIfindex) {
+            return new Ipv6ForwardingRule(newUpstreamIfindex, downstreamIfindex, address, srcMac,
+                    dstMac);
+        }
+    }
+    private final LinkedHashMap<Inet6Address, Ipv6ForwardingRule> mIpv6ForwardingRules =
+            new LinkedHashMap<>();
+
+    private final IpNeighborMonitor mIpNeighborMonitor;
 
     public IpServer(
             String ifaceName, Looper looper, int interfaceType, SharedLog log,
@@ -223,6 +296,12 @@
         mLastError = TetheringManager.TETHER_ERROR_NO_ERROR;
         mServingMode = STATE_AVAILABLE;
 
+        mIpNeighborMonitor = mDeps.getIpNeighborMonitor(getHandler(), mLog,
+                new MyNeighborEventConsumer());
+        if (!mIpNeighborMonitor.start()) {
+            mLog.e("Failed to create IpNeighborMonitor on " + mIfaceName);
+        }
+
         mInitialState = new InitialState();
         mLocalHotspotState = new LocalHotspotState();
         mTetheredState = new TetheredState();
@@ -262,6 +341,14 @@
         return new LinkProperties(mLinkProperties);
     }
 
+    /**
+     * Get the latest list of DHCP leases that was reported. Must be called on the IpServer looper
+     * thread.
+     */
+    public List<TetheredClient> getAllLeases() {
+        return Collections.unmodifiableList(mDhcpLeases);
+    }
+
     /** Stop this IpServer. After this is called this IpServer should not be used any more. */
     public void stop() {
         sendMessage(CMD_INTERFACE_DOWN);
@@ -334,7 +421,7 @@
 
                 mDhcpServer = server;
                 try {
-                    mDhcpServer.start(new OnHandlerStatusCallback() {
+                    mDhcpServer.startWithCallbacks(new OnHandlerStatusCallback() {
                         @Override
                         public void callback(int startStatusCode) {
                             if (startStatusCode != STATUS_SUCCESS) {
@@ -342,7 +429,7 @@
                                 handleError();
                             }
                         }
-                    });
+                    }, new DhcpLeaseCallback());
                 } catch (RemoteException e) {
                     throw new IllegalStateException(e);
                 }
@@ -355,6 +442,48 @@
         }
     }
 
+    private class DhcpLeaseCallback extends IDhcpLeaseCallbacks.Stub {
+        @Override
+        public void onLeasesChanged(List<DhcpLeaseParcelable> leaseParcelables) {
+            final ArrayList<TetheredClient> leases = new ArrayList<>();
+            for (DhcpLeaseParcelable lease : leaseParcelables) {
+                final LinkAddress address = new LinkAddress(
+                        intToInet4AddressHTH(lease.netAddr), lease.prefixLength);
+
+                final MacAddress macAddress;
+                try {
+                    macAddress = MacAddress.fromBytes(lease.hwAddr);
+                } catch (IllegalArgumentException e) {
+                    Log.wtf(TAG, "Invalid address received from DhcpServer: "
+                            + Arrays.toString(lease.hwAddr));
+                    return;
+                }
+
+                final TetheredClient.AddressInfo addressInfo = new TetheredClient.AddressInfo(
+                        address, lease.hostname, lease.expTime);
+                leases.add(new TetheredClient(
+                        macAddress,
+                        Collections.singletonList(addressInfo),
+                        mInterfaceType));
+            }
+
+            getHandler().post(() -> {
+                mDhcpLeases = leases;
+                mCallback.dhcpLeasesChanged();
+            });
+        }
+
+        @Override
+        public int getInterfaceVersion() {
+            return this.VERSION;
+        }
+
+        @Override
+        public String getInterfaceHash() throws RemoteException {
+            return this.HASH;
+        }
+    }
+
     private boolean startDhcp(Inet4Address addr, int prefixLen) {
         if (mUsingLegacyDhcp) {
             return true;
@@ -388,6 +517,8 @@
                             mLastError = TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR;
                             // Not much more we can do here
                         }
+                        mDhcpLeases.clear();
+                        getHandler().post(mCallback::dhcpLeasesChanged);
                     }
                 });
                 mDhcpServer = null;
@@ -538,13 +669,21 @@
         }
 
         RaParams params = null;
+        int upstreamIfindex = 0;
 
         if (v6only != null) {
+            final String upstreamIface = v6only.getInterfaceName();
+
             params = new RaParams();
-            params.mtu = v6only.getMtu();
+            // We advertise an mtu lower by 16, which is the closest multiple of 8 >= 14,
+            // the ethernet header size.  This makes kernel ebpf tethering offload happy.
+            // This hack should be reverted once we have the kernel fixed up.
+            // Note: this will automatically clamp to at least 1280 (ipv6 minimum mtu)
+            // see RouterAdvertisementDaemon.java putMtu()
+            params.mtu = v6only.getMtu() - 16;
             params.hasDefaultRoute = v6only.hasIpv6DefaultRoute();
 
-            if (params.hasDefaultRoute) params.hopLimit = getHopLimit(v6only.getInterfaceName());
+            if (params.hasDefaultRoute) params.hopLimit = getHopLimit(upstreamIface);
 
             for (LinkAddress linkAddr : v6only.getLinkAddresses()) {
                 if (linkAddr.getPrefixLength() != RFC7421_PREFIX_LENGTH) continue;
@@ -558,12 +697,18 @@
                     params.dnses.add(dnsServer);
                 }
             }
+
+            upstreamIfindex = mDeps.getIfindex(upstreamIface);
         }
+
         // If v6only is null, we pass in null to setRaParams(), which handles
         // deprecation of any existing RA data.
 
         setRaParams(params);
         mLastIPv6LinkProperties = v6only;
+
+        updateIpv6ForwardingRules(mLastIPv6UpstreamIfindex, upstreamIfindex, null);
+        mLastIPv6UpstreamIfindex = upstreamIfindex;
     }
 
     private void configureLocalIPv6Routes(
@@ -658,6 +803,87 @@
         }
     }
 
+    private void addIpv6ForwardingRule(Ipv6ForwardingRule rule) {
+        try {
+            mNetd.tetherRuleAddDownstreamIpv6(mInterfaceParams.index, rule.upstreamIfindex,
+                    rule.address.getAddress(),  mInterfaceParams.macAddr.toByteArray(),
+                    rule.dstMac.toByteArray());
+            mIpv6ForwardingRules.put(rule.address, rule);
+        } catch (RemoteException | ServiceSpecificException e) {
+            mLog.e("Could not add IPv6 downstream rule: ", e);
+        }
+    }
+
+    private void removeIpv6ForwardingRule(Ipv6ForwardingRule rule, boolean removeFromMap) {
+        try {
+            mNetd.tetherRuleRemoveDownstreamIpv6(rule.upstreamIfindex, rule.address.getAddress());
+            if (removeFromMap) {
+                mIpv6ForwardingRules.remove(rule.address);
+            }
+        } catch (RemoteException | ServiceSpecificException e) {
+            mLog.e("Could not remove IPv6 downstream rule: ", e);
+        }
+    }
+
+    private void clearIpv6ForwardingRules() {
+        for (Ipv6ForwardingRule rule : mIpv6ForwardingRules.values()) {
+            removeIpv6ForwardingRule(rule, false /*removeFromMap*/);
+        }
+        mIpv6ForwardingRules.clear();
+    }
+
+    // Convenience method to replace a rule with the same rule on a new upstream interface.
+    // Allows replacing the rules in one iteration pass without ConcurrentModificationExceptions.
+    // Relies on the fact that rules are in a map indexed by IP address.
+    private void updateIpv6ForwardingRule(Ipv6ForwardingRule rule, int newIfindex) {
+        addIpv6ForwardingRule(rule.onNewUpstream(newIfindex));
+        removeIpv6ForwardingRule(rule, false /*removeFromMap*/);
+    }
+
+    // Handles all updates to IPv6 forwarding rules. These can currently change only if the upstream
+    // changes or if a neighbor event is received.
+    private void updateIpv6ForwardingRules(int prevUpstreamIfindex, int upstreamIfindex,
+            NeighborEvent e) {
+        // If we no longer have an upstream, clear forwarding rules and do nothing else.
+        if (upstreamIfindex == 0) {
+            clearIpv6ForwardingRules();
+            return;
+        }
+
+        // If the upstream interface has changed, remove all rules and re-add them with the new
+        // upstream interface.
+        if (prevUpstreamIfindex != upstreamIfindex) {
+            for (Ipv6ForwardingRule rule : mIpv6ForwardingRules.values()) {
+                updateIpv6ForwardingRule(rule, upstreamIfindex);
+            }
+        }
+
+        // If we're here to process a NeighborEvent, do so now.
+        // mInterfaceParams must be non-null or the event would not have arrived.
+        if (e == null) return;
+        if (!(e.ip instanceof Inet6Address) || e.ip.isMulticastAddress()
+                || e.ip.isLoopbackAddress() || e.ip.isLinkLocalAddress()) {
+            return;
+        }
+
+        Ipv6ForwardingRule rule = new Ipv6ForwardingRule(upstreamIfindex,
+                mInterfaceParams.index, (Inet6Address) e.ip, mInterfaceParams.macAddr,
+                e.macAddr);
+        if (e.isValid()) {
+            addIpv6ForwardingRule(rule);
+        } else {
+            removeIpv6ForwardingRule(rule, true /*removeFromMap*/);
+        }
+    }
+
+    private void handleNeighborEvent(NeighborEvent e) {
+        if (mInterfaceParams != null
+                && mInterfaceParams.index == e.ifindex
+                && mInterfaceParams.hasMacAddress) {
+            updateIpv6ForwardingRules(mLastIPv6UpstreamIfindex, mLastIPv6UpstreamIfindex, e);
+        }
+    }
+
     private byte getHopLimit(String upstreamIface) {
         try {
             int upstreamHopLimit = Integer.parseUnsignedInt(
@@ -883,6 +1109,7 @@
 
             for (String ifname : mUpstreamIfaceSet.ifnames) cleanupUpstreamInterface(ifname);
             mUpstreamIfaceSet = null;
+            clearIpv6ForwardingRules();
         }
 
         private void cleanupUpstreamInterface(String upstreamIface) {
@@ -945,6 +1172,9 @@
                         }
                     }
                     break;
+                case CMD_NEIGHBOR_EVENT:
+                    handleNeighborEvent((NeighborEvent) message.obj);
+                    break;
                 default:
                     return false;
             }
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/ConnectedClientsTracker.java b/packages/Tethering/src/com/android/server/connectivity/tethering/ConnectedClientsTracker.java
new file mode 100644
index 0000000..cdd1a5d
--- /dev/null
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/ConnectedClientsTracker.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2020 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 static android.net.TetheringManager.TETHERING_WIFI;
+
+import android.net.MacAddress;
+import android.net.TetheredClient;
+import android.net.TetheredClient.AddressInfo;
+import android.net.ip.IpServer;
+import android.net.wifi.WifiClient;
+import android.os.SystemClock;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Tracker for clients connected to downstreams.
+ *
+ * <p>This class is not thread safe, it is intended to be used only from the tethering handler
+ * thread.
+ */
+public class ConnectedClientsTracker {
+    private final Clock mClock;
+
+    @NonNull
+    private List<WifiClient> mLastWifiClients = Collections.emptyList();
+    @NonNull
+    private List<TetheredClient> mLastTetheredClients = Collections.emptyList();
+
+    @VisibleForTesting
+    static class Clock {
+        public long elapsedRealtime() {
+            return SystemClock.elapsedRealtime();
+        }
+    }
+
+    public ConnectedClientsTracker() {
+        this(new Clock());
+    }
+
+    @VisibleForTesting
+    ConnectedClientsTracker(Clock clock) {
+        mClock = clock;
+    }
+
+    /**
+     * Update the tracker with new connected clients.
+     *
+     * <p>The new list can be obtained through {@link #getLastTetheredClients()}.
+     * @param ipServers The IpServers used to assign addresses to clients.
+     * @param wifiClients The list of L2-connected WiFi clients. Null for no change since last
+     *                    update.
+     * @return True if the list of clients changed since the last calculation.
+     */
+    public boolean updateConnectedClients(
+            Iterable<IpServer> ipServers, @Nullable List<WifiClient> wifiClients) {
+        final long now = mClock.elapsedRealtime();
+
+        if (wifiClients != null) {
+            mLastWifiClients = wifiClients;
+        }
+        final Set<MacAddress> wifiClientMacs = getClientMacs(mLastWifiClients);
+
+        // Build the list of non-expired leases from all IpServers, grouped by mac address
+        final Map<MacAddress, TetheredClient> clientsMap = new HashMap<>();
+        for (IpServer server : ipServers) {
+            for (TetheredClient client : server.getAllLeases()) {
+                if (client.getTetheringType() == TETHERING_WIFI
+                        && !wifiClientMacs.contains(client.getMacAddress())) {
+                    // Skip leases of WiFi clients that are not (or no longer) L2-connected
+                    continue;
+                }
+                final TetheredClient prunedClient = pruneExpired(client, now);
+                if (prunedClient == null) continue; // All addresses expired
+
+                addLease(clientsMap, prunedClient);
+            }
+        }
+
+        // TODO: add IPv6 addresses from netlink
+
+        // Add connected WiFi clients that do not have any known address
+        for (MacAddress client : wifiClientMacs) {
+            if (clientsMap.containsKey(client)) continue;
+            clientsMap.put(client, new TetheredClient(
+                    client, Collections.emptyList() /* addresses */, TETHERING_WIFI));
+        }
+
+        final HashSet<TetheredClient> clients = new HashSet<>(clientsMap.values());
+        final boolean clientsChanged = clients.size() != mLastTetheredClients.size()
+                || !clients.containsAll(mLastTetheredClients);
+        mLastTetheredClients = Collections.unmodifiableList(new ArrayList<>(clients));
+        return clientsChanged;
+    }
+
+    private static void addLease(Map<MacAddress, TetheredClient> clientsMap, TetheredClient lease) {
+        final TetheredClient aggregateClient = clientsMap.getOrDefault(
+                lease.getMacAddress(), lease);
+        if (aggregateClient == lease) {
+            // This is the first lease with this mac address
+            clientsMap.put(lease.getMacAddress(), lease);
+            return;
+        }
+
+        // Only add the address info; this assumes that the tethering type is the same when the mac
+        // address is the same. If a client is connected through different tethering types with the
+        // same mac address, connected clients callbacks will report all of its addresses under only
+        // one of these tethering types. This keeps the API simple considering that such a scenario
+        // would really be a rare edge case.
+        clientsMap.put(lease.getMacAddress(), aggregateClient.addAddresses(lease));
+    }
+
+    /**
+     * Get the last list of tethered clients, as calculated in {@link #updateConnectedClients}.
+     *
+     * <p>The returned list is immutable.
+     */
+    @NonNull
+    public List<TetheredClient> getLastTetheredClients() {
+        return mLastTetheredClients;
+    }
+
+    private static boolean hasExpiredAddress(List<AddressInfo> addresses, long now) {
+        for (AddressInfo info : addresses) {
+            if (info.getExpirationTime() <= now) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Nullable
+    private static TetheredClient pruneExpired(TetheredClient client, long now) {
+        final List<AddressInfo> addresses = client.getAddresses();
+        if (addresses.size() == 0) return null;
+        if (!hasExpiredAddress(addresses, now)) return client;
+
+        final ArrayList<AddressInfo> newAddrs = new ArrayList<>(addresses.size() - 1);
+        for (AddressInfo info : addresses) {
+            if (info.getExpirationTime() > now) {
+                newAddrs.add(info);
+            }
+        }
+
+        if (newAddrs.size() == 0) {
+            return null;
+        }
+        return new TetheredClient(client.getMacAddress(), newAddrs, client.getTetheringType());
+    }
+
+    @NonNull
+    private static Set<MacAddress> getClientMacs(@NonNull List<WifiClient> clients) {
+        final Set<MacAddress> macs = new HashSet<>(clients.size());
+        for (WifiClient c : clients) {
+            macs.add(c.getMacAddress());
+        }
+        return macs;
+    }
+}
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
index 1edbbf8..84ca656 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
@@ -16,6 +16,8 @@
 
 package com.android.server.connectivity.tethering;
 
+import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.Manifest.permission.NETWORK_STACK;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.hardware.usb.UsbManager.USB_CONFIGURED;
 import static android.hardware.usb.UsbManager.USB_CONNECTED;
@@ -24,6 +26,7 @@
 import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
 import static android.net.ConnectivityManager.EXTRA_NETWORK_INFO;
+import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
 import static android.net.TetheringManager.ACTION_TETHER_STATE_CHANGED;
 import static android.net.TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY;
 import static android.net.TetheringManager.EXTRA_ACTIVE_TETHER;
@@ -79,6 +82,7 @@
 import android.net.Network;
 import android.net.NetworkInfo;
 import android.net.TetherStatesParcel;
+import android.net.TetheredClient;
 import android.net.TetheringCallbackStartedParcel;
 import android.net.TetheringConfigurationParcel;
 import android.net.TetheringRequestParcel;
@@ -89,6 +93,7 @@
 import android.net.util.PrefixUtils;
 import android.net.util.SharedLog;
 import android.net.util.VersionedBroadcastListener;
+import android.net.wifi.WifiClient;
 import android.net.wifi.WifiManager;
 import android.net.wifi.p2p.WifiP2pGroup;
 import android.net.wifi.p2p.WifiP2pInfo;
@@ -128,8 +133,10 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.HashSet;
+import java.util.Collections;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.concurrent.RejectedExecutionException;
@@ -176,6 +183,17 @@
         }
     }
 
+    /**
+     * Cookie added when registering {@link android.net.TetheringManager.TetheringEventCallback}.
+     */
+    private static class CallbackCookie {
+        public final boolean hasListClientsPermission;
+
+        private CallbackCookie(boolean hasListClientsPermission) {
+            this.hasListClientsPermission = hasListClientsPermission;
+        }
+    }
+
     private final SharedLog mLog = new SharedLog(TAG);
     private final RemoteCallbackList<ITetheringEventCallback> mTetheringEventCallbacks =
             new RemoteCallbackList<>();
@@ -191,7 +209,8 @@
     private final UpstreamNetworkMonitor mUpstreamNetworkMonitor;
     // TODO: Figure out how to merge this and other downstream-tracking objects
     // into a single coherent structure.
-    private final HashSet<IpServer> mForwardedDownstreams;
+    // Use LinkedHashSet for predictable ordering order for ConnectedClientsTracker.
+    private final LinkedHashSet<IpServer> mForwardedDownstreams;
     private final VersionedBroadcastListener mCarrierConfigChange;
     private final TetheringDependencies mDeps;
     private final EntitlementManager mEntitlementMgr;
@@ -200,6 +219,7 @@
     private final NetdCallback mNetdCallback;
     private final UserRestrictionActionListener mTetheringRestriction;
     private final ActiveDataSubIdListener mActiveDataSubIdListener;
+    private final ConnectedClientsTracker mConnectedClientsTracker;
     private int mActiveDataSubId = INVALID_SUBSCRIPTION_ID;
     // All the usage of mTetheringEventCallback should run in the same thread.
     private ITetheringEventCallback mTetheringEventCallback = null;
@@ -234,6 +254,7 @@
         mPublicSync = new Object();
 
         mTetherStates = new ArrayMap<>();
+        mConnectedClientsTracker = new ConnectedClientsTracker();
 
         mTetherMasterSM = new TetherMasterSM("TetherMaster", mLooper, deps);
         mTetherMasterSM.start();
@@ -246,7 +267,7 @@
                 statsManager, mLog);
         mUpstreamNetworkMonitor = mDeps.getUpstreamNetworkMonitor(mContext, mTetherMasterSM, mLog,
                 TetherMasterSM.EVENT_UPSTREAM_CALLBACK);
-        mForwardedDownstreams = new HashSet<>();
+        mForwardedDownstreams = new LinkedHashSet<>();
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(ACTION_CARRIER_CONFIG_CHANGED);
@@ -291,6 +312,13 @@
 
         startStateMachineUpdaters(mHandler);
         startTrackDefaultNetwork();
+
+        final WifiManager wifiManager = getWifiManager();
+        if (wifiManager != null) {
+            wifiManager.registerSoftApCallback(
+                  mHandler::post /* executor */,
+                  new TetheringSoftApCallback());
+        }
     }
 
     private void startStateMachineUpdaters(Handler handler) {
@@ -385,6 +413,24 @@
         }
     }
 
+    private class TetheringSoftApCallback implements WifiManager.SoftApCallback {
+        // TODO: Remove onStateChanged override when this method has default on
+        // WifiManager#SoftApCallback interface.
+        // Wifi listener for state change of the soft AP
+        @Override
+        public void onStateChanged(final int state, final int failureReason) {
+            // Nothing
+        }
+
+        // Called by wifi when the number of soft AP clients changed.
+        @Override
+        public void onConnectedClientsChanged(final List<WifiClient> clients) {
+            if (mConnectedClientsTracker.updateConnectedClients(mForwardedDownstreams, clients)) {
+                reportTetherClientsChanged(mConnectedClientsTracker.getLastTetheredClients());
+            }
+        }
+    }
+
     void interfaceStatusChanged(String iface, boolean up) {
         // Never called directly: only called from interfaceLinkStateChanged.
         // See NetlinkHandler.cpp: notifyInterfaceChanged.
@@ -1938,14 +1984,21 @@
 
     /** Register tethering event callback */
     void registerTetheringEventCallback(ITetheringEventCallback callback) {
+        final boolean hasListPermission =
+                hasCallingPermission(NETWORK_SETTINGS)
+                        || hasCallingPermission(PERMISSION_MAINLINE_NETWORK_STACK)
+                        || hasCallingPermission(NETWORK_STACK);
         mHandler.post(() -> {
-            mTetheringEventCallbacks.register(callback);
+            mTetheringEventCallbacks.register(callback, new CallbackCookie(hasListPermission));
             final TetheringCallbackStartedParcel parcel = new TetheringCallbackStartedParcel();
             parcel.tetheringSupported = mDeps.isTetheringSupported();
             parcel.upstreamNetwork = mTetherUpstream;
             parcel.config = mConfig.toStableParcelable();
             parcel.states =
                     mTetherStatesParcel != null ? mTetherStatesParcel : emptyTetherStatesParcel();
+            parcel.tetheredClients = hasListPermission
+                    ? mConnectedClientsTracker.getLastTetheredClients()
+                    : Collections.emptyList();
             try {
                 callback.onCallbackStarted(parcel);
             } catch (RemoteException e) {
@@ -1965,6 +2018,10 @@
         return parcel;
     }
 
+    private boolean hasCallingPermission(@NonNull String permission) {
+        return mContext.checkCallingPermission(permission) == PERMISSION_GRANTED;
+    }
+
     /** Unregister tethering event callback */
     void unregisterTetheringEventCallback(ITetheringEventCallback callback) {
         mHandler.post(() -> {
@@ -2018,6 +2075,24 @@
         }
     }
 
+    private void reportTetherClientsChanged(List<TetheredClient> clients) {
+        final int length = mTetheringEventCallbacks.beginBroadcast();
+        try {
+            for (int i = 0; i < length; i++) {
+                try {
+                    final CallbackCookie cookie =
+                            (CallbackCookie) mTetheringEventCallbacks.getBroadcastCookie(i);
+                    if (!cookie.hasListClientsPermission) continue;
+                    mTetheringEventCallbacks.getBroadcastItem(i).onTetherClientsChanged(clients);
+                } catch (RemoteException e) {
+                    // Not really very much to do here.
+                }
+            }
+        } finally {
+            mTetheringEventCallbacks.finishBroadcast();
+        }
+    }
+
     void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer, @Nullable String[] args) {
         // Binder.java closes the resource for us.
         @SuppressWarnings("resource")
@@ -2109,6 +2184,14 @@
             public void updateLinkProperties(IpServer who, LinkProperties newLp) {
                 notifyLinkPropertiesChanged(who, newLp);
             }
+
+            @Override
+            public void dhcpLeasesChanged() {
+                if (mConnectedClientsTracker.updateConnectedClients(
+                        mForwardedDownstreams, null /* wifiClients */)) {
+                    reportTetherClientsChanged(mConnectedClientsTracker.getLastTetheredClients());
+                }
+            }
         };
     }
 
diff --git a/packages/Tethering/tests/unit/Android.bp b/packages/Tethering/tests/unit/Android.bp
index 13174c5..ddc095f 100644
--- a/packages/Tethering/tests/unit/Android.bp
+++ b/packages/Tethering/tests/unit/Android.bp
@@ -34,7 +34,13 @@
         "TetheringApiCurrentLib",
         "testables",
     ],
+    // TODO(b/147200698) change sdk_version to module-current and
+    // remove framework-minus-apex, ext, and framework-res
+    sdk_version: "core_platform",
     libs: [
+        "framework-minus-apex",
+        "ext",
+        "framework-res",
         "android.test.runner",
         "android.test.base",
         "android.test.mock",
diff --git a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index f29ad78..948266d 100644
--- a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -29,6 +29,11 @@
 import static android.net.ip.IpServer.STATE_LOCAL_ONLY;
 import static android.net.ip.IpServer.STATE_TETHERED;
 import static android.net.ip.IpServer.STATE_UNAVAILABLE;
+import static android.net.netlink.NetlinkConstants.RTM_DELNEIGH;
+import static android.net.netlink.NetlinkConstants.RTM_NEWNEIGH;
+import static android.net.netlink.StructNdMsg.NUD_FAILED;
+import static android.net.netlink.StructNdMsg.NUD_REACHABLE;
+import static android.net.netlink.StructNdMsg.NUD_STALE;
 import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
 
 import static org.junit.Assert.assertEquals;
@@ -38,9 +43,11 @@
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.never;
@@ -52,6 +59,7 @@
 import static org.mockito.Mockito.when;
 
 import android.net.INetd;
+import android.net.InetAddresses;
 import android.net.InterfaceConfigurationParcel;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
@@ -61,6 +69,8 @@
 import android.net.dhcp.DhcpServingParamsParcel;
 import android.net.dhcp.IDhcpServer;
 import android.net.dhcp.IDhcpServerCallbacks;
+import android.net.ip.IpNeighborMonitor.NeighborEvent;
+import android.net.ip.IpNeighborMonitor.NeighborEventConsumer;
 import android.net.util.InterfaceParams;
 import android.net.util.InterfaceSet;
 import android.net.util.SharedLog;
@@ -81,6 +91,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.net.Inet4Address;
+import java.net.InetAddress;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -88,6 +99,8 @@
     private static final String IFACE_NAME = "testnet1";
     private static final String UPSTREAM_IFACE = "upstream0";
     private static final String UPSTREAM_IFACE2 = "upstream1";
+    private static final int UPSTREAM_IFINDEX = 101;
+    private static final int UPSTREAM_IFINDEX2 = 102;
     private static final String BLUETOOTH_IFACE_ADDR = "192.168.42.1";
     private static final int BLUETOOTH_DHCP_PREFIX_LENGTH = 24;
     private static final int DHCP_LEASE_TIME_SECS = 3600;
@@ -102,6 +115,7 @@
     @Mock private SharedLog mSharedLog;
     @Mock private IDhcpServer mDhcpServer;
     @Mock private RouterAdvertisementDaemon mRaDaemon;
+    @Mock private IpNeighborMonitor mIpNeighborMonitor;
     @Mock private IpServer.Dependencies mDependencies;
 
     @Captor private ArgumentCaptor<DhcpServingParamsParcel> mDhcpParamsCaptor;
@@ -111,6 +125,7 @@
             ArgumentCaptor.forClass(LinkProperties.class);
     private IpServer mIpServer;
     private InterfaceConfigurationParcel mInterfaceConfiguration;
+    private NeighborEventConsumer mNeighborEventConsumer;
 
     private void initStateMachine(int interfaceType) throws Exception {
         initStateMachine(interfaceType, false /* usingLegacyDhcp */);
@@ -130,16 +145,28 @@
         }).when(mDependencies).makeDhcpServer(any(), mDhcpParamsCaptor.capture(), any());
         when(mDependencies.getRouterAdvertisementDaemon(any())).thenReturn(mRaDaemon);
         when(mDependencies.getInterfaceParams(IFACE_NAME)).thenReturn(TEST_IFACE_PARAMS);
+
+        when(mDependencies.getIfindex(eq(UPSTREAM_IFACE))).thenReturn(UPSTREAM_IFINDEX);
+        when(mDependencies.getIfindex(eq(UPSTREAM_IFACE2))).thenReturn(UPSTREAM_IFINDEX2);
+
         mInterfaceConfiguration = new InterfaceConfigurationParcel();
         mInterfaceConfiguration.flags = new String[0];
         if (interfaceType == TETHERING_BLUETOOTH) {
             mInterfaceConfiguration.ipv4Addr = BLUETOOTH_IFACE_ADDR;
             mInterfaceConfiguration.prefixLength = BLUETOOTH_DHCP_PREFIX_LENGTH;
         }
+
+        ArgumentCaptor<NeighborEventConsumer> neighborCaptor =
+                ArgumentCaptor.forClass(NeighborEventConsumer.class);
+        doReturn(mIpNeighborMonitor).when(mDependencies).getIpNeighborMonitor(any(), any(),
+                neighborCaptor.capture());
+
         mIpServer = new IpServer(
                 IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd,
                 mCallback, usingLegacyDhcp, mDependencies);
         mIpServer.start();
+        mNeighborEventConsumer = neighborCaptor.getValue();
+
         // Starting the state machine always puts us in a consistent state and notifies
         // the rest of the world that we've changed from an unknown to available state.
         mLooper.dispatchAll();
@@ -158,7 +185,9 @@
         initStateMachine(interfaceType, usingLegacyDhcp);
         dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
         if (upstreamIface != null) {
-            dispatchTetherConnectionChanged(upstreamIface);
+            LinkProperties lp = new LinkProperties();
+            lp.setInterfaceName(upstreamIface);
+            dispatchTetherConnectionChanged(upstreamIface, lp);
         }
         reset(mNetd, mCallback);
     }
@@ -170,6 +199,8 @@
 
     @Test
     public void startsOutAvailable() {
+        when(mDependencies.getIpNeighborMonitor(any(), any(), any()))
+                .thenReturn(mIpNeighborMonitor);
         mIpServer = new IpServer(IFACE_NAME, mLooper.getLooper(), TETHERING_BLUETOOTH, mSharedLog,
                 mNetd, mCallback, false /* usingLegacyDhcp */, mDependencies);
         mIpServer.start();
@@ -467,9 +498,139 @@
         verify(mDependencies, never()).makeDhcpServer(any(), any(), any());
     }
 
+    private InetAddress addr(String addr) throws Exception {
+        return InetAddresses.parseNumericAddress(addr);
+    }
+
+    private void recvNewNeigh(int ifindex, InetAddress addr, short nudState, MacAddress mac) {
+        mNeighborEventConsumer.accept(new NeighborEvent(0, RTM_NEWNEIGH, ifindex, addr,
+                nudState, mac));
+        mLooper.dispatchAll();
+    }
+
+    private void recvDelNeigh(int ifindex, InetAddress addr, short nudState, MacAddress mac) {
+        mNeighborEventConsumer.accept(new NeighborEvent(0, RTM_DELNEIGH, ifindex, addr,
+                nudState, mac));
+        mLooper.dispatchAll();
+    }
+
+    @Test
+    public void addRemoveipv6ForwardingRules() throws Exception {
+        initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */);
+
+        final int myIfindex = TEST_IFACE_PARAMS.index;
+        final int notMyIfindex = myIfindex - 1;
+
+        final MacAddress myMac = TEST_IFACE_PARAMS.macAddr;
+        final InetAddress neighA = InetAddresses.parseNumericAddress("2001:db8::1");
+        final InetAddress neighB = InetAddresses.parseNumericAddress("2001:db8::2");
+        final InetAddress neighLL = InetAddresses.parseNumericAddress("fe80::1");
+        final InetAddress neighMC = InetAddresses.parseNumericAddress("ff02::1234");
+        final MacAddress macA = MacAddress.fromString("00:00:00:00:00:0a");
+        final MacAddress macB = MacAddress.fromString("11:22:33:00:00:0b");
+
+        reset(mNetd);
+
+        // Events on other interfaces are ignored.
+        recvNewNeigh(notMyIfindex, neighA, NUD_REACHABLE, macA);
+        verifyNoMoreInteractions(mNetd);
+
+        // Events on this interface are received and sent to netd.
+        recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA);
+        verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX),
+                eq(neighA.getAddress()), eq(myMac.toByteArray()), eq(macA.toByteArray()));
+        reset(mNetd);
+
+        recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB);
+        verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX),
+                eq(neighB.getAddress()), eq(myMac.toByteArray()), eq(macB.toByteArray()));
+        reset(mNetd);
+
+        // Link-local and multicast neighbors are ignored.
+        recvNewNeigh(myIfindex, neighLL, NUD_REACHABLE, macA);
+        verifyNoMoreInteractions(mNetd);
+        recvNewNeigh(myIfindex, neighMC, NUD_REACHABLE, macA);
+        verifyNoMoreInteractions(mNetd);
+
+        // A neighbor that is no longer valid causes the rule to be removed.
+        recvNewNeigh(myIfindex, neighA, NUD_FAILED, macA);
+        verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX), eq(neighA.getAddress()));
+        reset(mNetd);
+
+        // A neighbor that is deleted causes the rule to be removed.
+        recvDelNeigh(myIfindex, neighB, NUD_STALE, macB);
+        verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX), eq(neighB.getAddress()));
+        reset(mNetd);
+
+        // Upstream changes result in deleting and re-adding the rules.
+        recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA);
+        recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB);
+        reset(mNetd);
+
+        InOrder inOrder = inOrder(mNetd);
+        LinkProperties lp = new LinkProperties();
+        lp.setInterfaceName(UPSTREAM_IFACE2);
+        dispatchTetherConnectionChanged(UPSTREAM_IFACE2, lp);
+        inOrder.verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX2),
+                eq(neighA.getAddress()), eq(myMac.toByteArray()), eq(macA.toByteArray()));
+        inOrder.verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX),
+                    eq(neighA.getAddress()));
+        inOrder.verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX2),
+                eq(neighB.getAddress()), eq(myMac.toByteArray()), eq(macB.toByteArray()));
+        inOrder.verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX),
+                eq(neighB.getAddress()));
+        reset(mNetd);
+
+        // When the upstream is lost, rules are removed.
+        dispatchTetherConnectionChanged(null, null);
+        verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX2),
+                eq(neighA.getAddress()));
+        verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX2),
+                eq(neighB.getAddress()));
+        reset(mNetd);
+
+        // If the upstream is IPv4-only, no rules are added.
+        dispatchTetherConnectionChanged(UPSTREAM_IFACE);
+        reset(mNetd);
+        recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA);
+        verifyNoMoreInteractions(mNetd);
+
+        // Rules can be added again once upstream IPv6 connectivity is available.
+        lp.setInterfaceName(UPSTREAM_IFACE);
+        dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp);
+        recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB);
+        verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX),
+                eq(neighB.getAddress()), eq(myMac.toByteArray()), eq(macB.toByteArray()));
+        verify(mNetd, never()).tetherRuleAddDownstreamIpv6(anyInt(), anyInt(),
+                eq(neighA.getAddress()), any(), any());
+
+        // If upstream IPv6 connectivity is lost, rules are removed.
+        reset(mNetd);
+        dispatchTetherConnectionChanged(UPSTREAM_IFACE, null);
+        verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX), eq(neighB.getAddress()));
+
+        // When the interface goes down, rules are removed.
+        lp.setInterfaceName(UPSTREAM_IFACE);
+        dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp);
+        recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA);
+        recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB);
+        verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX),
+                eq(neighA.getAddress()), eq(myMac.toByteArray()), eq(macA.toByteArray()));
+        verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX),
+                eq(neighB.getAddress()), eq(myMac.toByteArray()), eq(macB.toByteArray()));
+        reset(mNetd);
+
+        mIpServer.stop();
+        mLooper.dispatchAll();
+        verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX), eq(neighA.getAddress()));
+        verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX), eq(neighB.getAddress()));
+        reset(mNetd);
+    }
+
     private void assertDhcpStarted(IpPrefix expectedPrefix) throws Exception {
         verify(mDependencies, times(1)).makeDhcpServer(eq(IFACE_NAME), any(), any());
-        verify(mDhcpServer, timeout(MAKE_DHCPSERVER_TIMEOUT_MS).times(1)).start(any());
+        verify(mDhcpServer, timeout(MAKE_DHCPSERVER_TIMEOUT_MS).times(1)).startWithCallbacks(
+                any(), any());
         final DhcpServingParamsParcel params = mDhcpParamsCaptor.getValue();
         // Last address byte is random
         assertTrue(expectedPrefix.contains(intToInet4AddressHTH(params.serverAddr)));
@@ -507,10 +668,17 @@
      *
      * @see #dispatchCommand(int)
      * @param upstreamIface String name of upstream interface (or null)
+     * @param v6lp IPv6 LinkProperties of the upstream interface, or null for an IPv4-only upstream.
      */
+    private void dispatchTetherConnectionChanged(String upstreamIface, LinkProperties v6lp) {
+        dispatchTetherConnectionChanged(upstreamIface);
+        mIpServer.sendMessage(IpServer.CMD_IPV6_TETHER_UPDATE, v6lp);
+        mLooper.dispatchAll();
+    }
+
     private void dispatchTetherConnectionChanged(String upstreamIface) {
-        mIpServer.sendMessage(IpServer.CMD_TETHER_CONNECTION_CHANGED,
-                new InterfaceSet(upstreamIface));
+        final InterfaceSet ifs = (upstreamIface != null) ? new InterfaceSet(upstreamIface) : null;
+        mIpServer.sendMessage(IpServer.CMD_TETHER_CONNECTION_CHANGED, ifs);
         mLooper.dispatchAll();
     }
 
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/ConnectedClientsTrackerTest.kt b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/ConnectedClientsTrackerTest.kt
new file mode 100644
index 0000000..56f3e21
--- /dev/null
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/ConnectedClientsTrackerTest.kt
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2020 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.net.LinkAddress
+import android.net.MacAddress
+import android.net.TetheredClient
+import android.net.TetheredClient.AddressInfo
+import android.net.TetheringManager.TETHERING_USB
+import android.net.TetheringManager.TETHERING_WIFI
+import android.net.ip.IpServer
+import android.net.wifi.WifiClient
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class ConnectedClientsTrackerTest {
+
+    private val server1 = mock(IpServer::class.java)
+    private val server2 = mock(IpServer::class.java)
+    private val servers = listOf(server1, server2)
+
+    private val clock = TestClock(1324L)
+
+    private val client1Addr = MacAddress.fromString("01:23:45:67:89:0A")
+    private val client1 = TetheredClient(client1Addr, listOf(
+            AddressInfo(LinkAddress("192.168.43.44/32"), null /* hostname */, clock.time + 20)),
+            TETHERING_WIFI)
+    private val wifiClient1 = makeWifiClient(client1Addr)
+    private val client2Addr = MacAddress.fromString("02:34:56:78:90:AB")
+    private val client2Exp30AddrInfo = AddressInfo(
+            LinkAddress("192.168.43.45/32"), "my_hostname", clock.time + 30)
+    private val client2 = TetheredClient(client2Addr, listOf(
+            client2Exp30AddrInfo,
+            AddressInfo(LinkAddress("2001:db8:12::34/72"), "other_hostname", clock.time + 10)),
+            TETHERING_WIFI)
+    private val wifiClient2 = makeWifiClient(client2Addr)
+    private val client3Addr = MacAddress.fromString("03:45:67:89:0A:BC")
+    private val client3 = TetheredClient(client3Addr,
+            listOf(AddressInfo(LinkAddress("2001:db8:34::34/72"), "other_other_hostname",
+                    clock.time + 10)),
+            TETHERING_USB)
+
+    @Test
+    fun testUpdateConnectedClients() {
+        doReturn(emptyList<TetheredClient>()).`when`(server1).allLeases
+        doReturn(emptyList<TetheredClient>()).`when`(server2).allLeases
+
+        val tracker = ConnectedClientsTracker(clock)
+        assertFalse(tracker.updateConnectedClients(servers, null))
+
+        // Obtain a lease for client 1
+        doReturn(listOf(client1)).`when`(server1).allLeases
+        assertSameClients(listOf(client1), assertNewClients(tracker, servers, listOf(wifiClient1)))
+
+        // Client 2 L2-connected, no lease yet
+        val client2WithoutAddr = TetheredClient(client2Addr, emptyList(), TETHERING_WIFI)
+        assertSameClients(listOf(client1, client2WithoutAddr),
+                assertNewClients(tracker, servers, listOf(wifiClient1, wifiClient2)))
+
+        // Client 2 lease obtained
+        doReturn(listOf(client1, client2)).`when`(server1).allLeases
+        assertSameClients(listOf(client1, client2), assertNewClients(tracker, servers, null))
+
+        // Client 3 lease obtained
+        doReturn(listOf(client3)).`when`(server2).allLeases
+        assertSameClients(listOf(client1, client2, client3),
+                assertNewClients(tracker, servers, null))
+
+        // Client 2 L2-disconnected
+        assertSameClients(listOf(client1, client3),
+                assertNewClients(tracker, servers, listOf(wifiClient1)))
+
+        // Client 1 L2-disconnected
+        assertSameClients(listOf(client3), assertNewClients(tracker, servers, emptyList()))
+
+        // Client 1 comes back
+        assertSameClients(listOf(client1, client3),
+                assertNewClients(tracker, servers, listOf(wifiClient1)))
+
+        // Leases lost, client 1 still L2-connected
+        doReturn(emptyList<TetheredClient>()).`when`(server1).allLeases
+        doReturn(emptyList<TetheredClient>()).`when`(server2).allLeases
+        assertSameClients(listOf(TetheredClient(client1Addr, emptyList(), TETHERING_WIFI)),
+                assertNewClients(tracker, servers, null))
+    }
+
+    @Test
+    fun testUpdateConnectedClients_LeaseExpiration() {
+        val tracker = ConnectedClientsTracker(clock)
+        doReturn(listOf(client1, client2)).`when`(server1).allLeases
+        doReturn(listOf(client3)).`when`(server2).allLeases
+        assertSameClients(listOf(client1, client2, client3), assertNewClients(
+                tracker, servers, listOf(wifiClient1, wifiClient2)))
+
+        clock.time += 20
+        // Client 3 has no remaining lease: removed
+        val expectedClients = listOf(
+                // Client 1 has no remaining lease but is L2-connected
+                TetheredClient(client1Addr, emptyList(), TETHERING_WIFI),
+                // Client 2 has some expired leases
+                TetheredClient(
+                        client2Addr,
+                        // Only the "t + 30" address is left, the "t + 10" address expired
+                        listOf(client2Exp30AddrInfo),
+                        TETHERING_WIFI))
+        assertSameClients(expectedClients, assertNewClients(tracker, servers, null))
+    }
+
+    private fun assertNewClients(
+        tracker: ConnectedClientsTracker,
+        ipServers: Iterable<IpServer>,
+        wifiClients: List<WifiClient>?
+    ): List<TetheredClient> {
+        assertTrue(tracker.updateConnectedClients(ipServers, wifiClients))
+        return tracker.lastTetheredClients
+    }
+
+    private fun assertSameClients(expected: List<TetheredClient>, actual: List<TetheredClient>) {
+        val expectedSet = HashSet(expected)
+        assertEquals(expected.size, expectedSet.size)
+        assertEquals(expectedSet, HashSet(actual))
+    }
+
+    private fun makeWifiClient(macAddr: MacAddress): WifiClient {
+        // Use a mock WifiClient as the constructor is not part of the WiFi module exported API.
+        return mock(WifiClient::class.java).apply { doReturn(macAddr).`when`(this).macAddress }
+    }
+
+    private class TestClock(var time: Long) : ConnectedClientsTracker.Clock() {
+        override fun elapsedRealtime(): Long {
+            return time
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
index a917849..d14c62a 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
@@ -88,12 +88,14 @@
 import android.net.NetworkRequest;
 import android.net.RouteInfo;
 import android.net.TetherStatesParcel;
+import android.net.TetheredClient;
 import android.net.TetheringCallbackStartedParcel;
 import android.net.TetheringConfigurationParcel;
 import android.net.TetheringRequestParcel;
 import android.net.dhcp.DhcpServerCallbacks;
 import android.net.dhcp.DhcpServingParamsParcel;
 import android.net.dhcp.IDhcpServer;
+import android.net.ip.IpNeighborMonitor;
 import android.net.ip.IpServer;
 import android.net.ip.RouterAdvertisementDaemon;
 import android.net.util.InterfaceParams;
@@ -142,6 +144,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.List;
 import java.util.Vector;
 
 @RunWith(AndroidJUnit4.class)
@@ -171,6 +174,7 @@
     @Mock private UpstreamNetworkMonitor mUpstreamNetworkMonitor;
     @Mock private IPv6TetheringCoordinator mIPv6TetheringCoordinator;
     @Mock private RouterAdvertisementDaemon mRouterAdvertisementDaemon;
+    @Mock private IpNeighborMonitor mIpNeighborMonitor;
     @Mock private IDhcpServer mDhcpServer;
     @Mock private INetd mNetd;
     @Mock private UserManager mUserManager;
@@ -276,6 +280,11 @@
                 }
             }).run();
         }
+
+        public IpNeighborMonitor getIpNeighborMonitor(Handler h, SharedLog l,
+                IpNeighborMonitor.NeighborEventConsumer c) {
+            return mIpNeighborMonitor;
+        }
     }
 
     private class MockTetheringConfiguration extends TetheringConfiguration {
@@ -470,6 +479,7 @@
                 ArgumentCaptor.forClass(PhoneStateListener.class);
         verify(mTelephonyManager).listen(phoneListenerCaptor.capture(),
                 eq(PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE));
+        verify(mWifiManager).registerSoftApCallback(any(), any());
         mPhoneStateListener = phoneListenerCaptor.getValue();
     }
 
@@ -728,7 +738,8 @@
 
         sendIPv6TetherUpdates(upstreamState);
         verify(mRouterAdvertisementDaemon, never()).buildNewRa(any(), notNull());
-        verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
+        verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
+                any(), any());
     }
 
     @Test
@@ -764,7 +775,8 @@
         verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
         verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
         verify(mRouterAdvertisementDaemon, times(1)).start();
-        verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
+        verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
+                any(), any());
 
         sendIPv6TetherUpdates(upstreamState);
         verify(mRouterAdvertisementDaemon, times(1)).buildNewRa(any(), notNull());
@@ -778,7 +790,8 @@
 
         verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME);
         verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
-        verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
+        verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
+                any(), any());
         verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME);
         verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
 
@@ -794,7 +807,8 @@
         runUsbTethering(upstreamState);
 
         verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
-        verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
+        verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
+                any(), any());
         verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
 
         // Then 464xlat comes up
@@ -817,7 +831,8 @@
         verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
         verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
         // DHCP not restarted on downstream (still times(1))
-        verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
+        verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
+                any(), any());
     }
 
     @Test
@@ -847,7 +862,8 @@
     public void workingNcmTethering() throws Exception {
         runNcmTethering();
 
-        verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
+        verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
+                any(), any());
     }
 
     @Test
@@ -1171,6 +1187,11 @@
         }
 
         @Override
+        public void onTetherClientsChanged(List<TetheredClient> clients) {
+            // TODO: check this
+        }
+
+        @Override
         public void onCallbackStarted(TetheringCallbackStartedParcel parcel) {
             mActualUpstreams.add(parcel.upstreamNetwork);
             mTetheringConfigs.add(parcel.config);
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index ef071a4..2c7ea310 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -230,6 +230,10 @@
     // Package: android
     NOTE_TEST_HARNESS_MODE_ENABLED = 54;
 
+    // Display the Android Debug Protocol status
+    // Package: android
+    NOTE_ADB_WIFI_ACTIVE = 62;
+
     // ADD_NEW_IDS_ABOVE_THIS_LINE
     // Legacy IDs with arbitrary values appear below
     // Legacy IDs existed as stable non-conflicting constants prior to the O release
diff --git a/services/Android.bp b/services/Android.bp
index 073fccc..5019bb1 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -72,7 +72,7 @@
 
     libs: [
         "android.hidl.manager-V1.0-java",
-        "framework-tethering"
+        "framework-tethering-stubs",
     ],
 
     plugins: [
@@ -110,22 +110,30 @@
     name: "services-stubs.sources",
     srcs: [":services-sources"],
     installable: false,
-    // TODO: remove the --hide options below
     args: " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.SYSTEM_SERVER\\)" +
         " --hide-annotation android.annotation.Hide" +
+        " --hide InternalClasses" + // com.android.* classes are okay in this interface
+        // TODO: remove the --hide options below
         " --hide-package com.google.android.startop.iorap" +
         " --hide ReferencesHidden" +
         " --hide DeprecationMismatch" +
         " --hide HiddenTypedefConstant",
-    libs: [
-        "framework-all",
-    ],
     visibility: ["//visibility:private"],
     check_api: {
         current: {
             api_file: "api/current.txt",
             removed_api_file: "api/removed.txt",
         },
+        last_released: {
+            api_file: ":last-released-system-server-api",
+            removed_api_file: "api/removed.txt",
+            baseline_file: ":system-server-api-incompatibilities-with-last-released"
+        },
+        api_lint: {
+            enabled: true,
+            new_since: ":last-released-system-server-api",
+            baseline_file: "api/lint-baseline.txt",
+        },
     },
 }
 
diff --git a/services/accessibility/OWNERS b/services/accessibility/OWNERS
index 265674a..c6f42f7 100644
--- a/services/accessibility/OWNERS
+++ b/services/accessibility/OWNERS
@@ -1,3 +1,4 @@
 svetoslavganov@google.com
 pweaver@google.com
 rhedjao@google.com
+qasid@google.com
diff --git a/services/api/lint-baseline.txt b/services/api/lint-baseline.txt
new file mode 100644
index 0000000..9a97707
--- /dev/null
+++ b/services/api/lint-baseline.txt
@@ -0,0 +1 @@
+// Baseline format: 1.0
diff --git a/services/core/Android.bp b/services/core/Android.bp
index c32065d..a09063b 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -29,7 +29,7 @@
         "android.hardware.tv.cec-V1.0-java",
         "android.hardware.vibrator-java",
         "app-compat-annotations",
-        "framework-tethering",
+        "framework-tethering-stubs",
     ],
 
     required: [
@@ -54,6 +54,7 @@
         "android.hidl.manager-V1.2-java",
         "dnsresolver_aidl_interface-V2-java",
         "netd_event_listener_interface-java",
+        "ike-stubs",
     ],
 }
 
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 5f032fc..34b7be4 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -1536,7 +1536,8 @@
     }
 
     @Override
-    public NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser(int userId) {
+    public NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser(
+                int userId, String callingPackageName) {
         // The basic principle is: if an app's traffic could possibly go over a
         // network, without the app doing anything multinetwork-specific,
         // (hence, by "default"), then include that network's capabilities in
@@ -1558,7 +1559,10 @@
         NetworkAgentInfo nai = getDefaultNetwork();
         NetworkCapabilities nc = getNetworkCapabilitiesInternal(nai);
         if (nc != null) {
-            result.put(nai.network, nc);
+            result.put(
+                    nai.network,
+                    maybeSanitizeLocationInfoForCaller(
+                            nc, Binder.getCallingUid(), callingPackageName));
         }
 
         synchronized (mVpns) {
@@ -1568,10 +1572,12 @@
                     Network[] networks = vpn.getUnderlyingNetworks();
                     if (networks != null) {
                         for (Network network : networks) {
-                            nai = getNetworkAgentInfoForNetwork(network);
-                            nc = getNetworkCapabilitiesInternal(nai);
+                            nc = getNetworkCapabilitiesInternal(network);
                             if (nc != null) {
-                                result.put(network, nc);
+                                result.put(
+                                        network,
+                                        maybeSanitizeLocationInfoForCaller(
+                                                nc, Binder.getCallingUid(), callingPackageName));
                             }
                         }
                     }
@@ -1638,20 +1644,26 @@
         }
     }
 
+    private NetworkCapabilities getNetworkCapabilitiesInternal(Network network) {
+        return getNetworkCapabilitiesInternal(getNetworkAgentInfoForNetwork(network));
+    }
+
     private NetworkCapabilities getNetworkCapabilitiesInternal(NetworkAgentInfo nai) {
         if (nai == null) return null;
         synchronized (nai) {
             if (nai.networkCapabilities == null) return null;
             return networkCapabilitiesRestrictedForCallerPermissions(
-                    nai.networkCapabilities,
-                    Binder.getCallingPid(), Binder.getCallingUid());
+                    nai.networkCapabilities, Binder.getCallingPid(), Binder.getCallingUid());
         }
     }
 
     @Override
-    public NetworkCapabilities getNetworkCapabilities(Network network) {
+    public NetworkCapabilities getNetworkCapabilities(Network network, String callingPackageName) {
+        mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackageName);
         enforceAccessPermission();
-        return getNetworkCapabilitiesInternal(getNetworkAgentInfoForNetwork(network));
+        return maybeSanitizeLocationInfoForCaller(
+                getNetworkCapabilitiesInternal(network),
+                Binder.getCallingUid(), callingPackageName);
     }
 
     @VisibleForTesting
@@ -1667,20 +1679,34 @@
         }
         newNc.setAdministratorUids(Collections.EMPTY_LIST);
 
-        maybeSanitizeLocationInfoForCaller(newNc, callerUid);
-
         return newNc;
     }
 
-    private void maybeSanitizeLocationInfoForCaller(
-            NetworkCapabilities nc, int callerUid) {
-        // TODO(b/142072839): Conditionally reset the owner UID if the following
-        // conditions are not met:
-        // 1. The destination app is the network owner
-        // 2. The destination app has the ACCESS_COARSE_LOCATION permission granted
-        // if target SDK<29 or otherwise has the ACCESS_FINE_LOCATION permission granted
-        // 3. The user's location toggle is on
-        nc.setOwnerUid(INVALID_UID);
+    @VisibleForTesting
+    @Nullable
+    NetworkCapabilities maybeSanitizeLocationInfoForCaller(
+            @Nullable NetworkCapabilities nc, int callerUid, @NonNull String callerPkgName) {
+        if (nc == null) {
+            return null;
+        }
+        final NetworkCapabilities newNc = new NetworkCapabilities(nc);
+        if (callerUid != newNc.getOwnerUid()) {
+            newNc.setOwnerUid(INVALID_UID);
+            return newNc;
+        }
+
+        Binder.withCleanCallingIdentity(
+                () -> {
+                    if (!mLocationPermissionChecker.checkLocationPermission(
+                            callerPkgName, null /* featureId */, callerUid, null /* message */)) {
+                        // Caller does not have the requisite location permissions. Reset the
+                        // owner's UID in the NetworkCapabilities.
+                        newNc.setOwnerUid(INVALID_UID);
+                    }
+                }
+        );
+
+        return newNc;
     }
 
     private LinkProperties linkPropertiesRestrictedForCallerPermissions(
@@ -1755,7 +1781,7 @@
     public boolean isActiveNetworkMetered() {
         enforceAccessPermission();
 
-        final NetworkCapabilities caps = getNetworkCapabilities(getActiveNetwork());
+        final NetworkCapabilities caps = getNetworkCapabilitiesInternal(getActiveNetwork());
         if (caps != null) {
             return !caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
         } else {
@@ -3269,7 +3295,6 @@
         for (int i = 0; i < nai.numNetworkRequests(); i++) {
             NetworkRequest request = nai.requestAt(i);
             final NetworkRequestInfo nri = mNetworkRequests.get(request);
-            ensureRunningOnConnectivityServiceThread();
             final NetworkAgentInfo currentNetwork = nri.mSatisfier;
             if (currentNetwork != null && currentNetwork.network.netId == nai.network.netId) {
                 nri.mSatisfier = null;
@@ -3421,7 +3446,6 @@
 
             // If this Network is already the highest scoring Network for a request, or if
             // there is hope for it to become one if it validated, then it is needed.
-            ensureRunningOnConnectivityServiceThread();
             if (nri.request.isRequest() && nai.satisfies(nri.request) &&
                     (nai.isSatisfyingRequest(nri.request.requestId) ||
                     // Note that this catches two important cases:
@@ -3460,7 +3484,6 @@
         if (mNetworkRequests.get(nri.request) == null) {
             return;
         }
-        ensureRunningOnConnectivityServiceThread();
         if (nri.mSatisfier != null) {
             return;
         }
@@ -3498,7 +3521,6 @@
         mNetworkRequestInfoLogs.log("RELEASE " + nri);
         if (nri.request.isRequest()) {
             boolean wasKept = false;
-            ensureRunningOnConnectivityServiceThread();
             final NetworkAgentInfo nai = nri.mSatisfier;
             if (nai != null) {
                 boolean wasBackgroundNetwork = nai.isBackgroundNetwork();
@@ -4783,7 +4805,7 @@
                 return false;
             }
 
-            return vpn.startAlwaysOnVpn();
+            return vpn.startAlwaysOnVpn(mKeyStore);
         }
     }
 
@@ -4798,7 +4820,7 @@
                 Slog.w(TAG, "User " + userId + " has no Vpn configuration");
                 return false;
             }
-            return vpn.isAlwaysOnPackageSupported(packageName);
+            return vpn.isAlwaysOnPackageSupported(packageName, mKeyStore);
         }
     }
 
@@ -4819,11 +4841,11 @@
                 Slog.w(TAG, "User " + userId + " has no Vpn configuration");
                 return false;
             }
-            if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownWhitelist)) {
+            if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownWhitelist, mKeyStore)) {
                 return false;
             }
             if (!startAlwaysOnVpn(userId)) {
-                vpn.setAlwaysOnPackage(null, false, null);
+                vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
                 return false;
             }
         }
@@ -5009,7 +5031,7 @@
                 loge("Starting user already has a VPN");
                 return;
             }
-            userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, userId);
+            userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, userId, mKeyStore);
             mVpns.put(userId, userVpn);
             if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) {
                 updateLockdownVpn();
@@ -5080,7 +5102,7 @@
             if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName)) {
                 Slog.d(TAG, "Restarting always-on VPN package " + packageName + " for user "
                         + userId);
-                vpn.startAlwaysOnVpn();
+                vpn.startAlwaysOnVpn(mKeyStore);
             }
         }
     }
@@ -5102,7 +5124,7 @@
             if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) {
                 Slog.d(TAG, "Removing always-on VPN package " + packageName + " for user "
                         + userId);
-                vpn.setAlwaysOnPackage(null, false, null);
+                vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
             }
         }
     }
@@ -5322,8 +5344,8 @@
         }
 
         public String toString() {
-            return "uid/pid:" + mUid + "/" + mPid + " " + request +
-                    (mPendingIntent == null ? "" : " to trigger " + mPendingIntent);
+            return "uid/pid:" + mUid + "/" + mPid + " " + request
+                    + (mPendingIntent == null ? "" : " to trigger " + mPendingIntent);
         }
     }
 
@@ -6416,8 +6438,13 @@
         }
         switch (notificationType) {
             case ConnectivityManager.CALLBACK_AVAILABLE: {
-                putParcelable(bundle, networkCapabilitiesRestrictedForCallerPermissions(
-                        networkAgent.networkCapabilities, nri.mPid, nri.mUid));
+                final NetworkCapabilities nc =
+                        networkCapabilitiesRestrictedForCallerPermissions(
+                                networkAgent.networkCapabilities, nri.mPid, nri.mUid);
+                putParcelable(
+                        bundle,
+                        maybeSanitizeLocationInfoForCaller(
+                                nc, nri.mUid, nri.request.getRequestorPackageName()));
                 putParcelable(bundle, linkPropertiesRestrictedForCallerPermissions(
                         networkAgent.linkProperties, nri.mPid, nri.mUid));
                 // For this notification, arg1 contains the blocked status.
@@ -6430,9 +6457,13 @@
             }
             case ConnectivityManager.CALLBACK_CAP_CHANGED: {
                 // networkAgent can't be null as it has been accessed a few lines above.
-                final NetworkCapabilities nc = networkCapabilitiesRestrictedForCallerPermissions(
-                        networkAgent.networkCapabilities, nri.mPid, nri.mUid);
-                putParcelable(bundle, nc);
+                final NetworkCapabilities netCap =
+                        networkCapabilitiesRestrictedForCallerPermissions(
+                                networkAgent.networkCapabilities, nri.mPid, nri.mUid);
+                putParcelable(
+                        bundle,
+                        maybeSanitizeLocationInfoForCaller(
+                                netCap, nri.mUid, nri.request.getRequestorPackageName()));
                 break;
             }
             case ConnectivityManager.CALLBACK_IP_CHANGED: {
diff --git a/services/core/java/com/android/server/UserspaceRebootLogger.java b/services/core/java/com/android/server/UserspaceRebootLogger.java
new file mode 100644
index 0000000..74f113f
--- /dev/null
+++ b/services/core/java/com/android/server/UserspaceRebootLogger.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_SHUTDOWN_SEQUENCE_ABORTED;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERDATA_REMOUNT;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERSPACE_REBOOT_WATCHDOG_TRIGGERED;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__OUTCOME_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__SUCCESS;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__LOCKED;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__UNLOCKED;
+
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.Slog;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Utility class to help abstract logging {@code UserspaceRebootReported} atom.
+ */
+public final class UserspaceRebootLogger {
+
+    private static final String TAG = "UserspaceRebootLogger";
+
+    private static final String USERSPACE_REBOOT_SHOULD_LOG_PROPERTY =
+            "persist.sys.userspace_reboot.log.should_log";
+    private static final String USERSPACE_REBOOT_LAST_STARTED_PROPERTY =
+            "sys.userspace_reboot.log.last_started";
+    private static final String USERSPACE_REBOOT_LAST_FINISHED_PROPERTY =
+            "sys.userspace_reboot.log.last_finished";
+    private static final String BOOT_REASON_PROPERTY = "sys.boot.reason";
+
+    private UserspaceRebootLogger() {}
+
+    /**
+     * Modifies internal state to note that {@code UserspaceRebootReported} atom needs to be
+     * logged on the next successful boot.
+     */
+    public static void noteUserspaceRebootWasRequested() {
+        SystemProperties.set(USERSPACE_REBOOT_SHOULD_LOG_PROPERTY, "1");
+        SystemProperties.set(USERSPACE_REBOOT_LAST_STARTED_PROPERTY,
+                String.valueOf(SystemClock.elapsedRealtime()));
+    }
+
+    /**
+     * Updates internal state on boot after successful userspace reboot.
+     *
+     * <p>Should be called right before framework sets {@code sys.boot_completed} property.
+     */
+    public static void noteUserspaceRebootSuccess() {
+        SystemProperties.set(USERSPACE_REBOOT_LAST_FINISHED_PROPERTY,
+                String.valueOf(SystemClock.elapsedRealtime()));
+    }
+
+    /**
+     * Returns {@code true} if {@code UserspaceRebootReported} atom should be logged.
+     */
+    public static boolean shouldLogUserspaceRebootEvent() {
+        return SystemProperties.getBoolean(USERSPACE_REBOOT_SHOULD_LOG_PROPERTY, false);
+    }
+
+    /**
+     * Asynchronously logs {@code UserspaceRebootReported} on the given {@code executor}.
+     *
+     * <p>Should be called in the end of {@link
+     * com.android.server.am.ActivityManagerService#finishBooting()} method, after framework have
+     * tried to proactivelly unlock storage of the primary user.
+     */
+    public static void logEventAsync(boolean userUnlocked, Executor executor) {
+        final int outcome = computeOutcome();
+        final long durationMillis;
+        if (outcome == USERSPACE_REBOOT_REPORTED__OUTCOME__SUCCESS) {
+            durationMillis = SystemProperties.getLong(USERSPACE_REBOOT_LAST_FINISHED_PROPERTY, 0)
+                    - SystemProperties.getLong(USERSPACE_REBOOT_LAST_STARTED_PROPERTY, 0);
+        } else {
+            durationMillis = 0;
+        }
+        final int encryptionState =
+                userUnlocked
+                    ? USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__UNLOCKED
+                    : USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__LOCKED;
+        executor.execute(
+                () -> {
+                    Slog.i(TAG, "Logging UserspaceRebootReported atom: { outcome: " + outcome
+                            + " durationMillis: " + durationMillis + " encryptionState: "
+                            + encryptionState + " }");
+                    FrameworkStatsLog.write(FrameworkStatsLog.USERSPACE_REBOOT_REPORTED, outcome,
+                            durationMillis, encryptionState);
+                    SystemProperties.set(USERSPACE_REBOOT_SHOULD_LOG_PROPERTY, "");
+                });
+    }
+
+    private static int computeOutcome() {
+        if (SystemProperties.getLong(USERSPACE_REBOOT_LAST_STARTED_PROPERTY, -1) != -1) {
+            return USERSPACE_REBOOT_REPORTED__OUTCOME__SUCCESS;
+        }
+        String reason = SystemProperties.get(BOOT_REASON_PROPERTY, "");
+        if (reason.startsWith("reboot,")) {
+            reason = reason.substring("reboot".length());
+        }
+        switch (reason) {
+            case "userspace_failed,watchdog_fork":
+                // Since fork happens before shutdown sequence, attribute it to
+                // USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_SHUTDOWN_SEQUENCE_ABORTED.
+            case "userspace_failed,shutdown_aborted":
+                return USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_SHUTDOWN_SEQUENCE_ABORTED;
+            case "userspace_failed,init_user0_failed":
+                // init_user0 will fail if userdata wasn't remounted correctly, attribute to
+                // USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERDATA_REMOUNT.
+            case "mount_userdata_failed":
+                return USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERDATA_REMOUNT;
+            case "userspace_failed,watchdog_triggered":
+                return
+                    USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERSPACE_REBOOT_WATCHDOG_TRIGGERED;
+            default:
+                return USERSPACE_REBOOT_REPORTED__OUTCOME__OUTCOME_UNKNOWN;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
index da93998..10a2b91 100644
--- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java
+++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
@@ -20,19 +20,37 @@
 
 import android.annotation.TestApi;
 import android.app.ActivityManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
 import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.database.ContentObserver;
+import android.debug.AdbManager;
 import android.debug.AdbProtoEnums;
 import android.debug.AdbTransportType;
+import android.debug.PairDevice;
+import android.net.ConnectivityManager;
 import android.net.LocalSocket;
 import android.net.LocalSocketAddress;
+import android.net.NetworkInfo;
 import android.net.Uri;
+import android.net.nsd.NsdManager;
+import android.net.nsd.NsdServiceInfo;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiSsid;
+import android.os.Bundle;
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.Handler;
@@ -52,6 +70,8 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.XmlUtils;
 import com.android.internal.util.dump.DualDumpOutputStream;
@@ -71,6 +91,8 @@
 import java.io.OutputStream;
 import java.nio.charset.StandardCharsets;
 import java.security.MessageDigest;
+import java.security.SecureRandom;
+import java.util.AbstractMap;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -79,6 +101,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * Provides communication to the Android Debug Bridge daemon to allow, deny, or clear public keysi
@@ -87,6 +110,7 @@
 public class AdbDebuggingManager {
     private static final String TAG = "AdbDebuggingManager";
     private static final boolean DEBUG = false;
+    private static final boolean MDNS_DEBUG = false;
 
     private static final String ADBD_SOCKET = "adbd";
     private static final String ADB_DIRECTORY = "misc/adb";
@@ -98,19 +122,39 @@
     private static final int BUFFER_SIZE = 65536;
 
     private final Context mContext;
+    private final ContentResolver mContentResolver;
     private final Handler mHandler;
     private AdbDebuggingThread mThread;
-    private boolean mAdbEnabled = false;
+    private boolean mAdbUsbEnabled = false;
+    private boolean mAdbWifiEnabled = false;
     private String mFingerprints;
-    private final List<String> mConnectedKeys;
+    // A key can be used more than once (e.g. USB, wifi), so need to keep a refcount
+    private final Map<String, Integer> mConnectedKeys;
     private String mConfirmComponent;
     private final File mTestUserKeyFile;
 
+    private static final String WIFI_PERSISTENT_CONFIG_PROPERTY =
+            "persist.adb.tls_server.enable";
+    private static final String WIFI_PERSISTENT_GUID =
+            "persist.adb.wifi.guid";
+    private static final int PAIRING_CODE_LENGTH = 6;
+    private PairingThread mPairingThread = null;
+    // A list of keys connected via wifi
+    private final Set<String> mWifiConnectedKeys;
+    // The current info of the adbwifi connection.
+    private AdbConnectionInfo mAdbConnectionInfo;
+    // Polls for a tls port property when adb wifi is enabled
+    private AdbConnectionPortPoller mConnectionPortPoller;
+    private final PortListenerImpl mPortListener = new PortListenerImpl();
+
     public AdbDebuggingManager(Context context) {
         mHandler = new AdbDebuggingHandler(FgThread.get().getLooper());
         mContext = context;
+        mContentResolver = mContext.getContentResolver();
         mTestUserKeyFile = null;
-        mConnectedKeys = new ArrayList<>(1);
+        mConnectedKeys = new HashMap<String, Integer>();
+        mWifiConnectedKeys = new HashSet<String>();
+        mAdbConnectionInfo = new AdbConnectionInfo();
     }
 
     /**
@@ -121,9 +165,178 @@
     protected AdbDebuggingManager(Context context, String confirmComponent, File testUserKeyFile) {
         mHandler = new AdbDebuggingHandler(FgThread.get().getLooper());
         mContext = context;
+        mContentResolver = mContext.getContentResolver();
         mConfirmComponent = confirmComponent;
         mTestUserKeyFile = testUserKeyFile;
-        mConnectedKeys = new ArrayList<>();
+        mConnectedKeys = new HashMap<String, Integer>();
+        mWifiConnectedKeys = new HashSet<String>();
+        mAdbConnectionInfo = new AdbConnectionInfo();
+    }
+
+    class PairingThread extends Thread implements NsdManager.RegistrationListener {
+        private NsdManager mNsdManager;
+        private String mPublicKey;
+        private String mPairingCode;
+        private String mGuid;
+        private String mServiceName;
+        private final String mServiceType = "_adb_secure_pairing._tcp.";
+        private int mPort;
+
+        private native int native_pairing_start(String guid, String password);
+        private native void native_pairing_cancel();
+        private native boolean native_pairing_wait();
+
+        PairingThread(String pairingCode, String serviceName) {
+            super(TAG);
+            mPairingCode = pairingCode;
+            mGuid = SystemProperties.get(WIFI_PERSISTENT_GUID);
+            mServiceName = serviceName;
+            if (serviceName == null || serviceName.isEmpty()) {
+                mServiceName = mGuid;
+            }
+            mPort = -1;
+            mNsdManager = (NsdManager) mContext.getSystemService(Context.NSD_SERVICE);
+        }
+
+        @Override
+        public void run() {
+            if (mGuid.isEmpty()) {
+                Slog.e(TAG, "adbwifi guid was not set");
+                return;
+            }
+            mPort = native_pairing_start(mGuid, mPairingCode);
+            if (mPort <= 0 || mPort > 65535) {
+                Slog.e(TAG, "Unable to start pairing server");
+                return;
+            }
+
+            // Register the mdns service
+            NsdServiceInfo serviceInfo = new NsdServiceInfo();
+            serviceInfo.setServiceName(mServiceName);
+            serviceInfo.setServiceType(mServiceType);
+            serviceInfo.setPort(mPort);
+            mNsdManager.registerService(serviceInfo, NsdManager.PROTOCOL_DNS_SD, this);
+
+            // Send pairing port to UI
+            Message msg = mHandler.obtainMessage(
+                    AdbDebuggingHandler.MSG_RESPONSE_PAIRING_PORT);
+            msg.obj = mPort;
+            mHandler.sendMessage(msg);
+
+            boolean paired = native_pairing_wait();
+            if (DEBUG) {
+                if (mPublicKey != null) {
+                    Slog.i(TAG, "Pairing succeeded key=" + mPublicKey);
+                } else {
+                    Slog.i(TAG, "Pairing failed");
+                }
+            }
+
+            mNsdManager.unregisterService(this);
+
+            Bundle bundle = new Bundle();
+            bundle.putString("publicKey", paired ? mPublicKey : null);
+            Message message = Message.obtain(mHandler,
+                                             AdbDebuggingHandler.MSG_RESPONSE_PAIRING_RESULT,
+                                             bundle);
+            mHandler.sendMessage(message);
+        }
+
+        public void cancelPairing() {
+            native_pairing_cancel();
+        }
+
+        @Override
+        public void onServiceRegistered(NsdServiceInfo serviceInfo) {
+            if (MDNS_DEBUG) Slog.i(TAG, "Registered pairing service: " + serviceInfo);
+        }
+
+        @Override
+        public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
+            Slog.e(TAG, "Failed to register pairing service(err=" + errorCode
+                    + "): " + serviceInfo);
+            cancelPairing();
+        }
+
+        @Override
+        public void onServiceUnregistered(NsdServiceInfo serviceInfo) {
+            if (MDNS_DEBUG) Slog.i(TAG, "Unregistered pairing service: " + serviceInfo);
+        }
+
+        @Override
+        public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
+            Slog.w(TAG, "Failed to unregister pairing service(err=" + errorCode
+                    + "): " + serviceInfo);
+        }
+    }
+
+    interface AdbConnectionPortListener {
+        void onPortReceived(int port);
+    }
+
+    /**
+     * This class will poll for a period of time for adbd to write the port
+     * it connected to.
+     *
+     * TODO(joshuaduong): The port is being sent via system property because the adbd socket
+     * (AdbDebuggingManager) is not created when ro.adb.secure=0. Thus, we must communicate the
+     * port through different means. A better fix would be to always start AdbDebuggingManager, but
+     * it needs to adjust accordingly on whether ro.adb.secure is set.
+     */
+    static class AdbConnectionPortPoller extends Thread {
+        private final String mAdbPortProp = "service.adb.tls.port";
+        private AdbConnectionPortListener mListener;
+        private final int mDurationSecs = 10;
+        private AtomicBoolean mCanceled = new AtomicBoolean(false);
+
+        AdbConnectionPortPoller(AdbConnectionPortListener listener) {
+            mListener = listener;
+        }
+
+        @Override
+        public void run() {
+            if (DEBUG) Slog.d(TAG, "Starting adb port property poller");
+            // Once adbwifi is enabled, we poll the service.adb.tls.port
+            // system property until we get the port, or -1 on failure.
+            // Let's also limit the polling to 10 seconds, just in case
+            // something went wrong.
+            for (int i = 0; i < mDurationSecs; ++i) {
+                if (mCanceled.get()) {
+                    return;
+                }
+
+                // If the property is set to -1, then that means adbd has failed
+                // to start the server. Otherwise we should have a valid port.
+                int port = SystemProperties.getInt(mAdbPortProp, Integer.MAX_VALUE);
+                if (port == -1 || (port > 0 && port <= 65535)) {
+                    mListener.onPortReceived(port);
+                    return;
+                }
+                SystemClock.sleep(1000);
+            }
+            Slog.w(TAG, "Failed to receive adb connection port");
+            mListener.onPortReceived(-1);
+        }
+
+        public void cancelAndWait() {
+            mCanceled.set(true);
+            if (this.isAlive()) {
+                try {
+                    this.join();
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+    }
+
+    class PortListenerImpl implements AdbConnectionPortListener {
+        public void onPortReceived(int port) {
+            Message msg = mHandler.obtainMessage(port > 0
+                     ? AdbDebuggingHandler.MSG_SERVER_CONNECTED
+                     : AdbDebuggingHandler.MSG_SERVER_DISCONNECTED);
+            msg.obj = port;
+            mHandler.sendMessage(msg);
+        }
     }
 
     class AdbDebuggingThread extends Thread {
@@ -213,6 +426,46 @@
                                 AdbDebuggingHandler.MESSAGE_ADB_CONNECTED_KEY);
                         msg.obj = key;
                         mHandler.sendMessage(msg);
+                    } else if (buffer[0] == 'W' && buffer[1] == 'E') {
+                        // adbd_auth.h and AdbTransportType.aidl need to be kept in
+                        // sync.
+                        byte transportType = buffer[2];
+                        String key = new String(Arrays.copyOfRange(buffer, 3, count));
+                        if (transportType == AdbTransportType.USB) {
+                            Slog.d(TAG, "Received USB TLS connected key message: " + key);
+                            Message msg = mHandler.obtainMessage(
+                                    AdbDebuggingHandler.MESSAGE_ADB_CONNECTED_KEY);
+                            msg.obj = key;
+                            mHandler.sendMessage(msg);
+                        } else if (transportType == AdbTransportType.WIFI) {
+                            Slog.d(TAG, "Received WIFI TLS connected key message: " + key);
+                            Message msg = mHandler.obtainMessage(
+                                    AdbDebuggingHandler.MSG_WIFI_DEVICE_CONNECTED);
+                            msg.obj = key;
+                            mHandler.sendMessage(msg);
+                        } else {
+                            Slog.e(TAG, "Got unknown transport type from adbd (" + transportType
+                                    + ")");
+                        }
+                    } else if (buffer[0] == 'W' && buffer[1] == 'F') {
+                        byte transportType = buffer[2];
+                        String key = new String(Arrays.copyOfRange(buffer, 3, count));
+                        if (transportType == AdbTransportType.USB) {
+                            Slog.d(TAG, "Received USB TLS disconnect message: " + key);
+                            Message msg = mHandler.obtainMessage(
+                                    AdbDebuggingHandler.MESSAGE_ADB_DISCONNECT);
+                            msg.obj = key;
+                            mHandler.sendMessage(msg);
+                        } else if (transportType == AdbTransportType.WIFI) {
+                            Slog.d(TAG, "Received WIFI TLS disconnect key message: " + key);
+                            Message msg = mHandler.obtainMessage(
+                                    AdbDebuggingHandler.MSG_WIFI_DEVICE_DISCONNECTED);
+                            msg.obj = key;
+                            mHandler.sendMessage(msg);
+                        } else {
+                            Slog.e(TAG, "Got unknown transport type from adbd (" + transportType
+                                    + ")");
+                        }
                     } else {
                         Slog.e(TAG, "Wrong message: "
                                 + (new String(Arrays.copyOfRange(buffer, 0, 2))));
@@ -268,7 +521,156 @@
         }
     }
 
+    class AdbConnectionInfo {
+        private String mBssid;
+        private String mSsid;
+        private int mPort;
+
+        AdbConnectionInfo() {
+            mBssid = "";
+            mSsid = "";
+            mPort = -1;
+        }
+
+        AdbConnectionInfo(String bssid, String ssid) {
+            mBssid = bssid;
+            mSsid = ssid;
+        }
+
+        AdbConnectionInfo(AdbConnectionInfo other) {
+            mBssid = other.mBssid;
+            mSsid = other.mSsid;
+            mPort = other.mPort;
+        }
+
+        public String getBSSID() {
+            return mBssid;
+        }
+
+        public String getSSID() {
+            return mSsid;
+        }
+
+        public int getPort() {
+            return mPort;
+        }
+
+        public void setPort(int port) {
+            mPort = port;
+        }
+
+        public void clear() {
+            mBssid = "";
+            mSsid = "";
+            mPort = -1;
+        }
+    }
+
+    private void setAdbConnectionInfo(AdbConnectionInfo info) {
+        synchronized (mAdbConnectionInfo) {
+            if (info == null) {
+                mAdbConnectionInfo.clear();
+                return;
+            }
+            mAdbConnectionInfo = info;
+        }
+    }
+
+    private AdbConnectionInfo getAdbConnectionInfo() {
+        synchronized (mAdbConnectionInfo) {
+            return new AdbConnectionInfo(mAdbConnectionInfo);
+        }
+    }
+
     class AdbDebuggingHandler extends Handler {
+        private NotificationManager mNotificationManager;
+        private boolean mAdbNotificationShown;
+
+        private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                String action = intent.getAction();
+                // We only care about when wifi is disabled, and when there is a wifi network
+                // change.
+                if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
+                    int state = intent.getIntExtra(
+                            WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_DISABLED);
+                    if (state == WifiManager.WIFI_STATE_DISABLED) {
+                        Slog.i(TAG, "Wifi disabled. Disabling adbwifi.");
+                        Settings.Global.putInt(mContentResolver,
+                                Settings.Global.ADB_WIFI_ENABLED, 0);
+                    }
+                } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
+                    // We only care about wifi type connections
+                    NetworkInfo networkInfo = (NetworkInfo) intent.getParcelableExtra(
+                            WifiManager.EXTRA_NETWORK_INFO);
+                    if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
+                        // Check for network disconnect
+                        if (!networkInfo.isConnected()) {
+                            Slog.i(TAG, "Network disconnected. Disabling adbwifi.");
+                            Settings.Global.putInt(mContentResolver,
+                                    Settings.Global.ADB_WIFI_ENABLED, 0);
+                            return;
+                        }
+
+                        WifiManager wifiManager =
+                                (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+                        WifiInfo wifiInfo = wifiManager.getConnectionInfo();
+                        if (wifiInfo == null || wifiInfo.getNetworkId() == -1) {
+                            Slog.i(TAG, "Not connected to any wireless network."
+                                    + " Not enabling adbwifi.");
+                            Settings.Global.putInt(mContentResolver,
+                                    Settings.Global.ADB_WIFI_ENABLED, 0);
+                        }
+
+                        // Check for network change
+                        String bssid = wifiInfo.getBSSID();
+                        if (bssid == null || bssid.isEmpty()) {
+                            Slog.e(TAG, "Unable to get the wifi ap's BSSID. Disabling adbwifi.");
+                            Settings.Global.putInt(mContentResolver,
+                                    Settings.Global.ADB_WIFI_ENABLED, 0);
+                        }
+                        synchronized (mAdbConnectionInfo) {
+                            if (!bssid.equals(mAdbConnectionInfo.getBSSID())) {
+                                Slog.i(TAG, "Detected wifi network change. Disabling adbwifi.");
+                                Settings.Global.putInt(mContentResolver,
+                                        Settings.Global.ADB_WIFI_ENABLED, 0);
+                            }
+                        }
+                    }
+                }
+            }
+        };
+
+        private static final String ADB_NOTIFICATION_CHANNEL_ID_TV = "usbdevicemanager.adb.tv";
+
+        private boolean isTv() {
+            return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
+        }
+
+        private void setupNotifications() {
+            if (mNotificationManager != null) {
+                return;
+            }
+            mNotificationManager = (NotificationManager)
+                    mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+            if (mNotificationManager == null) {
+                Slog.e(TAG, "Unable to setup notifications for wireless debugging");
+                return;
+            }
+
+            // Ensure that the notification channels are set up
+            if (isTv()) {
+                // TV-specific notification channel
+                mNotificationManager.createNotificationChannel(
+                        new NotificationChannel(ADB_NOTIFICATION_CHANNEL_ID_TV,
+                                mContext.getString(
+                                        com.android.internal.R.string
+                                                .adb_debugging_notification_channel_tv),
+                                NotificationManager.IMPORTANCE_HIGH));
+            }
+        }
+
         // The default time to schedule the job to keep the keystore updated with a currently
         // connected key as well as to removed expired keys.
         static final long UPDATE_KEYSTORE_JOB_INTERVAL = 86400000;
@@ -288,8 +690,50 @@
         static final int MESSAGE_ADB_UPDATE_KEYSTORE = 9;
         static final int MESSAGE_ADB_CONNECTED_KEY = 10;
 
+        // === Messages from the UI ==============
+        // UI asks adbd to enable adbdwifi
+        static final int MSG_ADBDWIFI_ENABLE = 11;
+        // UI asks adbd to disable adbdwifi
+        static final int MSG_ADBDWIFI_DISABLE = 12;
+        // Cancel pairing
+        static final int MSG_PAIRING_CANCEL = 14;
+        // Enable pairing by pairing code
+        static final int MSG_PAIR_PAIRING_CODE = 15;
+        // Enable pairing by QR code
+        static final int MSG_PAIR_QR_CODE = 16;
+        // UI asks to unpair (forget) a device.
+        static final int MSG_REQ_UNPAIR = 17;
+        // User allows debugging on the current network
+        static final int MSG_ADBWIFI_ALLOW = 18;
+        // User denies debugging on the current network
+        static final int MSG_ADBWIFI_DENY = 19;
+
+        // === Messages from the PairingThread ===========
+        // Result of the pairing
+        static final int MSG_RESPONSE_PAIRING_RESULT = 20;
+        // The port opened for pairing
+        static final int MSG_RESPONSE_PAIRING_PORT = 21;
+
+        // === Messages from adbd ================
+        // Notifies us a wifi device connected.
+        static final int MSG_WIFI_DEVICE_CONNECTED = 22;
+        // Notifies us a wifi device disconnected.
+        static final int MSG_WIFI_DEVICE_DISCONNECTED = 23;
+        // Notifies us the TLS server is connected and listening
+        static final int MSG_SERVER_CONNECTED = 24;
+        // Notifies us the TLS server is disconnected
+        static final int MSG_SERVER_DISCONNECTED = 25;
+
+        // === Messages we can send to adbd ===========
+        static final String MSG_DISCONNECT_DEVICE = "DD";
+        static final String MSG_DISABLE_ADBDWIFI = "DA";
+
         private AdbKeyStore mAdbKeyStore;
 
+        // Usb, Wi-Fi transports can be enabled together or separately, so don't break the framework
+        // connection unless all transport types are disconnected.
+        private int mAdbEnabledRefCount = 0;
+
         private ContentObserver mAuthTimeObserver = new ContentObserver(this) {
             @Override
             public void onChange(boolean selfChange, Uri uri) {
@@ -314,44 +758,111 @@
             mAdbKeyStore = adbKeyStore;
         }
 
+        // Show when at least one device is connected.
+        public void showAdbConnectedNotification(boolean show) {
+            final int id = SystemMessage.NOTE_ADB_WIFI_ACTIVE;
+            final int titleRes = com.android.internal.R.string.adbwifi_active_notification_title;
+            if (show == mAdbNotificationShown) {
+                return;
+            }
+            setupNotifications();
+            if (!mAdbNotificationShown) {
+                Resources r = mContext.getResources();
+                CharSequence title = r.getText(titleRes);
+                CharSequence message = r.getText(
+                        com.android.internal.R.string.adbwifi_active_notification_message);
+
+                Intent intent = new Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS);
+                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                        | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+                PendingIntent pi = PendingIntent.getActivityAsUser(mContext, 0,
+                        intent, 0, null, UserHandle.CURRENT);
+
+                Notification notification =
+                        new Notification.Builder(mContext, SystemNotificationChannels.DEVELOPER)
+                                .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
+                                .setWhen(0)
+                                .setOngoing(true)
+                                .setTicker(title)
+                                .setDefaults(0)  // please be quiet
+                                .setColor(mContext.getColor(
+                                        com.android.internal.R.color
+                                                .system_notification_accent_color))
+                                .setContentTitle(title)
+                                .setContentText(message)
+                                .setContentIntent(pi)
+                                .setVisibility(Notification.VISIBILITY_PUBLIC)
+                                .extend(new Notification.TvExtender()
+                                        .setChannelId(ADB_NOTIFICATION_CHANNEL_ID_TV))
+                                .build();
+                mAdbNotificationShown = true;
+                mNotificationManager.notifyAsUser(null, id, notification,
+                        UserHandle.ALL);
+            } else {
+                mAdbNotificationShown = false;
+                mNotificationManager.cancelAsUser(null, id, UserHandle.ALL);
+            }
+        }
+
+        private void startAdbDebuggingThread() {
+            ++mAdbEnabledRefCount;
+            if (DEBUG) Slog.i(TAG, "startAdbDebuggingThread ref=" + mAdbEnabledRefCount);
+            if (mAdbEnabledRefCount > 1) {
+                return;
+            }
+
+            registerForAuthTimeChanges();
+            mThread = new AdbDebuggingThread();
+            mThread.start();
+
+            mAdbKeyStore.updateKeyStore();
+            scheduleJobToUpdateAdbKeyStore();
+        }
+
+        private void stopAdbDebuggingThread() {
+            --mAdbEnabledRefCount;
+            if (DEBUG) Slog.i(TAG, "stopAdbDebuggingThread ref=" + mAdbEnabledRefCount);
+            if (mAdbEnabledRefCount > 0) {
+                return;
+            }
+
+            if (mThread != null) {
+                mThread.stopListening();
+                mThread = null;
+            }
+
+            if (!mConnectedKeys.isEmpty()) {
+                for (Map.Entry<String, Integer> entry : mConnectedKeys.entrySet()) {
+                    mAdbKeyStore.setLastConnectionTime(entry.getKey(),
+                            System.currentTimeMillis());
+                }
+                sendPersistKeyStoreMessage();
+                mConnectedKeys.clear();
+                mWifiConnectedKeys.clear();
+            }
+            scheduleJobToUpdateAdbKeyStore();
+        }
+
         public void handleMessage(Message msg) {
+            if (mAdbKeyStore == null) {
+                mAdbKeyStore = new AdbKeyStore();
+            }
+
             switch (msg.what) {
                 case MESSAGE_ADB_ENABLED:
-                    if (mAdbEnabled) {
+                    if (mAdbUsbEnabled) {
                         break;
                     }
-                    registerForAuthTimeChanges();
-                    mAdbEnabled = true;
-
-                    mThread = new AdbDebuggingThread();
-                    mThread.start();
-
-                    mAdbKeyStore = new AdbKeyStore();
-                    mAdbKeyStore.updateKeyStore();
-                    scheduleJobToUpdateAdbKeyStore();
+                    startAdbDebuggingThread();
+                    mAdbUsbEnabled = true;
                     break;
 
                 case MESSAGE_ADB_DISABLED:
-                    if (!mAdbEnabled) {
+                    if (!mAdbUsbEnabled) {
                         break;
                     }
-
-                    mAdbEnabled = false;
-
-                    if (mThread != null) {
-                        mThread.stopListening();
-                        mThread = null;
-                    }
-
-                    if (!mConnectedKeys.isEmpty()) {
-                        for (String connectedKey : mConnectedKeys) {
-                            mAdbKeyStore.setLastConnectionTime(connectedKey,
-                                    System.currentTimeMillis());
-                        }
-                        sendPersistKeyStoreMessage();
-                        mConnectedKeys.clear();
-                    }
-                    scheduleJobToUpdateAdbKeyStore();
+                    stopAdbDebuggingThread();
+                    mAdbUsbEnabled = false;
                     break;
 
                 case MESSAGE_ADB_ALLOW: {
@@ -367,8 +878,8 @@
                     if (mThread != null) {
                         mThread.sendResponse("OK");
                         if (alwaysAllow) {
-                            if (!mConnectedKeys.contains(key)) {
-                                mConnectedKeys.add(key);
+                            if (!mConnectedKeys.containsKey(key)) {
+                                mConnectedKeys.put(key, 1);
                             }
                             mAdbKeyStore.setLastConnectionTime(key, System.currentTimeMillis());
                             sendPersistKeyStoreMessage();
@@ -407,13 +918,14 @@
                     }
                     logAdbConnectionChanged(key, AdbProtoEnums.AWAITING_USER_APPROVAL, false);
                     mFingerprints = fingerprints;
-                    startConfirmation(key, mFingerprints);
+                    startConfirmationForKey(key, mFingerprints);
                     break;
                 }
 
                 case MESSAGE_ADB_CLEAR: {
                     Slog.d(TAG, "Received a request to clear the adb authorizations");
                     mConnectedKeys.clear();
+                    mWifiConnectedKeys.clear();
                     mAdbKeyStore.deleteKeyStore();
                     cancelJobToUpdateAdbKeyStore();
                     break;
@@ -423,12 +935,17 @@
                     String key = (String) msg.obj;
                     boolean alwaysAllow = false;
                     if (key != null && key.length() > 0) {
-                        if (mConnectedKeys.contains(key)) {
+                        if (mConnectedKeys.containsKey(key)) {
                             alwaysAllow = true;
-                            mAdbKeyStore.setLastConnectionTime(key, System.currentTimeMillis());
-                            sendPersistKeyStoreMessage();
-                            scheduleJobToUpdateAdbKeyStore();
-                            mConnectedKeys.remove(key);
+                            int refcount = mConnectedKeys.get(key) - 1;
+                            if (refcount == 0) {
+                                mAdbKeyStore.setLastConnectionTime(key, System.currentTimeMillis());
+                                sendPersistKeyStoreMessage();
+                                scheduleJobToUpdateAdbKeyStore();
+                                mConnectedKeys.remove(key);
+                            } else {
+                                mConnectedKeys.put(key, refcount);
+                            }
                         }
                     } else {
                         Slog.w(TAG, "Received a disconnected key message with an empty key");
@@ -446,8 +963,8 @@
 
                 case MESSAGE_ADB_UPDATE_KEYSTORE: {
                     if (!mConnectedKeys.isEmpty()) {
-                        for (String connectedKey : mConnectedKeys) {
-                            mAdbKeyStore.setLastConnectionTime(connectedKey,
+                        for (Map.Entry<String, Integer> entry : mConnectedKeys.entrySet()) {
+                            mAdbKeyStore.setLastConnectionTime(entry.getKey(),
                                     System.currentTimeMillis());
                         }
                         sendPersistKeyStoreMessage();
@@ -464,8 +981,10 @@
                     if (key == null || key.length() == 0) {
                         Slog.w(TAG, "Received a connected key message with an empty key");
                     } else {
-                        if (!mConnectedKeys.contains(key)) {
-                            mConnectedKeys.add(key);
+                        if (!mConnectedKeys.containsKey(key)) {
+                            mConnectedKeys.put(key, 1);
+                        } else {
+                            mConnectedKeys.put(key, mConnectedKeys.get(key) + 1);
                         }
                         mAdbKeyStore.setLastConnectionTime(key, System.currentTimeMillis());
                         sendPersistKeyStoreMessage();
@@ -474,6 +993,199 @@
                     }
                     break;
                 }
+                case MSG_ADBDWIFI_ENABLE: {
+                    if (mAdbWifiEnabled) {
+                        break;
+                    }
+
+                    AdbConnectionInfo currentInfo = getCurrentWifiApInfo();
+                    if (currentInfo == null) {
+                        Settings.Global.putInt(mContentResolver,
+                                Settings.Global.ADB_WIFI_ENABLED, 0);
+                        break;
+                    }
+
+                    if (!verifyWifiNetwork(currentInfo.getBSSID(),
+                            currentInfo.getSSID())) {
+                        // This means that the network is not in the list of trusted networks.
+                        // We'll give user a prompt on whether to allow wireless debugging on
+                        // the current wifi network.
+                        Settings.Global.putInt(mContentResolver,
+                                Settings.Global.ADB_WIFI_ENABLED, 0);
+                        break;
+                    }
+
+                    setAdbConnectionInfo(currentInfo);
+                    IntentFilter intentFilter =
+                            new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION);
+                    intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+                    mContext.registerReceiver(mBroadcastReceiver, intentFilter);
+
+                    SystemProperties.set(WIFI_PERSISTENT_CONFIG_PROPERTY, "1");
+                    mConnectionPortPoller =
+                            new AdbDebuggingManager.AdbConnectionPortPoller(mPortListener);
+                    mConnectionPortPoller.start();
+
+                    startAdbDebuggingThread();
+                    mAdbWifiEnabled = true;
+
+                    if (DEBUG) Slog.i(TAG, "adb start wireless adb");
+                    break;
+                }
+                case MSG_ADBDWIFI_DISABLE:
+                    if (!mAdbWifiEnabled) {
+                        break;
+                    }
+                    mAdbWifiEnabled = false;
+                    setAdbConnectionInfo(null);
+                    mContext.unregisterReceiver(mBroadcastReceiver);
+
+                    if (mThread != null) {
+                        mThread.sendResponse(MSG_DISABLE_ADBDWIFI);
+                    }
+                    onAdbdWifiServerDisconnected(-1);
+                    stopAdbDebuggingThread();
+                    break;
+                case MSG_ADBWIFI_ALLOW:
+                    if (mAdbWifiEnabled) {
+                        break;
+                    }
+                    String bssid = (String) msg.obj;
+                    boolean alwaysAllow = msg.arg1 == 1;
+                    if (alwaysAllow) {
+                        mAdbKeyStore.addTrustedNetwork(bssid);
+                    }
+
+                    // Let's check again to make sure we didn't switch networks while verifying
+                    // the wifi bssid.
+                    AdbConnectionInfo newInfo = getCurrentWifiApInfo();
+                    if (newInfo == null || !bssid.equals(newInfo.getBSSID())) {
+                        break;
+                    }
+
+                    setAdbConnectionInfo(newInfo);
+                    Settings.Global.putInt(mContentResolver,
+                            Settings.Global.ADB_WIFI_ENABLED, 1);
+                    IntentFilter intentFilter =
+                            new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION);
+                    intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+                    mContext.registerReceiver(mBroadcastReceiver, intentFilter);
+
+                    SystemProperties.set(WIFI_PERSISTENT_CONFIG_PROPERTY, "1");
+                    mConnectionPortPoller =
+                            new AdbDebuggingManager.AdbConnectionPortPoller(mPortListener);
+                    mConnectionPortPoller.start();
+
+                    startAdbDebuggingThread();
+                    mAdbWifiEnabled = true;
+
+                    if (DEBUG) Slog.i(TAG, "adb start wireless adb");
+                    break;
+                case MSG_ADBWIFI_DENY:
+                    Settings.Global.putInt(mContentResolver,
+                            Settings.Global.ADB_WIFI_ENABLED, 0);
+                    sendServerConnectionState(false, -1);
+                    break;
+                case MSG_REQ_UNPAIR: {
+                    String fingerprint = (String) msg.obj;
+                    // Tell adbd to disconnect the device if connected.
+                    String publicKey = mAdbKeyStore.findKeyFromFingerprint(fingerprint);
+                    if (publicKey == null || publicKey.isEmpty()) {
+                        Slog.e(TAG, "Not a known fingerprint [" + fingerprint + "]");
+                        break;
+                    }
+                    String cmdStr = MSG_DISCONNECT_DEVICE + publicKey;
+                    if (mThread != null) {
+                        mThread.sendResponse(cmdStr);
+                    }
+                    mAdbKeyStore.removeKey(publicKey);
+                    // Send the updated paired devices list to the UI.
+                    sendPairedDevicesToUI(mAdbKeyStore.getPairedDevices());
+                    break;
+                }
+                case MSG_RESPONSE_PAIRING_RESULT: {
+                    Bundle bundle = (Bundle) msg.obj;
+                    String publicKey = bundle.getString("publicKey");
+                    onPairingResult(publicKey);
+                    // Send the updated paired devices list to the UI.
+                    sendPairedDevicesToUI(mAdbKeyStore.getPairedDevices());
+                    break;
+                }
+                case MSG_RESPONSE_PAIRING_PORT: {
+                    int port = (int) msg.obj;
+                    sendPairingPortToUI(port);
+                    break;
+                }
+                case MSG_PAIR_PAIRING_CODE: {
+                    String pairingCode = createPairingCode(PAIRING_CODE_LENGTH);
+                    updateUIPairCode(pairingCode);
+                    mPairingThread = new PairingThread(pairingCode, null);
+                    mPairingThread.start();
+                    break;
+                }
+                case MSG_PAIR_QR_CODE: {
+                    Bundle bundle = (Bundle) msg.obj;
+                    String serviceName = bundle.getString("serviceName");
+                    String password = bundle.getString("password");
+                    mPairingThread = new PairingThread(password, serviceName);
+                    mPairingThread.start();
+                    break;
+                }
+                case MSG_PAIRING_CANCEL:
+                    if (mPairingThread != null) {
+                        mPairingThread.cancelPairing();
+                        try {
+                            mPairingThread.join();
+                        } catch (InterruptedException e) {
+                            Slog.w(TAG, "Error while waiting for pairing thread to quit.");
+                            e.printStackTrace();
+                        }
+                        mPairingThread = null;
+                    }
+                    break;
+                case MSG_WIFI_DEVICE_CONNECTED: {
+                    String key = (String) msg.obj;
+                    if (mWifiConnectedKeys.add(key)) {
+                        sendPairedDevicesToUI(mAdbKeyStore.getPairedDevices());
+                        showAdbConnectedNotification(true);
+                    }
+                    break;
+                }
+                case MSG_WIFI_DEVICE_DISCONNECTED: {
+                    String key = (String) msg.obj;
+                    if (mWifiConnectedKeys.remove(key)) {
+                        sendPairedDevicesToUI(mAdbKeyStore.getPairedDevices());
+                        if (mWifiConnectedKeys.isEmpty()) {
+                            showAdbConnectedNotification(false);
+                        }
+                    }
+                    break;
+                }
+                case MSG_SERVER_CONNECTED: {
+                    int port = (int) msg.obj;
+                    onAdbdWifiServerConnected(port);
+                    synchronized (mAdbConnectionInfo) {
+                        mAdbConnectionInfo.setPort(port);
+                    }
+                    Settings.Global.putInt(mContentResolver,
+                            Settings.Global.ADB_WIFI_ENABLED, 1);
+                    break;
+                }
+                case MSG_SERVER_DISCONNECTED: {
+                    if (!mAdbWifiEnabled) {
+                        break;
+                    }
+                    int port = (int) msg.obj;
+                    onAdbdWifiServerDisconnected(port);
+                    Settings.Global.putInt(mContentResolver,
+                            Settings.Global.ADB_WIFI_ENABLED, 0);
+                    stopAdbDebuggingThread();
+                    if (mConnectionPortPoller != null) {
+                        mConnectionPortPoller.cancelAndWait();
+                        mConnectionPortPoller = null;
+                    }
+                    break;
+                }
             }
         }
 
@@ -535,6 +1247,142 @@
         private void cancelJobToUpdateAdbKeyStore() {
             removeMessages(AdbDebuggingHandler.MESSAGE_ADB_UPDATE_KEYSTORE);
         }
+
+        // Generates a random string of digits with size |size|.
+        private String createPairingCode(int size) {
+            String res = "";
+            SecureRandom rand = new SecureRandom();
+            for (int i = 0; i < size; ++i) {
+                res += rand.nextInt(10);
+            }
+
+            return res;
+        }
+
+        private void sendServerConnectionState(boolean connected, int port) {
+            Intent intent = new Intent(AdbManager.WIRELESS_DEBUG_STATE_CHANGED_ACTION);
+            intent.putExtra(AdbManager.WIRELESS_STATUS_EXTRA, connected
+                    ? AdbManager.WIRELESS_STATUS_CONNECTED
+                    : AdbManager.WIRELESS_STATUS_DISCONNECTED);
+            intent.putExtra(AdbManager.WIRELESS_DEBUG_PORT_EXTRA, port);
+            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+        }
+
+        private void onAdbdWifiServerConnected(int port) {
+            // Send the paired devices list to the UI
+            sendPairedDevicesToUI(mAdbKeyStore.getPairedDevices());
+            sendServerConnectionState(true, port);
+        }
+
+        private void onAdbdWifiServerDisconnected(int port) {
+            // The TLS server disconnected while we had wireless debugging enabled.
+            // Let's disable it.
+            mWifiConnectedKeys.clear();
+            showAdbConnectedNotification(false);
+            sendServerConnectionState(false, port);
+        }
+
+        /**
+         * Returns the [bssid, ssid] of the current access point.
+         */
+        private AdbConnectionInfo getCurrentWifiApInfo() {
+            WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+            WifiInfo wifiInfo = wifiManager.getConnectionInfo();
+            if (wifiInfo == null || wifiInfo.getNetworkId() == -1) {
+                Slog.i(TAG, "Not connected to any wireless network. Not enabling adbwifi.");
+                return null;
+            }
+
+            String ssid = null;
+            if (wifiInfo.isPasspointAp() || wifiInfo.isOsuAp()) {
+                ssid = wifiInfo.getPasspointProviderFriendlyName();
+            } else {
+                ssid = wifiInfo.getSSID();
+                if (ssid == null || WifiSsid.NONE.equals(ssid)) {
+                    // OK, it's not in the connectionInfo; we have to go hunting for it
+                    List<WifiConfiguration> networks = wifiManager.getConfiguredNetworks();
+                    int length = networks.size();
+                    for (int i = 0; i < length; i++) {
+                        if (networks.get(i).networkId == wifiInfo.getNetworkId()) {
+                            ssid = networks.get(i).SSID;
+                        }
+                    }
+                    if (ssid == null) {
+                        Slog.e(TAG, "Unable to get ssid of the wifi AP.");
+                        return null;
+                    }
+                }
+            }
+
+            String bssid = wifiInfo.getBSSID();
+            if (bssid == null || bssid.isEmpty()) {
+                Slog.e(TAG, "Unable to get the wifi ap's BSSID.");
+                return null;
+            }
+            return new AdbConnectionInfo(bssid, ssid);
+        }
+
+        private boolean verifyWifiNetwork(String bssid, String ssid) {
+            // Check against a list of user-trusted networks.
+            if (mAdbKeyStore.isTrustedNetwork(bssid)) {
+                return true;
+            }
+
+            // Ask user to confirm using wireless debugging on this network.
+            startConfirmationForNetwork(ssid, bssid);
+            return false;
+        }
+
+        private void onPairingResult(String publicKey) {
+            if (publicKey == null) {
+                Intent intent = new Intent(AdbManager.WIRELESS_DEBUG_PAIRING_RESULT_ACTION);
+                intent.putExtra(AdbManager.WIRELESS_STATUS_EXTRA, AdbManager.WIRELESS_STATUS_FAIL);
+                mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+            } else {
+                Intent intent = new Intent(AdbManager.WIRELESS_DEBUG_PAIRING_RESULT_ACTION);
+                intent.putExtra(AdbManager.WIRELESS_STATUS_EXTRA,
+                        AdbManager.WIRELESS_STATUS_SUCCESS);
+                String fingerprints = getFingerprints(publicKey);
+                String hostname = "nouser@nohostname";
+                String[] args = publicKey.split("\\s+");
+                if (args.length > 1) {
+                    hostname = args[1];
+                }
+                PairDevice device = new PairDevice(fingerprints, hostname, false);
+                intent.putExtra(AdbManager.WIRELESS_PAIR_DEVICE_EXTRA, device);
+                mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+                // Add the key into the keystore
+                mAdbKeyStore.setLastConnectionTime(publicKey,
+                        System.currentTimeMillis());
+                sendPersistKeyStoreMessage();
+                scheduleJobToUpdateAdbKeyStore();
+            }
+        }
+
+        private void sendPairingPortToUI(int port) {
+            Intent intent = new Intent(AdbManager.WIRELESS_DEBUG_PAIRING_RESULT_ACTION);
+            intent.putExtra(AdbManager.WIRELESS_STATUS_EXTRA,
+                    AdbManager.WIRELESS_STATUS_CONNECTED);
+            intent.putExtra(AdbManager.WIRELESS_DEBUG_PORT_EXTRA, port);
+            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+        }
+
+        private void sendPairedDevicesToUI(Map<String, PairDevice> devices) {
+            Intent intent = new Intent(AdbManager.WIRELESS_DEBUG_PAIRED_DEVICES_ACTION);
+            // Map is not serializable, so need to downcast
+            intent.putExtra(AdbManager.WIRELESS_DEVICES_EXTRA, (HashMap) devices);
+            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+        }
+
+        private void updateUIPairCode(String code) {
+            if (DEBUG) Slog.i(TAG, "updateUIPairCode: " + code);
+
+            Intent intent = new Intent(AdbManager.WIRELESS_DEBUG_PAIRING_RESULT_ACTION);
+            intent.putExtra(AdbManager.WIRELESS_PAIRING_CODE_EXTRA, code);
+            intent.putExtra(AdbManager.WIRELESS_STATUS_EXTRA,
+                    AdbManager.WIRELESS_STATUS_PAIRING_CODE);
+            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+        }
     }
 
     private String getFingerprints(String key) {
@@ -571,7 +1419,34 @@
         return sb.toString();
     }
 
-    private void startConfirmation(String key, String fingerprints) {
+    private void startConfirmationForNetwork(String ssid, String bssid) {
+        List<Map.Entry<String, String>> extras = new ArrayList<Map.Entry<String, String>>();
+        extras.add(new AbstractMap.SimpleEntry<String, String>("ssid", ssid));
+        extras.add(new AbstractMap.SimpleEntry<String, String>("bssid", bssid));
+        int currentUserId = ActivityManager.getCurrentUser();
+        UserInfo userInfo = UserManager.get(mContext).getUserInfo(currentUserId);
+        String componentString;
+        if (userInfo.isAdmin()) {
+            componentString = Resources.getSystem().getString(
+                    com.android.internal.R.string.config_customAdbWifiNetworkConfirmationComponent);
+        } else {
+            componentString = Resources.getSystem().getString(
+                    com.android.internal.R.string.config_customAdbWifiNetworkConfirmationComponent);
+        }
+        ComponentName componentName = ComponentName.unflattenFromString(componentString);
+        if (startConfirmationActivity(componentName, userInfo.getUserHandle(), extras)
+                || startConfirmationService(componentName, userInfo.getUserHandle(),
+                        extras)) {
+            return;
+        }
+        Slog.e(TAG, "Unable to start customAdbWifiNetworkConfirmation[SecondaryUser]Component "
+                + componentString + " as an Activity or a Service");
+    }
+
+    private void startConfirmationForKey(String key, String fingerprints) {
+        List<Map.Entry<String, String>> extras = new ArrayList<Map.Entry<String, String>>();
+        extras.add(new AbstractMap.SimpleEntry<String, String>("key", key));
+        extras.add(new AbstractMap.SimpleEntry<String, String>("fingerprints", fingerprints));
         int currentUserId = ActivityManager.getCurrentUser();
         UserInfo userInfo = UserManager.get(mContext).getUserInfo(currentUserId);
         String componentString;
@@ -586,9 +1461,9 @@
                     R.string.config_customAdbPublicKeyConfirmationSecondaryUserComponent);
         }
         ComponentName componentName = ComponentName.unflattenFromString(componentString);
-        if (startConfirmationActivity(componentName, userInfo.getUserHandle(), key, fingerprints)
+        if (startConfirmationActivity(componentName, userInfo.getUserHandle(), extras)
                 || startConfirmationService(componentName, userInfo.getUserHandle(),
-                        key, fingerprints)) {
+                        extras)) {
             return;
         }
         Slog.e(TAG, "unable to start customAdbPublicKeyConfirmation[SecondaryUser]Component "
@@ -599,9 +1474,9 @@
      * @return true if the componentName led to an Activity that was started.
      */
     private boolean startConfirmationActivity(ComponentName componentName, UserHandle userHandle,
-            String key, String fingerprints) {
+            List<Map.Entry<String, String>> extras) {
         PackageManager packageManager = mContext.getPackageManager();
-        Intent intent = createConfirmationIntent(componentName, key, fingerprints);
+        Intent intent = createConfirmationIntent(componentName, extras);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         if (packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null) {
             try {
@@ -618,8 +1493,8 @@
      * @return true if the componentName led to a Service that was started.
      */
     private boolean startConfirmationService(ComponentName componentName, UserHandle userHandle,
-            String key, String fingerprints) {
-        Intent intent = createConfirmationIntent(componentName, key, fingerprints);
+            List<Map.Entry<String, String>> extras) {
+        Intent intent = createConfirmationIntent(componentName, extras);
         try {
             if (mContext.startServiceAsUser(intent, userHandle) != null) {
                 return true;
@@ -630,12 +1505,13 @@
         return false;
     }
 
-    private Intent createConfirmationIntent(ComponentName componentName, String key,
-            String fingerprints) {
+    private Intent createConfirmationIntent(ComponentName componentName,
+            List<Map.Entry<String, String>> extras) {
         Intent intent = new Intent();
         intent.setClassName(componentName.getPackageName(), componentName.getClassName());
-        intent.putExtra("key", key);
-        intent.putExtra("fingerprints", fingerprints);
+        for (Map.Entry<String, String> entry : extras) {
+            intent.putExtra(entry.getKey(), entry.getValue());
+        }
         return intent;
     }
 
@@ -728,7 +1604,8 @@
             mHandler.sendEmptyMessage(enabled ? AdbDebuggingHandler.MESSAGE_ADB_ENABLED
                                               : AdbDebuggingHandler.MESSAGE_ADB_DISABLED);
         } else if (transportType == AdbTransportType.WIFI) {
-            // TODO(joshuaduong): Not implemented
+            mHandler.sendEmptyMessage(enabled ? AdbDebuggingHandler.MSG_ADBDWIFI_ENABLE
+                                              : AdbDebuggingHandler.MSG_ADBDWIFI_DISABLE);
         } else {
             throw new IllegalArgumentException(
                     "setAdbEnabled called with unimplemented transport type=" + transportType);
@@ -762,6 +1639,87 @@
     }
 
     /**
+     * Allows wireless debugging on the network identified by {@code bssid} either once
+     * or always if {@code alwaysAllow} is {@code true}.
+     */
+    public void allowWirelessDebugging(boolean alwaysAllow, String bssid) {
+        Message msg = mHandler.obtainMessage(AdbDebuggingHandler.MSG_ADBWIFI_ALLOW);
+        msg.arg1 = alwaysAllow ? 1 : 0;
+        msg.obj = bssid;
+        mHandler.sendMessage(msg);
+    }
+
+    /**
+     * Denies wireless debugging connection on the last requested network.
+     */
+    public void denyWirelessDebugging() {
+        mHandler.sendEmptyMessage(AdbDebuggingHandler.MSG_ADBWIFI_DENY);
+    }
+
+    /**
+     * Returns the port adbwifi is currently opened on.
+     */
+    public int getAdbWirelessPort() {
+        AdbConnectionInfo info = getAdbConnectionInfo();
+        if (info == null) {
+            return 0;
+        }
+        return info.getPort();
+    }
+
+    /**
+     * Returns the list of paired devices.
+     */
+    public Map<String, PairDevice> getPairedDevices() {
+        AdbKeyStore keystore = new AdbKeyStore();
+        return keystore.getPairedDevices();
+    }
+
+    /**
+     * Unpair with device
+     */
+    public void unpairDevice(String fingerprint) {
+        Message message = Message.obtain(mHandler,
+                                         AdbDebuggingHandler.MSG_REQ_UNPAIR,
+                                         fingerprint);
+        mHandler.sendMessage(message);
+    }
+
+    /**
+     * Enable pairing by pairing code
+     */
+    public void enablePairingByPairingCode() {
+        mHandler.sendEmptyMessage(AdbDebuggingHandler.MSG_PAIR_PAIRING_CODE);
+    }
+
+    /**
+     * Enable pairing by pairing code
+     */
+    public void enablePairingByQrCode(String serviceName, String password) {
+        Bundle bundle = new Bundle();
+        bundle.putString("serviceName", serviceName);
+        bundle.putString("password", password);
+        Message message = Message.obtain(mHandler,
+                                         AdbDebuggingHandler.MSG_PAIR_QR_CODE,
+                                         bundle);
+        mHandler.sendMessage(message);
+    }
+
+    /**
+     * Disables pairing
+     */
+    public void disablePairing() {
+        mHandler.sendEmptyMessage(AdbDebuggingHandler.MSG_PAIRING_CANCEL);
+    }
+
+    /**
+     * Status enabled/disabled check
+     */
+    public boolean isAdbWifiEnabled() {
+        return mAdbWifiEnabled;
+    }
+
+    /**
      * Sends a message to the handler to persist the keystore.
      */
     private void sendPersistKeyStoreMessage() {
@@ -814,9 +1772,19 @@
         private File mKeyFile;
         private AtomicFile mAtomicKeyFile;
 
+        private List<String> mTrustedNetworks;
+        private static final int KEYSTORE_VERSION = 1;
+        private static final int MAX_SUPPORTED_KEYSTORE_VERSION = 1;
+        private static final String XML_KEYSTORE_START_TAG = "keyStore";
+        private static final String XML_ATTRIBUTE_VERSION = "version";
         private static final String XML_TAG_ADB_KEY = "adbKey";
         private static final String XML_ATTRIBUTE_KEY = "key";
         private static final String XML_ATTRIBUTE_LAST_CONNECTION = "lastConnection";
+        // A list of trusted networks a device can always wirelessly debug on (always allow).
+        // TODO: Move trusted networks list into a different file?
+        private static final String XML_TAG_WIFI_ACCESS_POINT = "wifiAP";
+        private static final String XML_ATTRIBUTE_WIFI_BSSID = "bssid";
+
         private static final String SYSTEM_KEY_FILE = "/adb_keys";
 
         /**
@@ -843,10 +1811,48 @@
         private void init() {
             initKeyFile();
             mKeyMap = getKeyMap();
+            mTrustedNetworks = getTrustedNetworks();
             mSystemKeys = getSystemKeysFromFile(SYSTEM_KEY_FILE);
             addUserKeysToKeyStore();
         }
 
+        public void addTrustedNetwork(String bssid) {
+            mTrustedNetworks.add(bssid);
+            sendPersistKeyStoreMessage();
+        }
+
+        public Map<String, PairDevice> getPairedDevices() {
+            Map<String, PairDevice> pairedDevices = new HashMap<String, PairDevice>();
+            for (Map.Entry<String, Long> keyEntry : mKeyMap.entrySet()) {
+                String fingerprints = getFingerprints(keyEntry.getKey());
+                String hostname = "nouser@nohostname";
+                String[] args = keyEntry.getKey().split("\\s+");
+                if (args.length > 1) {
+                    hostname = args[1];
+                }
+                pairedDevices.put(keyEntry.getKey(), new PairDevice(
+                        hostname, fingerprints, mWifiConnectedKeys.contains(keyEntry.getKey())));
+            }
+            return pairedDevices;
+        }
+
+        public String findKeyFromFingerprint(String fingerprint) {
+            for (Map.Entry<String, Long> entry : mKeyMap.entrySet()) {
+                String f = getFingerprints(entry.getKey());
+                if (fingerprint.equals(f)) {
+                    return entry.getKey();
+                }
+            }
+            return null;
+        }
+
+        public void removeKey(String key) {
+            if (mKeyMap.containsKey(key)) {
+                mKeyMap.remove(key);
+                sendPersistKeyStoreMessage();
+            }
+        }
+
         /**
          * Initializes the key file that will be used to persist the adb grants.
          */
@@ -916,6 +1922,78 @@
             try (FileInputStream keyStream = mAtomicKeyFile.openRead()) {
                 XmlPullParser parser = Xml.newPullParser();
                 parser.setInput(keyStream, StandardCharsets.UTF_8.name());
+                // Check for supported keystore version.
+                XmlUtils.beginDocument(parser, XML_KEYSTORE_START_TAG);
+                if (parser.next() != XmlPullParser.END_DOCUMENT) {
+                    String tagName = parser.getName();
+                    if (tagName == null || !XML_KEYSTORE_START_TAG.equals(tagName)) {
+                        Slog.e(TAG, "Expected " + XML_KEYSTORE_START_TAG + ", but got tag="
+                                + tagName);
+                        return keyMap;
+                    }
+                    int keystoreVersion = Integer.parseInt(
+                            parser.getAttributeValue(null, XML_ATTRIBUTE_VERSION));
+                    if (keystoreVersion > MAX_SUPPORTED_KEYSTORE_VERSION) {
+                        Slog.e(TAG, "Keystore version=" + keystoreVersion
+                                + " not supported (max_supported="
+                                + MAX_SUPPORTED_KEYSTORE_VERSION + ")");
+                        return keyMap;
+                    }
+                }
+                while (parser.next() != XmlPullParser.END_DOCUMENT) {
+                    String tagName = parser.getName();
+                    if (tagName == null) {
+                        break;
+                    } else if (!tagName.equals(XML_TAG_ADB_KEY)) {
+                        XmlUtils.skipCurrentTag(parser);
+                        continue;
+                    }
+                    String key = parser.getAttributeValue(null, XML_ATTRIBUTE_KEY);
+                    long connectionTime;
+                    try {
+                        connectionTime = Long.valueOf(
+                                parser.getAttributeValue(null, XML_ATTRIBUTE_LAST_CONNECTION));
+                    } catch (NumberFormatException e) {
+                        Slog.e(TAG,
+                                "Caught a NumberFormatException parsing the last connection time: "
+                                        + e);
+                        XmlUtils.skipCurrentTag(parser);
+                        continue;
+                    }
+                    keyMap.put(key, connectionTime);
+                }
+            } catch (IOException e) {
+                Slog.e(TAG, "Caught an IOException parsing the XML key file: ", e);
+            } catch (XmlPullParserException e) {
+                Slog.w(TAG, "Caught XmlPullParserException parsing the XML key file: ", e);
+                // The file could be written in a format prior to introducing keystore tag.
+                return getKeyMapBeforeKeystoreVersion();
+            }
+            return keyMap;
+        }
+
+
+        /**
+         * Returns the key map with the keys and last connection times from the key file.
+         * This implementation was prior to adding the XML_KEYSTORE_START_TAG.
+         */
+        private Map<String, Long> getKeyMapBeforeKeystoreVersion() {
+            Map<String, Long> keyMap = new HashMap<String, Long>();
+            // if the AtomicFile could not be instantiated before attempt again; if it still fails
+            // return an empty key map.
+            if (mAtomicKeyFile == null) {
+                initKeyFile();
+                if (mAtomicKeyFile == null) {
+                    Slog.e(TAG, "Unable to obtain the key file, " + mKeyFile + ", for reading");
+                    return keyMap;
+                }
+            }
+            if (!mAtomicKeyFile.exists()) {
+                return keyMap;
+            }
+            try (FileInputStream keyStream = mAtomicKeyFile.openRead()) {
+                XmlPullParser parser = Xml.newPullParser();
+                parser.setInput(keyStream, StandardCharsets.UTF_8.name());
                 XmlUtils.beginDocument(parser, XML_TAG_ADB_KEY);
                 while (parser.next() != XmlPullParser.END_DOCUMENT) {
                     String tagName = parser.getName();
@@ -946,6 +2024,63 @@
         }
 
         /**
+         * Returns the map of trusted networks from the keystore file.
+         *
+         * This was implemented in keystore version 1.
+         */
+        private List<String> getTrustedNetworks() {
+            List<String> trustedNetworks = new ArrayList<String>();
+            // if the AtomicFile could not be instantiated before attempt again; if it still fails
+            // return an empty key map.
+            if (mAtomicKeyFile == null) {
+                initKeyFile();
+                if (mAtomicKeyFile == null) {
+                    Slog.e(TAG, "Unable to obtain the key file, " + mKeyFile + ", for reading");
+                    return trustedNetworks;
+                }
+            }
+            if (!mAtomicKeyFile.exists()) {
+                return trustedNetworks;
+            }
+            try (FileInputStream keyStream = mAtomicKeyFile.openRead()) {
+                XmlPullParser parser = Xml.newPullParser();
+                parser.setInput(keyStream, StandardCharsets.UTF_8.name());
+                // Check for supported keystore version.
+                XmlUtils.beginDocument(parser, XML_KEYSTORE_START_TAG);
+                if (parser.next() != XmlPullParser.END_DOCUMENT) {
+                    String tagName = parser.getName();
+                    if (tagName == null || !XML_KEYSTORE_START_TAG.equals(tagName)) {
+                        Slog.e(TAG, "Expected " + XML_KEYSTORE_START_TAG + ", but got tag="
+                                + tagName);
+                        return trustedNetworks;
+                    }
+                    int keystoreVersion = Integer.parseInt(
+                            parser.getAttributeValue(null, XML_ATTRIBUTE_VERSION));
+                    if (keystoreVersion > MAX_SUPPORTED_KEYSTORE_VERSION) {
+                        Slog.e(TAG, "Keystore version=" + keystoreVersion
+                                + " not supported (max_supported="
+                                + MAX_SUPPORTED_KEYSTORE_VERSION);
+                        return trustedNetworks;
+                    }
+                }
+                while (parser.next() != XmlPullParser.END_DOCUMENT) {
+                    String tagName = parser.getName();
+                    if (tagName == null) {
+                        break;
+                    } else if (!tagName.equals(XML_TAG_WIFI_ACCESS_POINT)) {
+                        XmlUtils.skipCurrentTag(parser);
+                        continue;
+                    }
+                    String bssid = parser.getAttributeValue(null, XML_ATTRIBUTE_WIFI_BSSID);
+                    trustedNetworks.add(bssid);
+                }
+            } catch (IOException | XmlPullParserException | NumberFormatException e) {
+                Slog.e(TAG, "Caught an exception parsing the XML key file: ", e);
+            }
+            return trustedNetworks;
+        }
+
+        /**
          * Updates the keystore with keys that were previously set to be always allowed before the
          * connection time of keys was tracked.
          */
@@ -981,7 +2116,7 @@
             // if there is nothing in the key map then ensure any keys left in the keystore files
             // are deleted as well.
             filterOutOldKeys();
-            if (mKeyMap.isEmpty()) {
+            if (mKeyMap.isEmpty() && mTrustedNetworks.isEmpty()) {
                 deleteKeyStore();
                 return;
             }
@@ -999,6 +2134,8 @@
                 serializer.setOutput(keyStream, StandardCharsets.UTF_8.name());
                 serializer.startDocument(null, true);
 
+                serializer.startTag(null, XML_KEYSTORE_START_TAG);
+                serializer.attribute(null, XML_ATTRIBUTE_VERSION, String.valueOf(KEYSTORE_VERSION));
                 for (Map.Entry<String, Long> keyEntry : mKeyMap.entrySet()) {
                     serializer.startTag(null, XML_TAG_ADB_KEY);
                     serializer.attribute(null, XML_ATTRIBUTE_KEY, keyEntry.getKey());
@@ -1006,7 +2143,12 @@
                             String.valueOf(keyEntry.getValue()));
                     serializer.endTag(null, XML_TAG_ADB_KEY);
                 }
-
+                for (String bssid : mTrustedNetworks) {
+                    serializer.startTag(null, XML_TAG_WIFI_ACCESS_POINT);
+                    serializer.attribute(null, XML_ATTRIBUTE_WIFI_BSSID, bssid);
+                    serializer.endTag(null, XML_TAG_WIFI_ACCESS_POINT);
+                }
+                serializer.endTag(null, XML_KEYSTORE_START_TAG);
                 serializer.endDocument();
                 mAtomicKeyFile.finishWrite(keyStream);
             } catch (IOException e) {
@@ -1067,6 +2209,7 @@
          */
         public void deleteKeyStore() {
             mKeyMap.clear();
+            mTrustedNetworks.clear();
             deleteKeyFile();
             if (mAtomicKeyFile == null) {
                 return;
@@ -1148,5 +2291,14 @@
                 return false;
             }
         }
+
+        /**
+         * Returns whether the specified bssid is in the list of trusted networks. This requires
+         * that the user previously allowed wireless debugging on this network and selected the
+         * option to 'Always allow'.
+         */
+        public boolean isTrustedNetwork(String bssid) {
+            return mTrustedNetworks.contains(bssid);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/adb/AdbService.java b/services/core/java/com/android/server/adb/AdbService.java
index f2a8615..7ccb284 100644
--- a/services/core/java/com/android/server/adb/AdbService.java
+++ b/services/core/java/com/android/server/adb/AdbService.java
@@ -21,8 +21,10 @@
 import android.annotation.UserIdInt;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.database.ContentObserver;
+import android.debug.AdbManager;
 import android.debug.AdbManagerInternal;
 import android.debug.AdbTransportType;
 import android.debug.IAdbManager;
@@ -34,6 +36,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.SystemProperties;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.service.adb.AdbServiceDumpProto;
 import android.sysprop.AdbProperties;
@@ -44,6 +47,7 @@
 
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
 import com.android.internal.util.dump.DualDumpOutputStream;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
@@ -54,6 +58,7 @@
 import java.io.PrintWriter;
 import java.util.Collections;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * The Android Debug Bridge (ADB) service. This controls the availability of ADB and authorization
@@ -61,6 +66,27 @@
  */
 public class AdbService extends IAdbManager.Stub {
     /**
+     * Adb native daemon.
+     */
+    static final String ADBD = "adbd";
+
+    /**
+     * Command to start native service.
+     */
+    static final String CTL_START = "ctl.start";
+
+    /**
+     * Command to start native service.
+     */
+    static final String CTL_STOP = "ctl.stop";
+
+    // The tcp port adb is currently using
+    AtomicInteger mConnectionPort = new AtomicInteger(-1);
+
+    private final AdbConnectionPortListener mPortListener = new AdbConnectionPortListener();
+    private AdbDebuggingManager.AdbConnectionPortPoller mConnectionPortPoller;
+
+    /**
      * Manages the service lifecycle for {@code AdbService} in {@code SystemServer}.
      */
     public static class Lifecycle extends SystemService {
@@ -129,14 +155,17 @@
             mIsAdbUsbEnabled = containsFunction(
                     SystemProperties.get(USB_PERSISTENT_CONFIG_PROPERTY, ""),
                     UsbManager.USB_FUNCTION_ADB);
-            // TODO(joshuaduong): Read the adb wifi state from a persistent system
-            // property (persist.sys.adb.wifi).
-            mIsAdbWifiEnabled = false;
+            mIsAdbWifiEnabled = "1".equals(
+                    SystemProperties.get(WIFI_PERSISTENT_CONFIG_PROPERTY, "0"));
 
             // register observer to listen for settings changes
+            mObserver = new AdbSettingsObserver();
             mContentResolver.registerContentObserver(
                     Settings.Global.getUriFor(Settings.Global.ADB_ENABLED),
-                    false, new AdbSettingsObserver());
+                    false, mObserver);
+            mContentResolver.registerContentObserver(
+                    Settings.Global.getUriFor(Settings.Global.ADB_WIFI_ENABLED),
+                    false, mObserver);
         } catch (Exception e) {
             Slog.e(TAG, "Error in initAdbState", e);
         }
@@ -153,6 +182,7 @@
 
     private class AdbSettingsObserver extends ContentObserver {
         private final Uri mAdbUsbUri = Settings.Global.getUriFor(Settings.Global.ADB_ENABLED);
+        private final Uri mAdbWifiUri = Settings.Global.getUriFor(Settings.Global.ADB_WIFI_ENABLED);
 
         AdbSettingsObserver() {
             super(null);
@@ -166,8 +196,13 @@
                 FgThread.getHandler().sendMessage(obtainMessage(
                         AdbService::setAdbEnabled, AdbService.this, shouldEnable,
                             AdbTransportType.USB));
+            } else if (mAdbWifiUri.equals(uri)) {
+                boolean shouldEnable = (Settings.Global.getInt(mContentResolver,
+                        Settings.Global.ADB_WIFI_ENABLED, 0) > 0);
+                FgThread.getHandler().sendMessage(obtainMessage(
+                        AdbService::setAdbEnabled, AdbService.this, shouldEnable,
+                            AdbTransportType.WIFI));
             }
-            // TODO(joshuaduong): Add condition for WIFI transport
         }
     }
 
@@ -179,6 +214,7 @@
      * May also contain vendor-specific default functions for testing purposes.
      */
     private static final String USB_PERSISTENT_CONFIG_PROPERTY = "persist.sys.usb.config";
+    private static final String WIFI_PERSISTENT_CONFIG_PROPERTY = "persist.adb.tls_server.enable";
 
     private final Context mContext;
     private final ContentResolver mContentResolver;
@@ -188,6 +224,8 @@
     private boolean mIsAdbWifiEnabled;
     private AdbDebuggingManager mDebuggingManager;
 
+    private ContentObserver mObserver;
+
     private AdbService(Context context) {
         mContext = context;
         mContentResolver = context.getContentResolver();
@@ -213,6 +251,8 @@
         try {
             Settings.Global.putInt(mContentResolver,
                     Settings.Global.ADB_ENABLED, mIsAdbUsbEnabled ? 1 : 0);
+            Settings.Global.putInt(mContentResolver,
+                    Settings.Global.ADB_WIFI_ENABLED, mIsAdbWifiEnabled ? 1 : 0);
         } catch (SecurityException e) {
             // If UserManager.DISALLOW_DEBUGGING_FEATURES is on, that this setting can't be changed.
             Slog.d(TAG, "ADB_ENABLED is restricted.");
@@ -231,8 +271,9 @@
     }
 
     @Override
-    public void allowDebugging(boolean alwaysAllow, String publicKey) {
+    public void allowDebugging(boolean alwaysAllow, @NonNull String publicKey) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
+        Preconditions.checkStringNotEmpty(publicKey);
         if (mDebuggingManager != null) {
             mDebuggingManager.allowDebugging(alwaysAllow, publicKey);
         }
@@ -282,53 +323,118 @@
     }
 
     @Override
-    public void allowWirelessDebugging(boolean alwaysAllow, String bssid) {
+    public void allowWirelessDebugging(boolean alwaysAllow, @NonNull String bssid) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
-        // TODO(joshuaduong): NOT IMPLEMENTED
+        Preconditions.checkStringNotEmpty(bssid);
+        if (mDebuggingManager != null) {
+            mDebuggingManager.allowWirelessDebugging(alwaysAllow, bssid);
+        }
     }
 
     @Override
     public void denyWirelessDebugging() {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
-        // TODO(joshuaduong): NOT IMPLEMENTED
+        if (mDebuggingManager != null) {
+            mDebuggingManager.denyWirelessDebugging();
+        }
     }
 
     @Override
     public Map<String, PairDevice> getPairedDevices() {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
-        // TODO(joshuaduong): NOT IMPLEMENTED
+        if (mDebuggingManager != null) {
+            return mDebuggingManager.getPairedDevices();
+        }
         return null;
     }
 
     @Override
-    public void unpairDevice(String fingerprint) {
+    public void unpairDevice(@NonNull String fingerprint) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
-        // TODO(joshuaduong): NOT IMPLEMENTED
+        Preconditions.checkStringNotEmpty(fingerprint);
+        if (mDebuggingManager != null) {
+            mDebuggingManager.unpairDevice(fingerprint);
+        }
     }
 
     @Override
     public void enablePairingByPairingCode() {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
-        // TODO(joshuaduong): NOT IMPLEMENTED
+        if (mDebuggingManager != null) {
+            mDebuggingManager.enablePairingByPairingCode();
+        }
     }
 
     @Override
-    public void enablePairingByQrCode(String serviceName, String password) {
+    public void enablePairingByQrCode(@NonNull String serviceName, @NonNull String password) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
-        // TODO(joshuaduong): NOT IMPLEMENTED
+        Preconditions.checkStringNotEmpty(serviceName);
+        Preconditions.checkStringNotEmpty(password);
+        if (mDebuggingManager != null) {
+            mDebuggingManager.enablePairingByQrCode(serviceName, password);
+        }
     }
 
     @Override
     public void disablePairing() {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
-        // TODO(joshuaduong): NOT IMPLEMENTED
+        if (mDebuggingManager != null) {
+            mDebuggingManager.disablePairing();
+        }
     }
 
     @Override
     public int getAdbWirelessPort() {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
-        // TODO(joshuaduong): NOT IMPLEMENTED
-        return 0;
+        if (mDebuggingManager != null) {
+            return mDebuggingManager.getAdbWirelessPort();
+        }
+        // If ro.adb.secure=0
+        return mConnectionPort.get();
+    }
+
+    /**
+     * This listener is only used when ro.adb.secure=0. Otherwise, AdbDebuggingManager will
+     * do this.
+     */
+    class AdbConnectionPortListener implements AdbDebuggingManager.AdbConnectionPortListener {
+        public void onPortReceived(int port) {
+            if (port > 0 && port <= 65535) {
+                mConnectionPort.set(port);
+            } else {
+                mConnectionPort.set(-1);
+                // Turn off wifi debugging, since the server did not start.
+                try {
+                    Settings.Global.putInt(mContentResolver,
+                            Settings.Global.ADB_WIFI_ENABLED, 0);
+                } catch (SecurityException e) {
+                    // If UserManager.DISALLOW_DEBUGGING_FEATURES is on, that this setting can't
+                    // be changed.
+                    Slog.d(TAG, "ADB_ENABLED is restricted.");
+                }
+            }
+            broadcastPortInfo(mConnectionPort.get());
+        }
+    }
+
+    private void broadcastPortInfo(int port) {
+        Intent intent = new Intent(AdbManager.WIRELESS_DEBUG_STATE_CHANGED_ACTION);
+        intent.putExtra(AdbManager.WIRELESS_STATUS_EXTRA, (port >= 0)
+                ? AdbManager.WIRELESS_STATUS_CONNECTED
+                : AdbManager.WIRELESS_STATUS_DISCONNECTED);
+        intent.putExtra(AdbManager.WIRELESS_DEBUG_PORT_EXTRA, port);
+        mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+        Slog.i(TAG, "sent port broadcast port=" + port);
+    }
+
+    private void startAdbd() {
+        SystemProperties.set(CTL_START, ADBD);
+    }
+
+    private void stopAdbd() {
+        if (!mIsAdbUsbEnabled && !mIsAdbWifiEnabled) {
+            SystemProperties.set(CTL_STOP, ADBD);
+        }
     }
 
     private void setAdbEnabled(boolean enable, byte transportType) {
@@ -342,11 +448,33 @@
             mIsAdbUsbEnabled = enable;
         } else if (transportType == AdbTransportType.WIFI && enable != mIsAdbWifiEnabled) {
             mIsAdbWifiEnabled = enable;
+            if (mIsAdbWifiEnabled) {
+                if (!AdbProperties.secure().orElse(false) && mDebuggingManager == null) {
+                    // Start adbd. If this is secure adb, then we defer enabling adb over WiFi.
+                    SystemProperties.set(WIFI_PERSISTENT_CONFIG_PROPERTY, "1");
+                    mConnectionPortPoller =
+                            new AdbDebuggingManager.AdbConnectionPortPoller(mPortListener);
+                    mConnectionPortPoller.start();
+                }
+            } else {
+                // Stop adb over WiFi.
+                SystemProperties.set(WIFI_PERSISTENT_CONFIG_PROPERTY, "0");
+                if (mConnectionPortPoller != null) {
+                    mConnectionPortPoller.cancelAndWait();
+                    mConnectionPortPoller = null;
+                }
+            }
         } else {
             // No change
             return;
         }
 
+        if (enable) {
+            startAdbd();
+        } else {
+            stopAdbd();
+        }
+
         for (IAdbTransport transport : mTransports.values()) {
             try {
                 transport.onAdbEnabled(enable, transportType);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2af04ae..53d2384 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -275,6 +275,7 @@
 import android.provider.DeviceConfig.Properties;
 import android.provider.Settings;
 import android.server.ServerProtoEnums;
+import android.sysprop.InitProperties;
 import android.sysprop.VoldProperties;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
@@ -346,6 +347,7 @@
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
 import com.android.server.ThreadPriorityBooster;
+import com.android.server.UserspaceRebootLogger;
 import com.android.server.Watchdog;
 import com.android.server.am.ActivityManagerServiceDumpProcessesProto.UidObserverRegistrationProto;
 import com.android.server.appop.AppOpsService;
@@ -2253,6 +2255,20 @@
         }
     }
 
+    private void maybeLogUserspaceRebootEvent() {
+        if (!UserspaceRebootLogger.shouldLogUserspaceRebootEvent()) {
+            return;
+        }
+        final int userId = mUserController.getCurrentUserId();
+        if (userId != UserHandle.USER_SYSTEM) {
+            // Only log for user0.
+            return;
+        }
+        // TODO(b/148767783): should we check all profiles under user0?
+        UserspaceRebootLogger.logEventAsync(StorageManager.isUserKeyUnlocked(userId),
+                BackgroundThread.getExecutor());
+    }
+
     /**
      * Encapsulates global settings related to hidden API enforcement behaviour, including tracking
      * the latest value via a content observer.
@@ -5306,6 +5322,12 @@
             // Start looking for apps that are abusing wake locks.
             Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_POWER_USE_MSG);
             mHandler.sendMessageDelayed(nmsg, mConstants.POWER_CHECK_INTERVAL);
+            // Check if we are performing userspace reboot before setting sys.boot_completed to
+            // avoid race with init reseting sys.init.userspace_reboot.in_progress once sys
+            // .boot_completed is 1.
+            if (InitProperties.userspace_reboot_in_progress().orElse(false)) {
+                UserspaceRebootLogger.noteUserspaceRebootSuccess();
+            }
             // Tell anyone interested that we are done booting!
             SystemProperties.set("sys.boot_completed", "1");
 
@@ -5326,6 +5348,7 @@
                             }
                         }
                     });
+            maybeLogUserspaceRebootEvent();
             mUserController.scheduleStartProfiles();
         }
 
diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index cc5b766..f15d999 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -16,7 +16,6 @@
 
 package com.android.server.compat;
 
-import android.app.compat.ChangeIdStateCache;
 import android.compat.Compatibility.ChangeConfig;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -79,7 +78,6 @@
     void addChange(CompatChange change) {
         synchronized (mChanges) {
             mChanges.put(change.getId(), change);
-            invalidateCache();
         }
     }
 
@@ -172,7 +170,6 @@
                 addChange(c);
             }
             c.addPackageOverride(packageName, enabled);
-            invalidateCache();
         }
         return alreadyKnown;
     }
@@ -229,7 +226,6 @@
                 // Should never occur, since validator is in the same process.
                 throw new RuntimeException("Unable to call override validator!", e);
             }
-            invalidateCache();
         }
         return overrideExists;
     }
@@ -252,7 +248,6 @@
                 addOverride(changeId, packageName, false);
 
             }
-            invalidateCache();
         }
     }
 
@@ -282,7 +277,6 @@
                     throw new RuntimeException("Unable to call override validator!", e);
                 }
             }
-            invalidateCache();
         }
     }
 
@@ -402,8 +396,4 @@
     IOverrideValidator getOverrideValidator() {
         return mOverrideValidator;
     }
-
-    private void invalidateCache() {
-        ChangeIdStateCache.invalidate();
-    }
 }
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 8692436..3c21d1a 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -216,14 +216,14 @@
      * Whether to keep the connection active after rebooting, or upgrading or reinstalling. This
      * only applies to {@link VpnService} connections.
      */
-    private boolean mAlwaysOn = false;
+    @VisibleForTesting protected boolean mAlwaysOn = false;
 
     /**
      * Whether to disable traffic outside of this VPN even when the VPN is not connected. System
      * apps can still bypass by choosing explicit networks. Has no effect if {@link mAlwaysOn} is
-     * not set.
+     * not set. Applies to all types of VPNs.
      */
-    private boolean mLockdown = false;
+    @VisibleForTesting protected boolean mLockdown = false;
 
     /**
      * Set of packages in addition to the VPN app itself that can access the network directly when
@@ -252,14 +252,14 @@
     private final int mUserHandle;
 
     public Vpn(Looper looper, Context context, INetworkManagementService netService,
-            @UserIdInt int userHandle) {
-        this(looper, context, netService, userHandle,
+            @UserIdInt int userHandle, @NonNull KeyStore keyStore) {
+        this(looper, context, netService, userHandle, keyStore,
                 new SystemServices(context), new Ikev2SessionCreator());
     }
 
     @VisibleForTesting
     protected Vpn(Looper looper, Context context, INetworkManagementService netService,
-            int userHandle, SystemServices systemServices,
+            int userHandle, @NonNull KeyStore keyStore, SystemServices systemServices,
             Ikev2SessionCreator ikev2SessionCreator) {
         mContext = context;
         mNetd = netService;
@@ -285,7 +285,7 @@
         mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
         updateCapabilities(null /* defaultNetwork */);
 
-        loadAlwaysOnPackage();
+        loadAlwaysOnPackage(keyStore);
     }
 
     /**
@@ -437,23 +437,36 @@
     /**
      * Checks if a VPN app supports always-on mode.
      *
-     * In order to support the always-on feature, an app has to
+     * <p>In order to support the always-on feature, an app has to either have an installed
+     * PlatformVpnProfile, or:
+     *
      * <ul>
-     *     <li>target {@link VERSION_CODES#N API 24} or above, and
-     *     <li>not opt out through the {@link VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON}
-     *         meta-data field.
+     *   <li>target {@link VERSION_CODES#N API 24} or above, and
+     *   <li>not opt out through the {@link VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON}
+     *       meta-data field.
      * </ul>
      *
      * @param packageName the canonical package name of the VPN app
+     * @param keyStore the keystore instance to use for checking if the app has a Platform VPN
+     *     profile installed.
      * @return {@code true} if and only if the VPN app exists and supports always-on mode
      */
-    public boolean isAlwaysOnPackageSupported(String packageName) {
+    public boolean isAlwaysOnPackageSupported(String packageName, @NonNull KeyStore keyStore) {
         enforceSettingsPermission();
 
         if (packageName == null) {
             return false;
         }
 
+        final long oldId = Binder.clearCallingIdentity();
+        try {
+            if (getVpnProfilePrivileged(packageName, keyStore) != null) {
+                return true;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(oldId);
+        }
+
         PackageManager pm = mContext.getPackageManager();
         ApplicationInfo appInfo = null;
         try {
@@ -485,27 +498,31 @@
     }
 
     /**
-     * Configures an always-on VPN connection through a specific application.
-     * This connection is automatically granted and persisted after a reboot.
+     * Configures an always-on VPN connection through a specific application. This connection is
+     * automatically granted and persisted after a reboot.
      *
-     * <p>The designated package should exist and declare a {@link VpnService} in its
-     *    manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE},
-     *    otherwise the call will fail.
+     * <p>The designated package should either have a PlatformVpnProfile installed, or declare a
+     * {@link VpnService} in its manifest guarded by {@link
+     * android.Manifest.permission.BIND_VPN_SERVICE}, otherwise the call will fail.
      *
      * <p>Note that this method does not check if the VPN app supports always-on mode. The check is
-     *    delayed to {@link #startAlwaysOnVpn()}, which is always called immediately after this
-     *    method in {@link android.net.IConnectivityManager#setAlwaysOnVpnPackage}.
+     * delayed to {@link #startAlwaysOnVpn()}, which is always called immediately after this method
+     * in {@link android.net.IConnectivityManager#setAlwaysOnVpnPackage}.
      *
      * @param packageName the package to designate as always-on VPN supplier.
      * @param lockdown whether to prevent traffic outside of a VPN, for example while connecting.
      * @param lockdownWhitelist packages to be whitelisted from lockdown.
+     * @param keyStore the Keystore instance to use for checking of PlatformVpnProfile(s)
      * @return {@code true} if the package has been set as always-on, {@code false} otherwise.
      */
     public synchronized boolean setAlwaysOnPackage(
-            String packageName, boolean lockdown, List<String> lockdownWhitelist) {
+            @Nullable String packageName,
+            boolean lockdown,
+            @Nullable List<String> lockdownWhitelist,
+            @NonNull KeyStore keyStore) {
         enforceControlPermissionOrInternalCaller();
 
-        if (setAlwaysOnPackageInternal(packageName, lockdown, lockdownWhitelist)) {
+        if (setAlwaysOnPackageInternal(packageName, lockdown, lockdownWhitelist, keyStore)) {
             saveAlwaysOnPackage();
             return true;
         }
@@ -513,20 +530,22 @@
     }
 
     /**
-     * Configures an always-on VPN connection through a specific application, the same as
-     * {@link #setAlwaysOnPackage}.
+     * Configures an always-on VPN connection through a specific application, the same as {@link
+     * #setAlwaysOnPackage}.
      *
-     * Does not perform permission checks. Does not persist any of the changes to storage.
+     * <p>Does not perform permission checks. Does not persist any of the changes to storage.
      *
      * @param packageName the package to designate as always-on VPN supplier.
      * @param lockdown whether to prevent traffic outside of a VPN, for example while connecting.
      * @param lockdownWhitelist packages to be whitelisted from lockdown. This is only used if
-     *        {@code lockdown} is {@code true}. Packages must not contain commas.
+     *     {@code lockdown} is {@code true}. Packages must not contain commas.
+     * @param keyStore the system keystore instance to check for profiles
      * @return {@code true} if the package has been set as always-on, {@code false} otherwise.
      */
     @GuardedBy("this")
     private boolean setAlwaysOnPackageInternal(
-            String packageName, boolean lockdown, List<String> lockdownWhitelist) {
+            @Nullable String packageName, boolean lockdown,
+            @Nullable List<String> lockdownWhitelist, @NonNull KeyStore keyStore) {
         if (VpnConfig.LEGACY_VPN.equals(packageName)) {
             Log.w(TAG, "Not setting legacy VPN \"" + packageName + "\" as always-on.");
             return false;
@@ -542,11 +561,18 @@
         }
 
         if (packageName != null) {
-            // TODO: Give the minimum permission possible; if there is a Platform VPN profile, only
-            // grant ACTIVATE_PLATFORM_VPN.
-            // Pre-authorize new always-on VPN package. Grant the full ACTIVATE_VPN appop, allowing
-            // both VpnService and Platform VPNs.
-            if (!setPackageAuthorization(packageName, VpnManager.TYPE_VPN_SERVICE)) {
+            final VpnProfile profile;
+            final long oldId = Binder.clearCallingIdentity();
+            try {
+                profile = getVpnProfilePrivileged(packageName, keyStore);
+            } finally {
+                Binder.restoreCallingIdentity(oldId);
+            }
+
+            // Pre-authorize new always-on VPN package.
+            final int grantType =
+                    (profile == null) ? VpnManager.TYPE_VPN_SERVICE : VpnManager.TYPE_VPN_PLATFORM;
+            if (!setPackageAuthorization(packageName, grantType)) {
                 return false;
             }
             mAlwaysOn = true;
@@ -611,11 +637,9 @@
         }
     }
 
-    /**
-     * Load the always-on package and lockdown config from Settings.Secure
-     */
+    /** Load the always-on package and lockdown config from Settings. */
     @GuardedBy("this")
-    private void loadAlwaysOnPackage() {
+    private void loadAlwaysOnPackage(@NonNull KeyStore keyStore) {
         final long token = Binder.clearCallingIdentity();
         try {
             final String alwaysOnPackage = mSystemServices.settingsSecureGetStringForUser(
@@ -626,17 +650,21 @@
                     Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST, mUserHandle);
             final List<String> whitelistedPackages = TextUtils.isEmpty(whitelistString)
                     ? Collections.emptyList() : Arrays.asList(whitelistString.split(","));
-            setAlwaysOnPackageInternal(alwaysOnPackage, alwaysOnLockdown, whitelistedPackages);
+            setAlwaysOnPackageInternal(
+                    alwaysOnPackage, alwaysOnLockdown, whitelistedPackages, keyStore);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
     }
 
     /**
+     * Starts the currently selected always-on VPN
+     *
+     * @param keyStore the keyStore instance for looking up PlatformVpnProfile(s)
      * @return {@code true} if the service was started, the service was already connected, or there
-     *         was no always-on VPN to start. {@code false} otherwise.
+     *     was no always-on VPN to start. {@code false} otherwise.
      */
-    public boolean startAlwaysOnVpn() {
+    public boolean startAlwaysOnVpn(@NonNull KeyStore keyStore) {
         final String alwaysOnPackage;
         synchronized (this) {
             alwaysOnPackage = getAlwaysOnPackage();
@@ -645,8 +673,8 @@
                 return true;
             }
             // Remove always-on VPN if it's not supported.
-            if (!isAlwaysOnPackageSupported(alwaysOnPackage)) {
-                setAlwaysOnPackage(null, false, null);
+            if (!isAlwaysOnPackageSupported(alwaysOnPackage, keyStore)) {
+                setAlwaysOnPackage(null, false, null, keyStore);
                 return false;
             }
             // Skip if the service is already established. This isn't bulletproof: it's not bound
@@ -657,10 +685,25 @@
             }
         }
 
-        // Tell the OS that background services in this app need to be allowed for
-        // a short time, so we can bootstrap the VPN service.
         final long oldId = Binder.clearCallingIdentity();
         try {
+            // Prefer VPN profiles, if any exist.
+            VpnProfile profile = getVpnProfilePrivileged(alwaysOnPackage, keyStore);
+            if (profile != null) {
+                startVpnProfilePrivileged(profile, alwaysOnPackage,
+                        null /* keyStore for private key retrieval - unneeded */);
+
+                // If the above startVpnProfilePrivileged() call returns, the Ikev2VpnProfile was
+                // correctly parsed, and the VPN has started running in a different thread. The only
+                // other possibility is that the above call threw an exception, which will be
+                // caught below, and returns false (clearing the always-on VPN). Once started, the
+                // Platform VPN cannot permanently fail, and is resilient to temporary failures. It
+                // will continue retrying until shut down by the user, or always-on is toggled off.
+                return true;
+            }
+
+            // Tell the OS that background services in this app need to be allowed for
+            // a short time, so we can bootstrap the VPN service.
             DeviceIdleController.LocalService idleController =
                     LocalServices.getService(DeviceIdleController.LocalService.class);
             idleController.addPowerSaveTempWhitelistApp(Process.myUid(), alwaysOnPackage,
@@ -675,6 +718,9 @@
                 Log.e(TAG, "VpnService " + serviceIntent + " failed to start", e);
                 return false;
             }
+        } catch (Exception e) {
+            Log.e(TAG, "Error starting always-on VPN", e);
+            return false;
         } finally {
             Binder.restoreCallingIdentity(oldId);
         }
@@ -773,6 +819,7 @@
     }
 
     /** Prepare the VPN for the given package. Does not perform permission checks. */
+    @GuardedBy("this")
     private void prepareInternal(String newPackage) {
         long token = Binder.clearCallingIdentity();
         try {
@@ -1894,6 +1941,27 @@
         // Prepare arguments for racoon.
         String[] racoon = null;
         switch (profile.type) {
+            case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
+                // Secret key is still just the alias (not the actual private key). The private key
+                // is retrieved from the KeyStore during conversion of the VpnProfile to an
+                // Ikev2VpnProfile.
+                profile.ipsecSecret = Ikev2VpnProfile.PREFIX_KEYSTORE_ALIAS + privateKey;
+                profile.ipsecUserCert = userCert;
+                // Fallthrough
+            case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
+                profile.ipsecCaCert = caCert;
+
+                // Start VPN profile
+                startVpnProfilePrivileged(profile, VpnConfig.LEGACY_VPN, keyStore);
+                return;
+            case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
+                // Ikev2VpnProfiles expect a base64-encoded preshared key.
+                profile.ipsecSecret =
+                        Ikev2VpnProfile.encodeForIpsecSecret(profile.ipsecSecret.getBytes());
+
+                // Start VPN profile
+                startVpnProfilePrivileged(profile, VpnConfig.LEGACY_VPN, keyStore);
+                return;
             case VpnProfile.TYPE_L2TP_IPSEC_PSK:
                 racoon = new String[] {
                     iface, profile.server, "udppsk", profile.ipsecIdentifier,
@@ -2816,6 +2884,10 @@
         return isVpnProfilePreConsented(mContext, packageName);
     }
 
+    private boolean isCurrentIkev2VpnLocked(@NonNull String packageName) {
+        return isCurrentPreparedPackage(packageName) && mVpnRunner instanceof IkeV2VpnRunner;
+    }
+
     /**
      * Deletes an app-provisioned VPN profile.
      *
@@ -2832,6 +2904,17 @@
 
         Binder.withCleanCallingIdentity(
                 () -> {
+                    // If this profile is providing the current VPN, turn it off, disabling
+                    // always-on as well if enabled.
+                    if (isCurrentIkev2VpnLocked(packageName)) {
+                        if (mAlwaysOn) {
+                            // Will transitively call prepareInternal(VpnConfig.LEGACY_VPN).
+                            setAlwaysOnPackage(null, false, null, keyStore);
+                        } else {
+                            prepareInternal(VpnConfig.LEGACY_VPN);
+                        }
+                    }
+
                     keyStore.delete(getProfileNameForPackage(packageName), Process.SYSTEM_UID);
                 });
     }
@@ -2885,24 +2968,35 @@
                         throw new IllegalArgumentException("No profile found for " + packageName);
                     }
 
-                    startVpnProfilePrivileged(profile, packageName);
+                    startVpnProfilePrivileged(profile, packageName,
+                            null /* keyStore for private key retrieval - unneeded */);
                 });
     }
 
-    private void startVpnProfilePrivileged(
-            @NonNull VpnProfile profile, @NonNull String packageName) {
-        // Ensure that no other previous instance is running.
-        if (mVpnRunner != null) {
-            mVpnRunner.exit();
-            mVpnRunner = null;
-        }
+    private synchronized void startVpnProfilePrivileged(
+            @NonNull VpnProfile profile, @NonNull String packageName, @Nullable KeyStore keyStore) {
+        // Make sure VPN is prepared. This method can be called by user apps via startVpnProfile(),
+        // by the Setting app via startLegacyVpn(), or by ConnectivityService via
+        // startAlwaysOnVpn(), so this is the common place to prepare the VPN. This also has the
+        // nice property of ensuring there are no other VpnRunner instances running.
+        prepareInternal(packageName);
         updateState(DetailedState.CONNECTING, "startPlatformVpn");
 
         try {
             // Build basic config
             mConfig = new VpnConfig();
-            mConfig.user = packageName;
-            mConfig.isMetered = profile.isMetered;
+            if (VpnConfig.LEGACY_VPN.equals(packageName)) {
+                mConfig.legacy = true;
+                mConfig.session = profile.name;
+                mConfig.user = profile.key;
+
+                // TODO: Add support for configuring meteredness via Settings. Until then, use a
+                // safe default.
+                mConfig.isMetered = true;
+            } else {
+                mConfig.user = packageName;
+                mConfig.isMetered = profile.isMetered;
+            }
             mConfig.startTime = SystemClock.elapsedRealtime();
             mConfig.proxyInfo = profile.proxy;
 
@@ -2910,7 +3004,8 @@
                 case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
                 case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
                 case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
-                    mVpnRunner = new IkeV2VpnRunner(Ikev2VpnProfile.fromVpnProfile(profile));
+                    mVpnRunner =
+                            new IkeV2VpnRunner(Ikev2VpnProfile.fromVpnProfile(profile, keyStore));
                     mVpnRunner.start();
                     break;
                 default:
@@ -2942,11 +3037,9 @@
 
         // To stop the VPN profile, the caller must be the current prepared package and must be
         // running an Ikev2VpnProfile.
-        if (!isCurrentPreparedPackage(packageName) && mVpnRunner instanceof IkeV2VpnRunner) {
-            return;
+        if (isCurrentIkev2VpnLocked(packageName)) {
+            prepareInternal(VpnConfig.LEGACY_VPN);
         }
-
-        prepareInternal(VpnConfig.LEGACY_VPN);
     }
 
     /**
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 9a85f952..a122611 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -1092,10 +1092,11 @@
         HdmiDeviceInfo avr = getAvrDeviceInfo();
         if (avr != null
                 && (avrAddress == avr.getLogicalAddress())
-                && isConnectedToArcPort(avr.getPhysicalAddress())
-                && isDirectConnectAddress(avr.getPhysicalAddress())) {
+                && isConnectedToArcPort(avr.getPhysicalAddress())) {
             if (enabled) {
-                return isConnected(avr.getPortId()) && isArcFeatureEnabled(avr.getPortId());
+                return isConnected(avr.getPortId())
+                    && isArcFeatureEnabled(avr.getPortId())
+                    && isDirectConnectAddress(avr.getPhysicalAddress());
             } else {
                 return true;
             }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index d8f5dfb..709811e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -9764,8 +9764,8 @@
     }
 
     @Override
-    public void notifyDexLoad(String loadingPackageName, List<String> classLoaderNames,
-            List<String> classPaths, String loaderIsa) {
+    public void notifyDexLoad(String loadingPackageName, Map<String, String> classLoaderContextMap,
+            String loaderIsa) {
         int userId = UserHandle.getCallingUserId();
         ApplicationInfo ai = getApplicationInfo(loadingPackageName, /*flags*/ 0, userId);
         if (ai == null) {
@@ -9773,7 +9773,7 @@
                 + loadingPackageName + ", user=" + userId);
             return;
         }
-        mDexManager.notifyDexLoad(ai, classLoaderNames, classPaths, loaderIsa, userId);
+        mDexManager.notifyDexLoad(ai, classLoaderContextMap, loaderIsa, userId);
     }
 
     @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..98c6c6d 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -562,6 +562,9 @@
                             android.provider.Settings.Global.putStringForUser(cr,
                                     android.provider.Settings.Global.ADB_ENABLED, "0",
                                     userId);
+                            android.provider.Settings.Global.putStringForUser(cr,
+                                    android.provider.Settings.Global.ADB_WIFI_ENABLED, "0",
+                                    userId);
                         }
                     }
                     break;
@@ -702,6 +705,7 @@
                 break;
 
             case android.provider.Settings.Global.ADB_ENABLED:
+            case android.provider.Settings.Global.ADB_WIFI_ENABLED:
                 if ("0".equals(value)) {
                     return false;
                 }
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 9e86a4b..441fa75 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -44,6 +44,8 @@
 import com.android.server.pm.PackageManagerService;
 import com.android.server.pm.PackageManagerServiceUtils;
 
+import dalvik.system.VMRuntime;
+
 import java.io.File;
 import java.io.IOException;
 import java.util.Arrays;
@@ -143,22 +145,15 @@
      * return as fast as possible.
      *
      * @param loadingAppInfo the package performing the load
-     * @param classLoadersNames the names of the class loaders present in the loading chain. The
-     *    list encodes the class loader chain in the natural order. The first class loader has
-     *    the second one as its parent and so on. The dex files present in the class path of the
-     *    first class loader will be recorded in the usage file.
-     * @param classPaths the class paths corresponding to the class loaders names from
-     *     {@param classLoadersNames}. The the first element corresponds to the first class loader
-     *     and so on. A classpath is represented as a list of dex files separated by
-     *     {@code File.pathSeparator}, or null if the class loader's classpath is not known.
-     *     The dex files found in the first class path will be recorded in the usage file.
+     * @param classLoaderContextMap a map from file paths to dex files that have been loaded to
+     *     the class loader context that was used to load them.
      * @param loaderIsa the ISA of the app loading the dex files
      * @param loaderUserId the user id which runs the code loading the dex files
      */
-    public void notifyDexLoad(ApplicationInfo loadingAppInfo, List<String> classLoadersNames,
-            List<String> classPaths, String loaderIsa, int loaderUserId) {
+    public void notifyDexLoad(ApplicationInfo loadingAppInfo,
+            Map<String, String> classLoaderContextMap, String loaderIsa, int loaderUserId) {
         try {
-            notifyDexLoadInternal(loadingAppInfo, classLoadersNames, classPaths, loaderIsa,
+            notifyDexLoadInternal(loadingAppInfo, classLoaderContextMap, loaderIsa,
                     loaderUserId);
         } catch (Exception e) {
             Slog.w(TAG, "Exception while notifying dex load for package " +
@@ -168,46 +163,23 @@
 
     @VisibleForTesting
     /*package*/ void notifyDexLoadInternal(ApplicationInfo loadingAppInfo,
-            List<String> classLoaderNames, List<String> classPaths, String loaderIsa,
+            Map<String, String> classLoaderContextMap, String loaderIsa,
             int loaderUserId) {
-        if (classLoaderNames.size() != classPaths.size()) {
-            Slog.wtf(TAG, "Bad call to noitfyDexLoad: args have different size");
+        if (classLoaderContextMap == null) {
             return;
         }
-        if (classLoaderNames.isEmpty()) {
+        if (classLoaderContextMap.isEmpty()) {
             Slog.wtf(TAG, "Bad call to notifyDexLoad: class loaders list is empty");
             return;
         }
         if (!PackageManagerServiceUtils.checkISA(loaderIsa)) {
-            Slog.w(TAG, "Loading dex files " + classPaths + " in unsupported ISA: " +
-                    loaderIsa + "?");
+            Slog.w(TAG, "Loading dex files " + classLoaderContextMap.keySet()
+                    + " in unsupported ISA: " + loaderIsa + "?");
             return;
         }
 
-        // The first classpath should never be null because the first classloader
-        // should always be an instance of BaseDexClassLoader.
-        String firstClassPath = classPaths.get(0);
-        if (firstClassPath == null) {
-            return;
-        }
-        // The classpath is represented as a list of dex files separated by File.pathSeparator.
-        String[] dexPathsToRegister = firstClassPath.split(File.pathSeparator);
-
-        // Encode the class loader contexts for the dexPathsToRegister.
-        String[] classLoaderContexts = DexoptUtils.processContextForDexLoad(
-                classLoaderNames, classPaths);
-
-        // A null classLoaderContexts means that there are unsupported class loaders in the
-        // chain.
-        if (classLoaderContexts == null) {
-            if (DEBUG) {
-                Slog.i(TAG, loadingAppInfo.packageName +
-                        " uses unsupported class loader in " + classLoaderNames);
-            }
-        }
-
-        int dexPathIndex = 0;
-        for (String dexPath : dexPathsToRegister) {
+        for (Map.Entry<String, String> mapping : classLoaderContextMap.entrySet()) {
+            String dexPath = mapping.getKey();
             // Find the owning package name.
             DexSearchResult searchResult = getDexPackage(loadingAppInfo, dexPath, loaderUserId);
 
@@ -229,7 +201,6 @@
                     // If the dex file is the primary apk (or a split) and not isUsedByOtherApps
                     // do not record it. This case does not bring any new usable information
                     // and can be safely skipped.
-                    dexPathIndex++;
                     continue;
                 }
 
@@ -239,13 +210,13 @@
                             searchResult.mOwningPackageName, loadingAppInfo.packageName);
                 }
 
-                if (classLoaderContexts != null) {
-
+                String classLoaderContext = mapping.getValue();
+                if (classLoaderContext != null
+                        && VMRuntime.isValidClassLoaderContext(classLoaderContext)) {
                     // Record dex file usage. If the current usage is a new pattern (e.g. new
                     // secondary, or UsedByOtherApps), record will return true and we trigger an
                     // async write to disk to make sure we don't loose the data in case of a reboot.
 
-                    String classLoaderContext = classLoaderContexts[dexPathIndex];
                     if (mPackageDexUsage.record(searchResult.mOwningPackageName,
                             dexPath, loaderUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit,
                             loadingAppInfo.packageName, classLoaderContext)) {
@@ -259,7 +230,6 @@
                     Slog.i(TAG, "Could not find owning package for dex file: " + dexPath);
                 }
             }
-            dexPathIndex++;
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
index e68c238..08763e7 100644
--- a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
+++ b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
@@ -83,8 +83,9 @@
             "=UnknownClassLoaderContext=";
 
     // The marker used for unsupported class loader contexts (no longer written, may occur in old
-    // files so discarded on read).
-    private static final String UNSUPPORTED_CLASS_LOADER_CONTEXT =
+    // files so discarded on read). Note: this matches
+    // ClassLoaderContext::kUnsupportedClassLoaderContextEncoding in the runtime.
+    /*package*/ static final String UNSUPPORTED_CLASS_LOADER_CONTEXT =
             "=UnsupportedClassLoaderContext=";
 
     /**
@@ -133,6 +134,9 @@
         if (classLoaderContext == null) {
             throw new IllegalArgumentException("Null classLoaderContext");
         }
+        if (classLoaderContext.equals(UNSUPPORTED_CLASS_LOADER_CONTEXT)) {
+            return false;
+        }
 
         synchronized (mPackageUseInfoMap) {
             PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(owningPackageName);
@@ -843,10 +847,11 @@
             boolean updateLoadingPackages = mLoadingPackages.addAll(dexUseInfo.mLoadingPackages);
 
             String oldClassLoaderContext = mClassLoaderContext;
-            if (UNKNOWN_CLASS_LOADER_CONTEXT.equals(mClassLoaderContext)) {
+            if (isUnknownOrUnsupportedContext(mClassLoaderContext)) {
                 // Can happen if we read a previous version.
                 mClassLoaderContext = dexUseInfo.mClassLoaderContext;
-            } else if (!Objects.equals(mClassLoaderContext, dexUseInfo.mClassLoaderContext)) {
+            } else if (!isUnknownOrUnsupportedContext(dexUseInfo.mClassLoaderContext)
+                        && !Objects.equals(mClassLoaderContext, dexUseInfo.mClassLoaderContext)) {
                 // We detected a context change.
                 mClassLoaderContext = VARIABLE_CLASS_LOADER_CONTEXT;
             }
@@ -857,6 +862,13 @@
                     || !Objects.equals(oldClassLoaderContext, mClassLoaderContext);
         }
 
+        private static boolean isUnknownOrUnsupportedContext(String context) {
+            // TODO: Merge UNKNOWN_CLASS_LOADER_CONTEXT & UNSUPPORTED_CLASS_LOADER_CONTEXT cases
+            // into UNSUPPORTED_CLASS_LOADER_CONTEXT.
+            return UNKNOWN_CLASS_LOADER_CONTEXT.equals(context)
+                    || UNSUPPORTED_CLASS_LOADER_CONTEXT.equals(context);
+        }
+
         public boolean isUsedByOtherApps() {
             return mIsUsedByOtherApps;
         }
@@ -878,7 +890,7 @@
         public boolean isUnknownClassLoaderContext() {
             // The class loader context may be unknown if we loaded the data from a previous version
             // which didn't save the context.
-            return UNKNOWN_CLASS_LOADER_CONTEXT.equals(mClassLoaderContext);
+            return isUnknownOrUnsupportedContext(mClassLoaderContext);
         }
 
         public boolean isVariableClassLoaderContext() {
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 3a7604a..26a623f 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -23,6 +23,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.SynchronousUserSwitchObserver;
@@ -91,6 +92,7 @@
 import com.android.server.ServiceThread;
 import com.android.server.SystemService;
 import com.android.server.UiThread;
+import com.android.server.UserspaceRebootLogger;
 import com.android.server.Watchdog;
 import com.android.server.am.BatteryStatsService;
 import com.android.server.lights.LightsManager;
@@ -2834,7 +2836,10 @@
     }
 
     private void shutdownOrRebootInternal(final @HaltMode int haltMode, final boolean confirm,
-            final String reason, boolean wait) {
+            @Nullable final String reason, boolean wait) {
+        if (PowerManager.REBOOT_USERSPACE.equals(reason)) {
+            UserspaceRebootLogger.noteUserspaceRebootWasRequested();
+        }
         if (mHandler == null || !mSystemReady) {
             if (RescueParty.isAttemptingFactoryReset()) {
                 // If we're stuck in a really low-level reboot loop, and a
@@ -4649,7 +4654,7 @@
          * @param wait If true, this call waits for the reboot to complete and does not return.
          */
         @Override // Binder call
-        public void reboot(boolean confirm, String reason, boolean wait) {
+        public void reboot(boolean confirm, @Nullable String reason, boolean wait) {
             mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null);
             if (PowerManager.REBOOT_RECOVERY.equals(reason)
                     || PowerManager.REBOOT_RECOVERY_UPDATE.equals(reason)) {
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 81b42da..153b006 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -21,6 +21,7 @@
         "BroadcastRadio/convert.cpp",
         "BroadcastRadio/regions.cpp",
         "com_android_server_AlarmManagerService.cpp",
+        "com_android_server_adb_AdbDebuggingManager.cpp",
         "com_android_server_am_BatteryStatsService.cpp",
         "com_android_server_connectivity_Vpn.cpp",
         "com_android_server_ConsumerIrService.cpp",
@@ -69,6 +70,8 @@
 cc_defaults {
     name: "libservices.core-libs",
     shared_libs: [
+        "libadb_pairing_server",
+        "libadb_pairing_connection",
         "libandroid_runtime",
         "libandroidfw",
         "libaudioclient",
diff --git a/services/core/jni/com_android_server_adb_AdbDebuggingManager.cpp b/services/core/jni/com_android_server_adb_AdbDebuggingManager.cpp
new file mode 100644
index 0000000..aa50eec
--- /dev/null
+++ b/services/core/jni/com_android_server_adb_AdbDebuggingManager.cpp
@@ -0,0 +1,187 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "AdbDebuggingManager-JNI"
+
+#define LOG_NDEBUG 0
+
+#include <algorithm>
+#include <condition_variable>
+#include <mutex>
+#include <optional>
+#include <random>
+#include <string>
+#include <vector>
+
+#include <adb/pairing/pairing_server.h>
+#include <android-base/properties.h>
+#include <utils/Log.h>
+
+#include <nativehelper/JNIHelp.h>
+#include "jni.h"
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+namespace {
+
+template <class T, class N>
+class JSmartWrapper {
+public:
+    JSmartWrapper(JNIEnv* env, T* jData) :
+        mEnv(env),
+        mJData(jData) {
+    }
+
+    virtual ~JSmartWrapper() = default;
+
+    const N* data() const {
+        return mRawData;
+    }
+
+    jsize size() const {
+        return mSize;
+    }
+
+protected:
+    N* mRawData = nullptr;
+    JNIEnv* mEnv = nullptr;
+    T* mJData = nullptr;
+    jsize mSize = 0;
+};  // JSmartWrapper
+
+class JStringUTFWrapper : public JSmartWrapper<jstring, const char> {
+public:
+    explicit JStringUTFWrapper(JNIEnv* env, jstring* str)
+        : JSmartWrapper(env, str) {
+        mRawData = env->GetStringUTFChars(*str, NULL);
+        mSize = env->GetStringUTFLength(*str);
+    }
+
+    virtual ~JStringUTFWrapper() {
+        if (data()) {
+            mEnv->ReleaseStringUTFChars(*mJData, mRawData);
+        }
+    }
+}; // JStringUTFWrapper
+
+struct ServerDeleter {
+    void operator()(PairingServerCtx* p) {
+        pairing_server_destroy(p);
+    }
+};
+using PairingServerPtr = std::unique_ptr<PairingServerCtx, ServerDeleter>;
+struct PairingResultWaiter {
+    std::mutex mutex_;
+    std::condition_variable cv_;
+    std::optional<bool> is_valid_;
+    PeerInfo peer_info_;
+
+    static void ResultCallback(const PeerInfo* peer_info,
+                               void* opaque) {
+        auto* p = reinterpret_cast<PairingResultWaiter*>(opaque);
+        {
+            std::unique_lock<std::mutex> lock(p->mutex_);
+            if (peer_info) {
+                memcpy(&(p->peer_info_), peer_info, sizeof(PeerInfo));
+            }
+            p->is_valid_ = (peer_info != nullptr);
+        }
+        p->cv_.notify_one();
+    }
+};
+
+PairingServerPtr sServer;
+std::unique_ptr<PairingResultWaiter> sWaiter;
+}  // namespace
+
+static jint native_pairing_start(JNIEnv* env,
+                                 jobject thiz,
+                                 jstring guid,
+                                 jstring password) {
+    // Server-side only sends its GUID on success.
+    PeerInfo system_info = {};
+    system_info.type = ADB_DEVICE_GUID;
+    JStringUTFWrapper guidWrapper(env, &guid);
+    memcpy(system_info.data, guidWrapper.data(), guidWrapper.size());
+
+    JStringUTFWrapper passwordWrapper(env, &password);
+
+    // Create the pairing server
+    sServer = PairingServerPtr(
+            pairing_server_new_no_cert(reinterpret_cast<const uint8_t*>(passwordWrapper.data()),
+                                       passwordWrapper.size(),
+                                       &system_info,
+                                       0));
+
+    sWaiter.reset(new PairingResultWaiter);
+    uint16_t port = pairing_server_start(sServer.get(), sWaiter->ResultCallback, sWaiter.get());
+    if (port == 0) {
+        ALOGE("Failed to start pairing server");
+        return -1;
+    }
+
+    return port;
+}
+
+static void native_pairing_cancel(JNIEnv* /* env */,
+                                  jclass /* clazz */) {
+    if (sServer != nullptr) {
+        sServer.reset();
+    }
+}
+
+static jboolean native_pairing_wait(JNIEnv* env,
+                                    jobject thiz) {
+    ALOGI("Waiting for pairing server to complete");
+    std::unique_lock<std::mutex> lock(sWaiter->mutex_);
+    if (!sWaiter->is_valid_.has_value()) {
+        sWaiter->cv_.wait(lock, [&]() { return sWaiter->is_valid_.has_value(); });
+    }
+    if (!*(sWaiter->is_valid_)) {
+        return JNI_FALSE;
+    }
+
+    std::string peer_public_key = reinterpret_cast<char*>(sWaiter->peer_info_.data);
+    // Write to PairingThread's member variables
+    jclass clazz = env->GetObjectClass(thiz);
+    jfieldID mPublicKey = env->GetFieldID(clazz, "mPublicKey", "Ljava/lang/String;");
+    jstring jpublickey = env->NewStringUTF(peer_public_key.c_str());
+    env->SetObjectField(thiz, mPublicKey, jpublickey);
+    return JNI_TRUE;
+}
+
+// ----------------------------------------------------------------------------
+
+static const JNINativeMethod gPairingThreadMethods[] = {
+    /* name, signature, funcPtr */
+    { "native_pairing_start", "(Ljava/lang/String;Ljava/lang/String;)I",
+            (void*) native_pairing_start },
+    { "native_pairing_cancel", "()V",
+            (void*) native_pairing_cancel },
+    { "native_pairing_wait", "()Z",
+            (void*) native_pairing_wait },
+};
+
+int register_android_server_AdbDebuggingManager(JNIEnv* env) {
+    int res = jniRegisterNativeMethods(env, "com/android/server/adb/AdbDebuggingManager$PairingThread",
+              gPairingThreadMethods, NELEM(gPairingThreadMethods));
+    (void) res;  // Faked use when LOG_NDEBUG.
+    LOG_FATAL_IF(res < 0, "Unable to register native methods.");
+    return 0;
+}
+
+} /* namespace android */
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 692c9d2..657920e 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -56,6 +56,7 @@
 int register_android_server_security_VerityUtils(JNIEnv* env);
 int register_android_server_am_AppCompactor(JNIEnv* env);
 int register_android_server_am_LowMemDetector(JNIEnv* env);
+int register_android_server_AdbDebuggingManager(JNIEnv* env);
 };
 
 using namespace android;
@@ -105,5 +106,6 @@
     register_android_server_security_VerityUtils(env);
     register_android_server_am_AppCompactor(env);
     register_android_server_am_LowMemDetector(env);
+    register_android_server_AdbDebuggingManager(env);
     return JNI_VERSION_1_4;
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 952a64c..03f64fc 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -403,7 +403,7 @@
      *  System property whose value is either "true" or "false", indicating whether
      *  device owner is present.
      */
-    private static final String PROPERTY_DEVICE_OWNER_PRESENT = "ro.device_owner";
+    private static final String PROPERTY_DEVICE_OWNER_PRESENT = "ro.organization_owned";
 
     private static final int STATUS_BAR_DISABLE_MASK =
             StatusBarManager.DISABLE_EXPAND |
@@ -434,6 +434,7 @@
 
         GLOBAL_SETTINGS_WHITELIST = new ArraySet<>();
         GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.ADB_ENABLED);
+        GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.ADB_WIFI_ENABLED);
         GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.AUTO_TIME);
         GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.AUTO_TIME_ZONE);
         GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.DATA_ROAMING);
@@ -2475,11 +2476,11 @@
         }
 
         if (!mInjector.systemPropertiesGet(PROPERTY_DEVICE_OWNER_PRESENT, "").isEmpty()) {
-            Slog.w(LOG_TAG, "Trying to set ro.device_owner, but it has already been set?");
+            Slog.w(LOG_TAG, "Trying to set ro.organization_owned, but it has already been set?");
         } else {
             final String value = Boolean.toString(hasDeviceOwner);
             mInjector.systemPropertiesSet(PROPERTY_DEVICE_OWNER_PRESENT, value);
-            Slog.i(LOG_TAG, "Set ro.device_owner property to " + value);
+            Slog.i(LOG_TAG, "Set ro.organization_owned property to " + value);
         }
     }
 
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index b93365a..de7ae55 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -41,7 +41,6 @@
 import android.database.sqlite.SQLiteGlobal;
 import android.hardware.display.DisplayManagerInternal;
 import android.net.ConnectivityModuleConnector;
-import android.net.ITetheringConnector;
 import android.net.NetworkStackClient;
 import android.os.BaseBundle;
 import android.os.Binder;
@@ -286,6 +285,8 @@
     private static final String CONTENT_SUGGESTIONS_SERVICE_CLASS =
             "com.android.server.contentsuggestions.ContentSuggestionsManagerService";
 
+    private static final String TETHERING_CONNECTOR_CLASS = "android.net.ITetheringConnector";
+
     private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
 
     private static final String UNCRYPT_PACKAGE_FILE = "/cache/recovery/uncrypt_file";
@@ -2226,7 +2227,7 @@
             try {
                 // TODO: hide implementation details, b/146312721.
                 ConnectivityModuleConnector.getInstance().startModuleService(
-                        ITetheringConnector.class.getName(),
+                        TETHERING_CONNECTOR_CLASS,
                         PERMISSION_MAINLINE_NETWORK_STACK, service -> {
                             ServiceManager.addService(Context.TETHERING_SERVICE, service,
                                     false /* allowIsolated */,
diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
index 44f4ccf..407f67e 100644
--- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
@@ -24,7 +24,6 @@
 import static org.mockito.Mockito.when;
 import static org.testng.Assert.assertThrows;
 
-import android.app.compat.ChangeIdStateCache;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
@@ -75,7 +74,6 @@
         // Assume userdebug/eng non-final build
         when(mBuildClassifier.isDebuggableBuild()).thenReturn(true);
         when(mBuildClassifier.isFinalBuild()).thenReturn(false);
-        ChangeIdStateCache.disable();
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
index 717a78d..ce5d6d9b 100644
--- a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
@@ -57,7 +57,6 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        android.app.compat.ChangeIdStateCache.disable();
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         when(mPackageManager.getPackageUid(eq(PACKAGE_NAME), eq(0))).thenThrow(
                 new PackageManager.NameNotFoundException());
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
index a4ba056..cb20b65 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
@@ -532,6 +532,61 @@
         assertHasDclInfo(mBarUser0, mBarUser0, secondaries);
     }
 
+    @Test
+    public void testPrimaryAndSecondaryDexLoad() {
+        // Foo loads both primary and secondary dexes
+        List<String> fooSecondaries = mFooUser0.getSecondaryDexPaths();
+        List<String> fooDexes = new ArrayList<>(mFooUser0.getBaseAndSplitDexPaths());
+        int primaryCount = fooDexes.size();
+        fooDexes.addAll(fooSecondaries);
+
+        notifyDexLoad(mFooUser0, fooDexes, mUser0);
+
+        PackageUseInfo pui = getPackageUseInfo(mFooUser0);
+        assertIsUsedByOtherApps(mFooUser0, pui, false);
+        assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size());
+
+        // Below we want to verify that the secondary dex files within fooDexes have been correctly
+        // reported and their class loader contexts were correctly recorded.
+        //
+        // In order to achieve this we first use DexoptUtils.processContextForDexLoad to compute the
+        // class loader contexts for all the dex files.
+        String[] allClassLoaderContexts = DexoptUtils.processContextForDexLoad(
+                Arrays.asList(mFooUser0.mClassLoader),
+                Arrays.asList(String.join(File.pathSeparator, fooDexes)));
+        // Next we filter out the class loader contexts corresponding to non-secondary dex files.
+        String[] secondaryClassLoaderContexts = Arrays.copyOfRange(allClassLoaderContexts,
+                primaryCount, allClassLoaderContexts.length);
+        assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0,
+                secondaryClassLoaderContexts);
+
+        assertHasDclInfo(mFooUser0, mFooUser0, fooSecondaries);
+    }
+
+    @Test
+    public void testNotifySecondary_withSharedLibrary() {
+        // Foo loads its own secondary files.
+        List<String> fooSecondaries = mFooUser0.getSecondaryDexPaths();
+
+        String contextSuffix = "{PCL[/system/framework/org.apache.http.legacy.jar]}";
+        String[] expectedContexts = DexoptUtils.processContextForDexLoad(
+                Arrays.asList(mFooUser0.mClassLoader),
+                Arrays.asList(String.join(File.pathSeparator, fooSecondaries)));
+        for (int i = 0; i < expectedContexts.length; i++) {
+            expectedContexts[i] += contextSuffix;
+        }
+
+        notifyDexLoad(mFooUser0, fooSecondaries, expectedContexts, mUser0);
+
+        PackageUseInfo pui = getPackageUseInfo(mFooUser0);
+        assertIsUsedByOtherApps(mFooUser0, pui, false);
+        assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size());
+        assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0,
+                expectedContexts);
+
+        assertHasDclInfo(mFooUser0, mFooUser0, fooSecondaries);
+    }
+
     private void assertSecondaryUse(TestData testData, PackageUseInfo pui,
             List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId,
             String[] expectedContexts) {
@@ -572,17 +627,43 @@
         // By default, assume a single class loader in the chain.
         // This makes writing tests much easier.
         List<String> classLoaders = Arrays.asList(testData.mClassLoader);
-        List<String> classPaths = (dexPaths == null)
-                                  ? Arrays.asList((String) null)
-                                  : Arrays.asList(String.join(File.pathSeparator, dexPaths));
+        List<String> classPaths = dexPaths != null
+                ? Arrays.<String>asList(String.join(File.pathSeparator, dexPaths)) : null;
         notifyDexLoad(testData, classLoaders, classPaths, loaderUserId);
     }
 
     private void notifyDexLoad(TestData testData, List<String> classLoaders,
             List<String> classPaths, int loaderUserId) {
+        String[] classLoaderContexts = computeClassLoaderContexts(classLoaders, classPaths);
         // We call the internal function so any exceptions thrown cause test failures.
-        mDexManager.notifyDexLoadInternal(testData.mPackageInfo.applicationInfo, classLoaders,
-                classPaths, testData.mLoaderIsa, loaderUserId);
+        List<String> dexPaths = classPaths != null
+                ? Arrays.asList(classPaths.get(0).split(File.pathSeparator)) : Arrays.asList();
+        notifyDexLoad(testData, dexPaths, classLoaderContexts, loaderUserId);
+    }
+
+    private void notifyDexLoad(TestData testData, List<String> dexPaths,
+            String[] classLoaderContexts, int loaderUserId) {
+        assertTrue(dexPaths.size() == classLoaderContexts.length);
+        HashMap<String, String> dexPathMapping = new HashMap<>(dexPaths.size());
+        for (int i = 0; i < dexPaths.size(); i++) {
+            dexPathMapping.put(dexPaths.get(i), classLoaderContexts != null
+                    ? classLoaderContexts[i] : PackageDexUsage.UNSUPPORTED_CLASS_LOADER_CONTEXT);
+        }
+        mDexManager.notifyDexLoadInternal(testData.mPackageInfo.applicationInfo, dexPathMapping,
+                testData.mLoaderIsa, loaderUserId);
+    }
+
+    private String[] computeClassLoaderContexts(List<String> classLoaders,
+            List<String> classPaths) {
+        if (classPaths == null) {
+            return new String[0];
+        }
+        String[] results = DexoptUtils.processContextForDexLoad(classLoaders, classPaths);
+        if (results == null) {
+            results = new String[classPaths.get(0).split(File.pathSeparator).length];
+            Arrays.fill(results, PackageDexUsage.UNSUPPORTED_CLASS_LOADER_CONTEXT);
+        }
+        return results;
     }
 
     private PackageUseInfo getPackageUseInfo(TestData testData) {
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 5bb75c9..d2b30b2 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -1718,21 +1718,6 @@
         private static final int ENUMERATION_TIME_OUT_MS = 2000;
 
         /**
-         * Command to start native service.
-         */
-        protected static final String CTL_START = "ctl.start";
-
-        /**
-         * Command to start native service.
-         */
-        protected static final String CTL_STOP = "ctl.stop";
-
-        /**
-         * Adb native daemon.
-         */
-        protected static final String ADBD = "adbd";
-
-        /**
          * Gadget HAL fully qualified instance name for registering for ServiceNotification.
          */
         protected static final String GADGET_HAL_FQ_NAME =
@@ -1913,17 +1898,7 @@
                     return;
                 }
                 try {
-                    if ((config & UsbManager.FUNCTION_ADB) != 0) {
-                        /**
-                         * Start adbd if ADB function is included in the configuration.
-                         */
-                        setSystemProperty(CTL_START, ADBD);
-                    } else {
-                        /**
-                         * Stop adbd otherwise.
-                         */
-                        setSystemProperty(CTL_STOP, ADBD);
-                    }
+                    // Adbd will be started by AdbService once Global.ADB_ENABLED is set.
                     UsbGadgetCallback usbGadgetCallback = new UsbGadgetCallback(mCurrentRequest,
                             config, chargingFunctions);
                     mGadgetProxy.setCurrentUsbFunctions(config, usbGadgetCallback,
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
old mode 100644
new mode 100755
index 52213d8..c5fcf67
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -465,8 +465,27 @@
          * @hide
          */
         public static final int CAPABILITY_ADD_PARTICIPANT = 0x02000000;
+
+        /**
+         * When set for a call, indicates that this {@code Call} can be transferred to another
+         * number.
+         * Call supports the blind and assured call transfer feature.
+         *
+         * @hide
+         */
+        public static final int CAPABILITY_TRANSFER = 0x04000000;
+
+        /**
+         * When set for a call, indicates that this {@code Call} can be transferred to another
+         * ongoing call.
+         * Call supports the consultative call transfer feature.
+         *
+         * @hide
+         */
+        public static final int CAPABILITY_TRANSFER_CONSULTATIVE = 0x08000000;
+
         //******************************************************************************************
-        // Next CAPABILITY value: 0x04000000
+        // Next CAPABILITY value: 0x10000000
         //******************************************************************************************
 
         /**
@@ -699,6 +718,12 @@
             if (can(capabilities, CAPABILITY_ADD_PARTICIPANT)) {
                 builder.append(" CAPABILITY_ADD_PARTICIPANT");
             }
+            if (can(capabilities, CAPABILITY_TRANSFER)) {
+                builder.append(" CAPABILITY_TRANSFER");
+            }
+            if (can(capabilities, CAPABILITY_TRANSFER_CONSULTATIVE)) {
+                builder.append(" CAPABILITY_TRANSFER_CONSULTATIVE");
+            }
             builder.append("]");
             return builder.toString();
         }
@@ -1564,6 +1589,30 @@
     }
 
     /**
+     * Instructs this {@code Call} to be transferred to another number.
+     *
+     * @param targetNumber The address to which the call will be transferred.
+     * @param isConfirmationRequired if {@code true} it will initiate ASSURED transfer,
+     * if {@code false}, it will initiate BLIND transfer.
+     *
+     * @hide
+     */
+    public void transfer(@NonNull Uri targetNumber, boolean isConfirmationRequired) {
+        mInCallAdapter.transferCall(mTelecomCallId, targetNumber, isConfirmationRequired);
+    }
+
+    /**
+     * Instructs this {@code Call} to be transferred to another ongoing call.
+     * This will initiate CONSULTATIVE transfer.
+     * @param toCall The other ongoing {@code Call} to which this call will be transferred.
+     *
+     * @hide
+     */
+    public void transfer(@NonNull android.telecom.Call toCall) {
+        mInCallAdapter.transferCall(mTelecomCallId, toCall.mTelecomCallId);
+    }
+
+    /**
      * Instructs this {@code Call} to disconnect.
      */
     public void disconnect() {
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
old mode 100644
new mode 100755
index 3b0ba254..4604cd2
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -387,8 +387,25 @@
      * @hide
      */
     public static final int CAPABILITY_ADD_PARTICIPANT = 0x04000000;
+
+    /**
+     * Indicates that this {@code Connection} can be transferred to another
+     * number.
+     * Connection supports the blind and assured call transfer feature.
+     * @hide
+     */
+    public static final int CAPABILITY_TRANSFER = 0x08000000;
+
+    /**
+     * Indicates that this {@code Connection} can be transferred to another
+     * ongoing {@code Connection}.
+     * Connection supports the consultative call transfer feature.
+     * @hide
+     */
+    public static final int CAPABILITY_TRANSFER_CONSULTATIVE = 0x10000000;
+
     //**********************************************************************************************
-    // Next CAPABILITY value: 0x08000000
+    // Next CAPABILITY value: 0x20000000
     //**********************************************************************************************
 
     /**
@@ -967,6 +984,13 @@
         if ((capabilities & CAPABILITY_ADD_PARTICIPANT) == CAPABILITY_ADD_PARTICIPANT) {
             builder.append(isLong ? " CAPABILITY_ADD_PARTICIPANT" : " add_participant");
         }
+        if ((capabilities & CAPABILITY_TRANSFER) == CAPABILITY_TRANSFER) {
+            builder.append(isLong ? " CAPABILITY_TRANSFER" : " sup_trans");
+        }
+        if ((capabilities & CAPABILITY_TRANSFER_CONSULTATIVE)
+                == CAPABILITY_TRANSFER_CONSULTATIVE) {
+            builder.append(isLong ? " CAPABILITY_TRANSFER_CONSULTATIVE" : " sup_cTrans");
+        }
         builder.append("]");
         return builder.toString();
     }
@@ -3092,6 +3116,26 @@
     public void onReject(String replyMessage) {}
 
     /**
+     * Notifies this Connection, a request to transfer to a target number.
+     * @param number the number to transfer this {@link Connection} to.
+     * @param isConfirmationRequired when {@code true}, the {@link ConnectionService}
+     * should wait until the transfer has successfully completed before disconnecting
+     * the current {@link Connection}.
+     * When {@code false}, the {@link ConnectionService} should signal the network to
+     * perform the transfer, but should immediately disconnect the call regardless of
+     * the outcome of the transfer.
+     * @hide
+     */
+    public void onTransfer(@NonNull Uri number, boolean isConfirmationRequired) {}
+
+    /**
+     * Notifies this Connection, a request to transfer to another Connection.
+     * @param otherConnection the {@link Connection} to transfer this call to.
+     * @hide
+     */
+    public void onTransfer(@NonNull Connection otherConnection) {}
+
+    /**
      * Notifies this Connection of a request to silence the ringer.
      * <p>
      * The ringer may be silenced by any of the following methods:
@@ -3532,7 +3576,7 @@
      * ATIS-1000082.
      * @return the verification status.
      */
-    public @VerificationStatus int getCallerNumberVerificationStatus() {
+    public final @VerificationStatus int getCallerNumberVerificationStatus() {
         return mCallerNumberVerificationStatus;
     }
 
@@ -3544,7 +3588,7 @@
      * by
      * {@link ConnectionService#onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest)}.
      */
-    public void setCallerNumberVerificationStatus(
+    public final void setCallerNumberVerificationStatus(
             @VerificationStatus int callerNumberVerificationStatus) {
         mCallerNumberVerificationStatus = callerNumberVerificationStatus;
     }
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
old mode 100644
new mode 100755
index 2aea723..0dca006
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -128,6 +128,8 @@
     private static final String SESSION_ANSWER = "CS.an";
     private static final String SESSION_ANSWER_VIDEO = "CS.anV";
     private static final String SESSION_DEFLECT = "CS.def";
+    private static final String SESSION_TRANSFER = "CS.trans";
+    private static final String SESSION_CONSULTATIVE_TRANSFER = "CS.cTrans";
     private static final String SESSION_REJECT = "CS.r";
     private static final String SESSION_REJECT_MESSAGE = "CS.rWM";
     private static final String SESSION_SILENCE = "CS.s";
@@ -196,6 +198,8 @@
     private static final int MSG_CREATE_CONFERENCE_FAILED = 37;
     private static final int MSG_REJECT_WITH_REASON = 38;
     private static final int MSG_ADD_PARTICIPANT = 39;
+    private static final int MSG_EXPLICIT_CALL_TRANSFER = 40;
+    private static final int MSG_EXPLICIT_CALL_TRANSFER_CONSULTATIVE = 41;
 
     private static Connection sNullConnection;
 
@@ -481,6 +485,38 @@
         }
 
         @Override
+        public void transfer(@NonNull String callId, @NonNull Uri number,
+                boolean isConfirmationRequired, Session.Info sessionInfo) {
+            Log.startSession(sessionInfo, SESSION_TRANSFER);
+            try {
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = callId;
+                args.arg2 = number;
+                args.argi1 = isConfirmationRequired ? 1 : 0;
+                args.arg3 = Log.createSubsession();
+                mHandler.obtainMessage(MSG_EXPLICIT_CALL_TRANSFER, args).sendToTarget();
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
+        public void consultativeTransfer(@NonNull String callId, @NonNull String otherCallId,
+                Session.Info sessionInfo) {
+            Log.startSession(sessionInfo, SESSION_CONSULTATIVE_TRANSFER);
+            try {
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = callId;
+                args.arg2 = otherCallId;
+                args.arg3 = Log.createSubsession();
+                mHandler.obtainMessage(
+                        MSG_EXPLICIT_CALL_TRANSFER_CONSULTATIVE, args).sendToTarget();
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
         public void silence(String callId, Session.Info sessionInfo) {
             Log.startSession(sessionInfo, SESSION_SILENCE);
             try {
@@ -1108,6 +1144,30 @@
                     }
                     break;
                 }
+                case MSG_EXPLICIT_CALL_TRANSFER: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    Log.continueSession((Session) args.arg3, SESSION_HANDLER + SESSION_TRANSFER);
+                    try {
+                        final boolean isConfirmationRequired = args.argi1 == 1;
+                        transfer((String) args.arg1, (Uri) args.arg2, isConfirmationRequired);
+                    } finally {
+                        args.recycle();
+                        Log.endSession();
+                    }
+                    break;
+                }
+                case MSG_EXPLICIT_CALL_TRANSFER_CONSULTATIVE: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    Log.continueSession(
+                            (Session) args.arg3, SESSION_HANDLER + SESSION_CONSULTATIVE_TRANSFER);
+                    try {
+                        consultativeTransfer((String) args.arg1, (String) args.arg2);
+                    } finally {
+                        args.recycle();
+                        Log.endSession();
+                    }
+                    break;
+                }
                 case MSG_DISCONNECT: {
                     SomeArgs args = (SomeArgs) msg.obj;
                     Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_DISCONNECT);
@@ -2042,6 +2102,18 @@
         findConnectionForAction(callId, "reject").onReject(rejectReason);
     }
 
+    private void transfer(String callId, Uri number, boolean isConfirmationRequired) {
+        Log.d(this, "transfer %s", callId);
+        findConnectionForAction(callId, "transfer").onTransfer(number, isConfirmationRequired);
+    }
+
+    private void consultativeTransfer(String callId, String otherCallId) {
+        Log.d(this, "consultativeTransfer %s", callId);
+        Connection connection1 = findConnectionForAction(callId, "consultativeTransfer");
+        Connection connection2 = findConnectionForAction(otherCallId, " consultativeTransfer");
+        connection1.onTransfer(connection2);
+    }
+
     private void silence(String callId) {
         Log.d(this, "silence %s", callId);
         findConnectionForAction(callId, "silence").onSilence();
diff --git a/telecomm/java/android/telecom/InCallAdapter.java b/telecomm/java/android/telecom/InCallAdapter.java
old mode 100644
new mode 100755
index 9d291740..dd6c153
--- a/telecomm/java/android/telecom/InCallAdapter.java
+++ b/telecomm/java/android/telecom/InCallAdapter.java
@@ -16,6 +16,7 @@
 
 package android.telecom;
 
+import android.annotation.NonNull;
 import android.bluetooth.BluetoothDevice;
 import android.net.Uri;
 import android.os.Bundle;
@@ -102,6 +103,35 @@
     }
 
     /**
+     * Instructs Telecom to transfer the specified call.
+     *
+     * @param callId The identifier of the call to transfer.
+     * @param targetNumber The address to transfer to.
+     * @param isConfirmationRequired if {@code true} it will initiate ASSURED transfer,
+     * if {@code false}, it will initiate BLIND transfer.
+     */
+    public void transferCall(@NonNull String callId, @NonNull Uri targetNumber,
+            boolean isConfirmationRequired) {
+        try {
+            mAdapter.transferCall(callId, targetNumber, isConfirmationRequired);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Instructs Telecom to transfer the specified call to another ongoing call.
+     *
+     * @param callId The identifier of the call to transfer.
+     * @param otherCallId The identifier of the other call to which this will be transferred.
+     */
+    public void transferCall(@NonNull String callId, @NonNull String otherCallId) {
+        try {
+            mAdapter.consultativeTransfer(callId, otherCallId);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
      * Instructs Telecom to disconnect the specified call.
      *
      * @param callId The identifier of the call to disconnect.
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
index a397d77..fb54179 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
@@ -81,6 +81,11 @@
 
     void rejectWithMessage(String callId, String message, in Session.Info sessionInfo);
 
+    void transfer(String callId, in Uri number, boolean isConfirmationRequired,
+            in Session.Info sessionInfo);
+
+    void consultativeTransfer(String callId, String otherCallId, in Session.Info sessionInfo);
+
     void disconnect(String callId, in Session.Info sessionInfo);
 
     void silence(String callId, in Session.Info sessionInfo);
diff --git a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
old mode 100644
new mode 100755
index 9beff22..edf1cf4
--- a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
@@ -36,6 +36,10 @@
 
     void rejectCallWithReason(String callId, int rejectReason);
 
+    void transferCall(String callId, in Uri targetNumber, boolean isConfirmationRequired);
+
+    void consultativeTransfer(String callId, String otherCallId);
+
     void disconnectCall(String callId);
 
     void holdCall(String callId);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 233eced..bca09e5 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -839,7 +839,8 @@
     /**
      * The default flag specifying whether ETWS/CMAS test setting is forcibly disabled in
      * Settings->More->Emergency broadcasts menu even though developer options is turned on.
-     * @deprecated moved to cellbroadcastreceiver resource show_test_settings
+     * @deprecated Use {@code com.android.cellbroadcastreceiver.CellBroadcastReceiver} resource
+     * {@code show_test_settings} to control whether to show test alert settings or not.
      */
     @Deprecated
     public static final String KEY_CARRIER_FORCE_DISABLE_ETWS_CMAS_TEST_BOOL =
@@ -1936,6 +1937,13 @@
             "carrier_allow_deflect_ims_call_bool";
 
     /**
+     * Flag indicating whether the carrier supports explicit call transfer for an IMS call.
+     * @hide
+     */
+    public static final String KEY_CARRIER_ALLOW_TRANSFER_IMS_CALL_BOOL =
+            "carrier_allow_transfer_ims_call_bool";
+
+    /**
      * Flag indicating whether the carrier always wants to play an "on-hold" tone when a call has
      * been remotely held.
      * <p>
@@ -1965,10 +1973,15 @@
             "allow_add_call_during_video_call";
 
     /**
-     * When false, indicates that holding a video call is disabled
+     * When {@code true}, indicates that video calls can be put on hold in order to swap to another
+     * call (e.g. a new outgoing call).
+     * When {@code false}, indicates that video calls will be disconnected when swapping to another
+     * call.
+     * <p>
+     * This is {@code true} by default.
      */
-    public static final String KEY_ALLOW_HOLDING_VIDEO_CALL_BOOL =
-            "allow_holding_video_call";
+    public static final String KEY_ALLOW_HOLD_VIDEO_CALL_BOOL =
+            "allow_hold_video_call_bool";
 
     /**
      * When true, indicates that the HD audio icon in the in-call screen should not be shown for
@@ -3336,6 +3349,25 @@
             "subscription_group_uuid_string";
 
     /**
+     * Data switch validation minimal gap time, in milliseconds.
+     *
+     * Which means, if the same subscription on the same network (based on MCC+MNC+TAC+subId)
+     * was recently validated (within this time gap), and Telephony receives a request to switch to
+     * it again, Telephony will skip the validation part and switch to it as soon as connection
+     * is setup, as if it's already validated.
+     *
+     * If the network was validated within the gap but the latest validation result is false, the
+     * validation will not be skipped.
+     *
+     * If not set or set to 0, validation will never be skipped.
+     * The max acceptable value of this config is 24 hours.
+     *
+     * @hide
+     */
+    public static final String KEY_DATA_SWITCH_VALIDATION_MIN_GAP_LONG =
+            "data_switch_validation_min_gap_LONG";
+
+    /**
     * A boolean property indicating whether this subscription should be managed as an opportunistic
     * subscription.
     *
@@ -3420,6 +3452,7 @@
         sDefaults.putString(KEY_CARRIER_CONFIG_VERSION_STRING, "");
         sDefaults.putBoolean(KEY_ALLOW_HOLD_IN_IMS_CALL_BOOL, true);
         sDefaults.putBoolean(KEY_CARRIER_ALLOW_DEFLECT_IMS_CALL_BOOL, false);
+        sDefaults.putBoolean(KEY_CARRIER_ALLOW_TRANSFER_IMS_CALL_BOOL, false);
         sDefaults.putBoolean(KEY_ALWAYS_PLAY_REMOTE_HOLD_TONE_BOOL, false);
         sDefaults.putBoolean(KEY_AUTO_RETRY_FAILED_WIFI_EMERGENCY_CALL, false);
         sDefaults.putBoolean(KEY_ADDITIONAL_CALL_SETTING_BOOL, true);
@@ -3713,7 +3746,7 @@
         sDefaults.putBoolean(KEY_DROP_VIDEO_CALL_WHEN_ANSWERING_AUDIO_CALL_BOOL, false);
         sDefaults.putBoolean(KEY_ALLOW_MERGE_WIFI_CALLS_WHEN_VOWIFI_OFF_BOOL, true);
         sDefaults.putBoolean(KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL, true);
-        sDefaults.putBoolean(KEY_ALLOW_HOLDING_VIDEO_CALL_BOOL, true);
+        sDefaults.putBoolean(KEY_ALLOW_HOLD_VIDEO_CALL_BOOL, true);
         sDefaults.putBoolean(KEY_WIFI_CALLS_CAN_BE_HD_AUDIO, true);
         sDefaults.putBoolean(KEY_VIDEO_CALLS_CAN_BE_HD_AUDIO, true);
         sDefaults.putBoolean(KEY_GSM_CDMA_CALLS_CAN_BE_HD_AUDIO, false);
@@ -3902,6 +3935,7 @@
         sDefaults.putLong(KEY_DATA_SWITCH_VALIDATION_TIMEOUT_LONG, 2000);
         sDefaults.putInt(KEY_PARAMETERS_USED_FOR_LTE_SIGNAL_BAR_INT,
                 CellSignalStrengthLte.USE_RSRP);
+        sDefaults.putLong(KEY_DATA_SWITCH_VALIDATION_MIN_GAP_LONG, 0);
     }
 
     /**
diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java
index 4978dc1..2529387 100644
--- a/telephony/java/android/telephony/CellSignalStrengthLte.java
+++ b/telephony/java/android/telephony/CellSignalStrengthLte.java
@@ -244,19 +244,30 @@
         } else {
             mParametersUseForLevel = cc.getInt(
                     CarrierConfigManager.KEY_PARAMETERS_USED_FOR_LTE_SIGNAL_BAR_INT);
-            Rlog.i(LOG_TAG, "Using signal strength level: " + mParametersUseForLevel);
+            if (DBG) {
+                Rlog.i(LOG_TAG, "Using signal strength level: " + mParametersUseForLevel);
+            }
             rsrpThresholds = cc.getIntArray(
                     CarrierConfigManager.KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY);
             if (rsrpThresholds == null) rsrpThresholds = sRsrpThresholds;
-            Rlog.i(LOG_TAG, "Applying LTE RSRP Thresholds: " + Arrays.toString(rsrpThresholds));
+            if (DBG) {
+                Rlog.i(LOG_TAG, "Applying LTE RSRP Thresholds: "
+                        + Arrays.toString(rsrpThresholds));
+            }
             rsrqThresholds = cc.getIntArray(
                     CarrierConfigManager.KEY_LTE_RSRQ_THRESHOLDS_INT_ARRAY);
             if (rsrqThresholds == null) rsrqThresholds = sRsrqThresholds;
-            Rlog.i(LOG_TAG, "Applying LTE RSRQ Thresholds: " + Arrays.toString(rsrqThresholds));
+            if (DBG) {
+                Rlog.i(LOG_TAG, "Applying LTE RSRQ Thresholds: "
+                        + Arrays.toString(rsrqThresholds));
+            }
             rssnrThresholds = cc.getIntArray(
                     CarrierConfigManager.KEY_LTE_RSSNR_THRESHOLDS_INT_ARRAY);
             if (rssnrThresholds == null) rssnrThresholds = sRssnrThresholds;
-            Rlog.i(LOG_TAG, "Applying LTE RSSNR Thresholds: " + Arrays.toString(rssnrThresholds));
+            if (DBG) {
+                Rlog.i(LOG_TAG, "Applying LTE RSSNR Thresholds: "
+                        + Arrays.toString(rssnrThresholds));
+            }
             rsrpOnly = cc.getBoolean(
                     CarrierConfigManager.KEY_USE_ONLY_RSRP_FOR_LTE_SIGNAL_BAR_BOOL, false);
         }
@@ -283,15 +294,21 @@
 
         if (isLevelForParameter(USE_RSRP)) {
             rsrpLevel = updateLevelWithMeasure(rsrp, rsrpThresholds);
-            Rlog.i(LOG_TAG, "Updated 4G LTE RSRP Level: " + rsrpLevel);
+            if (DBG) {
+                Rlog.i(LOG_TAG, "Updated 4G LTE RSRP Level: " + rsrpLevel);
+            }
         }
         if (isLevelForParameter(USE_RSRQ)) {
             rsrqLevel = updateLevelWithMeasure(mRsrq, rsrqThresholds);
-            Rlog.i(LOG_TAG, "Updated 4G LTE RSRQ Level: " + rsrqLevel);
+            if (DBG) {
+                Rlog.i(LOG_TAG, "Updated 4G LTE RSRQ Level: " + rsrqLevel);
+            }
         }
         if (isLevelForParameter(USE_RSSNR)) {
             rssnrLevel = updateLevelWithMeasure(mRssnr, rssnrThresholds);
-            Rlog.i(LOG_TAG, "Updated 4G LTE RSSNR Level: " + rssnrLevel);
+            if (DBG) {
+                Rlog.i(LOG_TAG, "Updated 4G LTE RSSNR Level: " + rssnrLevel);
+            }
         }
         // Apply the smaller value among three levels of three measures.
         mLevel = Math.min(Math.min(rsrpLevel, rsrqLevel), rssnrLevel);
diff --git a/telephony/java/android/telephony/CellSignalStrengthNr.java b/telephony/java/android/telephony/CellSignalStrengthNr.java
index e3d03a3..8562df1 100644
--- a/telephony/java/android/telephony/CellSignalStrengthNr.java
+++ b/telephony/java/android/telephony/CellSignalStrengthNr.java
@@ -40,6 +40,8 @@
      */
     public static final int UNKNOWN_ASU_LEVEL = 99;
 
+    private static final boolean VDBG = false;
+
     private static final String TAG = "CellSignalStrengthNr";
 
     // Lifted from Default carrier configs and max range of SSRSRP
@@ -301,31 +303,45 @@
         } else {
             mParametersUseForLevel = cc.getInt(
                     CarrierConfigManager.KEY_PARAMETERS_USE_FOR_5G_NR_SIGNAL_BAR_INT, USE_SSRSRP);
-            Rlog.i(TAG, "Using SSRSRP for Level.");
             mSsRsrpThresholds = cc.getIntArray(
                     CarrierConfigManager.KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY);
-            Rlog.i(TAG, "Applying 5G NR SSRSRP Thresholds: " + Arrays.toString(mSsRsrpThresholds));
+            if (VDBG) {
+                Rlog.i(TAG, "Applying 5G NR SSRSRP Thresholds: "
+                        + Arrays.toString(mSsRsrpThresholds));
+            }
             mSsRsrqThresholds = cc.getIntArray(
                     CarrierConfigManager.KEY_5G_NR_SSRSRQ_THRESHOLDS_INT_ARRAY);
-            Rlog.i(TAG, "Applying 5G NR SSRSRQ Thresholds: " + Arrays.toString(mSsRsrqThresholds));
+            if (VDBG) {
+                Rlog.i(TAG, "Applying 5G NR SSRSRQ Thresholds: "
+                        + Arrays.toString(mSsRsrqThresholds));
+            }
             mSsSinrThresholds = cc.getIntArray(
                     CarrierConfigManager.KEY_5G_NR_SSSINR_THRESHOLDS_INT_ARRAY);
-            Rlog.i(TAG, "Applying 5G NR SSSINR Thresholds: " + Arrays.toString(mSsSinrThresholds));
+            if (VDBG) {
+                Rlog.i(TAG, "Applying 5G NR SSSINR Thresholds: "
+                        + Arrays.toString(mSsSinrThresholds));
+            }
         }
         int ssRsrpLevel = SignalStrength.INVALID;
         int ssRsrqLevel = SignalStrength.INVALID;
         int ssSinrLevel = SignalStrength.INVALID;
         if (isLevelForParameter(USE_SSRSRP)) {
             ssRsrpLevel = updateLevelWithMeasure(mSsRsrp, mSsRsrpThresholds);
-            Rlog.i(TAG, "Updated 5G NR SSRSRP Level: " + ssRsrpLevel);
+            if (VDBG) {
+                Rlog.i(TAG, "Updated 5G NR SSRSRP Level: " + ssRsrpLevel);
+            }
         }
         if (isLevelForParameter(USE_SSRSRQ)) {
             ssRsrqLevel = updateLevelWithMeasure(mSsRsrq, mSsRsrqThresholds);
-            Rlog.i(TAG, "Updated 5G NR SSRSRQ Level: " + ssRsrqLevel);
+            if (VDBG) {
+                Rlog.i(TAG, "Updated 5G NR SSRSRQ Level: " + ssRsrqLevel);
+            }
         }
         if (isLevelForParameter(USE_SSSINR)) {
             ssSinrLevel = updateLevelWithMeasure(mSsSinr, mSsSinrThresholds);
-            Rlog.i(TAG, "Updated 5G NR SSSINR Level: " + ssSinrLevel);
+            if (VDBG) {
+                Rlog.i(TAG, "Updated 5G NR SSSINR Level: " + ssSinrLevel);
+            }
         }
         // Apply the smaller value among three levels of three measures.
         mLevel = Math.min(Math.min(ssRsrpLevel, ssRsrqLevel), ssSinrLevel);
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 9ef361d..948b027 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -353,6 +353,7 @@
 
     private String mOperatorAlphaLongRaw;
     private String mOperatorAlphaShortRaw;
+    private boolean mIsDataRoamingFromRegistration;
     private boolean mIsIwlanPreferred;
 
     /**
@@ -438,6 +439,7 @@
         mNrFrequencyRange = s.mNrFrequencyRange;
         mOperatorAlphaLongRaw = s.mOperatorAlphaLongRaw;
         mOperatorAlphaShortRaw = s.mOperatorAlphaShortRaw;
+        mIsDataRoamingFromRegistration = s.mIsDataRoamingFromRegistration;
         mIsIwlanPreferred = s.mIsIwlanPreferred;
     }
 
@@ -472,6 +474,7 @@
         mNrFrequencyRange = in.readInt();
         mOperatorAlphaLongRaw = in.readString();
         mOperatorAlphaShortRaw = in.readString();
+        mIsDataRoamingFromRegistration = in.readBoolean();
         mIsIwlanPreferred = in.readBoolean();
     }
 
@@ -499,6 +502,7 @@
         out.writeInt(mNrFrequencyRange);
         out.writeString(mOperatorAlphaLongRaw);
         out.writeString(mOperatorAlphaShortRaw);
+        out.writeBoolean(mIsDataRoamingFromRegistration);
         out.writeBoolean(mIsIwlanPreferred);
     }
 
@@ -649,7 +653,9 @@
     }
 
     /**
-     * Get current data network roaming type
+     * Get whether the current data network is roaming.
+     * This value may be overwritten by resource overlay or carrier configuration.
+     * @see #getDataRoamingFromRegistration() to get the value from the network registration.
      * @return roaming type
      * @hide
      */
@@ -659,18 +665,25 @@
     }
 
     /**
-     * Get whether data network registration state is roaming
+     * Set whether the data network registration state is roaming.
+     * This should only be set to the roaming value received
+     * once the data registration phase has completed.
+     * @hide
+     */
+    public void setDataRoamingFromRegistration(boolean dataRoaming) {
+        mIsDataRoamingFromRegistration = dataRoaming;
+    }
+
+    /**
+     * Get whether data network registration state is roaming.
+     * This value is set directly from the modem and will not be overwritten
+     * by resource overlay or carrier configuration.
      * @return true if registration indicates roaming, false otherwise
      * @hide
      */
+    @SystemApi
     public boolean getDataRoamingFromRegistration() {
-        final NetworkRegistrationInfo regState = getNetworkRegistrationInfo(
-                NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
-        if (regState != null) {
-            return regState.getRegistrationState()
-                    == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING;
-        }
-        return false;
+        return mIsDataRoamingFromRegistration;
     }
 
     /**
@@ -873,6 +886,7 @@
                     mNrFrequencyRange,
                     mOperatorAlphaLongRaw,
                     mOperatorAlphaShortRaw,
+                    mIsDataRoamingFromRegistration,
                     mIsIwlanPreferred);
         }
     }
@@ -903,6 +917,7 @@
                     && mNetworkRegistrationInfos.size() == s.mNetworkRegistrationInfos.size()
                     && mNetworkRegistrationInfos.containsAll(s.mNetworkRegistrationInfos)
                     && mNrFrequencyRange == s.mNrFrequencyRange
+                    && mIsDataRoamingFromRegistration == s.mIsDataRoamingFromRegistration
                     && mIsIwlanPreferred == s.mIsIwlanPreferred;
         }
     }
@@ -1062,6 +1077,8 @@
                     .append(", mNrFrequencyRange=").append(mNrFrequencyRange)
                     .append(", mOperatorAlphaLongRaw=").append(mOperatorAlphaLongRaw)
                     .append(", mOperatorAlphaShortRaw=").append(mOperatorAlphaShortRaw)
+                    .append(", mIsDataRoamingFromRegistration=")
+                    .append(mIsDataRoamingFromRegistration)
                     .append(", mIsIwlanPreferred=").append(mIsIwlanPreferred)
                     .append("}").toString();
         }
@@ -1102,6 +1119,7 @@
         }
         mOperatorAlphaLongRaw = null;
         mOperatorAlphaShortRaw = null;
+        mIsDataRoamingFromRegistration = false;
         mIsIwlanPreferred = false;
     }
 
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index e7b2613..f86eeb2 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -2015,8 +2015,12 @@
 
     /**
      * Gets the total capacity of SMS storage on RUIM and SIM cards
+     * <p>
+     * This is the number of 176 byte EF-SMS records which can be stored on the RUIM or SIM card.
+     * <p>
+     * See 3GPP TS 31.102 - 4.2.25 - EF-SMS for more information
      *
-     * @return the total capacity count of SMS on RUIM and SIM cards
+     * @return the total number of SMS records which can be stored on the RUIM or SIM cards.
      * @hide
      */
     @SystemApi
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index bb51399..76b9145 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -3800,21 +3800,20 @@
     }
 
     /**
-     * Return if the current radio is LTE on CDMA. This is a tri-state return value as for a period
-     * of time the mode may be unknown.
+     * Return if the current radio has global mode enabled, meaning it supports
+     * both 3GPP and 3GPP2 radio technologies at the same time.
      *
      * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
-     * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()}
+     * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()}.
      *
-     * @return {@link PhoneConstants#LTE_ON_CDMA_UNKNOWN}, {@link PhoneConstants#LTE_ON_CDMA_FALSE}
-     * or {@link PhoneConstants#LTE_ON_CDMA_TRUE}
-     *
+     * @return {@code true} if global mode is enabled
+     *         {@code false} if global mode is not enabled or unknown
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
-    @UnsupportedAppUsage
-    public int getLteOnCdmaMode() {
-        return getLteOnCdmaMode(getSubId());
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public boolean isGlobalModeEnabled() {
+        return getLteOnCdmaMode(getSubId()) == PhoneConstants.LTE_ON_CDMA_TRUE;
     }
 
     /**
@@ -3827,7 +3826,7 @@
      * or {@link PhoneConstants#LTE_ON_CDMA_TRUE}
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     @UnsupportedAppUsage
     public int getLteOnCdmaMode(int subId) {
         try {
@@ -6040,9 +6039,11 @@
      * @param AID Application id. See ETSI 102.221 and 101.220.
      * @param p2 P2 parameter (described in ISO 7816-4).
      * @return an IccOpenLogicalChannelResponse object.
-     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
+     *             {@link android.se.omapi.SEService#getUiccReader(int)},
+     *             {@link android.se.omapi.Reader#openSession()},
+     *             {@link android.se.omapi.Session#openLogicalChannel(byte[], byte)}.
      */
-    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
     @Deprecated
     public IccOpenLogicalChannelResponse iccOpenLogicalChannel(String AID, int p2) {
         return iccOpenLogicalChannel(getSubId(), AID, p2);
@@ -6074,9 +6075,11 @@
      * @param p2 P2 parameter (described in ISO 7816-4).
      * @return an IccOpenLogicalChannelResponse object.
      * @hide
-     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
+     *             {@link android.se.omapi.SEService#getUiccReader(int)},
+     *             {@link android.se.omapi.Reader#openSession()},
+     *             {@link android.se.omapi.Session#openLogicalChannel(byte[], byte)}.
      */
-    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
     @Deprecated
     public IccOpenLogicalChannelResponse iccOpenLogicalChannel(int subId, String AID, int p2) {
         try {
@@ -6105,9 +6108,9 @@
      *            iccOpenLogicalChannel.
      * @return true if the channel was closed successfully.
      * @hide
-     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
+     *             {@link android.se.omapi.Channel#close()}.
      */
-    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
     @Deprecated
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     @SystemApi
@@ -6135,9 +6138,9 @@
      * @param channel is the channel id to be closed as returned by a successful
      *            iccOpenLogicalChannel.
      * @return true if the channel was closed successfully.
-     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
+     *             {@link android.se.omapi.Channel#close()}.
      */
-    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
     @Deprecated
     public boolean iccCloseLogicalChannel(int channel) {
         return iccCloseLogicalChannel(getSubId(), channel);
@@ -6157,9 +6160,9 @@
      *            iccOpenLogicalChannel.
      * @return true if the channel was closed successfully.
      * @hide
-     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
+     *             {@link android.se.omapi.Channel#close()}.
      */
-    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
     @Deprecated
     public boolean iccCloseLogicalChannel(int subId, int channel) {
         try {
@@ -6196,9 +6199,9 @@
      * @return The APDU response from the ICC card with the status appended at the end, or null if
      * there is an issue connecting to the Telephony service.
      * @hide
-     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
+     *             {@link android.se.omapi.Channel#transmit(byte[])}.
      */
-    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
     @Deprecated
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     @SystemApi
@@ -6237,9 +6240,9 @@
      * @param data Data to be sent with the APDU.
      * @return The APDU response from the ICC card with the status appended at
      *            the end.
-     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
+     *             {@link android.se.omapi.Channel#transmit(byte[])}.
      */
-    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
     @Deprecated
     public String iccTransmitApduLogicalChannel(int channel, int cla,
             int instruction, int p1, int p2, int p3, String data) {
@@ -6269,9 +6272,9 @@
      * @return The APDU response from the ICC card with the status appended at
      *            the end.
      * @hide
-     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
+     *             {@link android.se.omapi.Channel#transmit(byte[])}.
      */
-    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
     @Deprecated
     public String iccTransmitApduLogicalChannel(int subId, int channel, int cla,
             int instruction, int p1, int p2, int p3, String data) {
@@ -6308,9 +6311,12 @@
      * @return The APDU response from the ICC card with the status appended at
      *            the end.
      * @hide
-     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
+     *             {@link android.se.omapi.SEService#getUiccReader(int)},
+     *             {@link android.se.omapi.Reader#openSession()},
+     *             {@link android.se.omapi.Session#openBasicChannel(byte[], byte)},
+     *             {@link android.se.omapi.Channel#transmit(byte[])}.
      */
-    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
     @Deprecated
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     @SystemApi
@@ -6347,9 +6353,12 @@
      * @param data Data to be sent with the APDU.
      * @return The APDU response from the ICC card with the status appended at
      *            the end.
-     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
+     *             {@link android.se.omapi.SEService#getUiccReader(int)},
+     *             {@link android.se.omapi.Reader#openSession()},
+     *             {@link android.se.omapi.Session#openBasicChannel(byte[], byte)},
+     *             {@link android.se.omapi.Channel#transmit(byte[])}.
      */
-    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
     @Deprecated
     public String iccTransmitApduBasicChannel(int cla,
             int instruction, int p1, int p2, int p3, String data) {
@@ -6377,9 +6386,12 @@
      * @return The APDU response from the ICC card with the status appended at
      *            the end.
      * @hide
-     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
+     *             {@link android.se.omapi.SEService#getUiccReader(int)},
+     *             {@link android.se.omapi.Reader#openSession()},
+     *             {@link android.se.omapi.Session#openBasicChannel(byte[], byte)},
+     *             {@link android.se.omapi.Channel#transmit(byte[])}.
      */
-    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
     @Deprecated
     public String iccTransmitApduBasicChannel(int subId, int cla,
             int instruction, int p1, int p2, int p3, String data) {
@@ -6408,9 +6420,12 @@
      * @param p3 P3 value of the APDU command.
      * @param filePath
      * @return The APDU response.
-     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
+     *             {@link android.se.omapi.SEService#getUiccReader(int)},
+     *             {@link android.se.omapi.Reader#openSession()},
+     *             {@link android.se.omapi.Session#openBasicChannel(byte[], byte)},
+     *             {@link android.se.omapi.Channel#transmit(byte[])}.
      */
-    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
     @Deprecated
     public byte[] iccExchangeSimIO(int fileID, int command, int p1, int p2, int p3,
             String filePath) {
@@ -6433,9 +6448,12 @@
      * @param filePath
      * @return The APDU response.
      * @hide
-     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
+     *             {@link android.se.omapi.SEService#getUiccReader(int)},
+     *             {@link android.se.omapi.Reader#openSession()},
+     *             {@link android.se.omapi.Session#openBasicChannel(byte[], byte)},
+     *             {@link android.se.omapi.Channel#transmit(byte[])}.
      */
-    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
     @Deprecated
     public byte[] iccExchangeSimIO(int subId, int fileID, int command, int p1, int p2,
             int p3, String filePath) {
@@ -6462,9 +6480,12 @@
      * @return The APDU response from the ICC card in hexadecimal format
      *         with the last 4 bytes being the status word. If the command fails,
      *         returns an empty string.
-     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
+     *             {@link android.se.omapi.SEService#getUiccReader(int)},
+     *             {@link android.se.omapi.Reader#openSession()},
+     *             {@link android.se.omapi.Session#openBasicChannel(byte[], byte)},
+     *             {@link android.se.omapi.Channel#transmit(byte[])}.
      */
-    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
     @Deprecated
     public String sendEnvelopeWithStatus(String content) {
         return sendEnvelopeWithStatus(getSubId(), content);
@@ -6485,9 +6506,12 @@
      *         with the last 4 bytes being the status word. If the command fails,
      *         returns an empty string.
      * @hide
-     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
+     *             {@link android.se.omapi.SEService#getUiccReader(int)},
+     *             {@link android.se.omapi.Reader#openSession()},
+     *             {@link android.se.omapi.Session#openBasicChannel(byte[], byte)},
+     *             {@link android.se.omapi.Channel#transmit(byte[])}.
      */
-    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
     @Deprecated
     public String sendEnvelopeWithStatus(int subId, String content) {
         try {
diff --git a/telephony/java/android/telephony/ims/ImsCallSession.java b/telephony/java/android/telephony/ims/ImsCallSession.java
old mode 100644
new mode 100755
index 1b583fd..80c38cb
--- a/telephony/java/android/telephony/ims/ImsCallSession.java
+++ b/telephony/java/android/telephony/ims/ImsCallSession.java
@@ -16,6 +16,8 @@
 
 package android.telephony.ims;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.Message;
 import android.os.RemoteException;
 import android.telephony.CallQuality;
@@ -451,6 +453,21 @@
         }
 
         /**
+         * Received success response for call transfer request.
+         */
+        public void callSessionTransferred(@NonNull ImsCallSession session) {
+            // no-op
+        }
+
+        /**
+         * Received failure response for call transfer request.
+         */
+        public void callSessionTransferFailed(@NonNull ImsCallSession session,
+                @Nullable ImsReasonInfo reasonInfo) {
+            // no-op
+        }
+
+        /**
          * Called when the IMS service reports a change to the call quality.
          */
         public void callQualityChanged(CallQuality callQuality) {
@@ -795,6 +812,41 @@
     }
 
     /**
+     * Transfers an ongoing call.
+     *
+     * @param number number to be transferred to.
+     * @param isConfirmationRequired indicates blind or assured transfer.
+     */
+    public void transfer(@NonNull String number, boolean isConfirmationRequired) {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            miSession.transfer(number, isConfirmationRequired);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Transfers a call to another ongoing call.
+     *
+     * @param transferToSession the other ImsCallSession to which this session will be transferred.
+     */
+    public void transfer(@NonNull ImsCallSession transferToSession) {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            if (transferToSession != null) {
+                miSession.consultativeTransfer(transferToSession.getSession());
+            }
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
      * Terminates a call.
      *
      * @see Listener#callSessionTerminated
@@ -1410,6 +1462,20 @@
             }
         }
 
+        @Override
+        public void callSessionTransferred() {
+            if (mListener != null) {
+                mListener.callSessionTransferred(ImsCallSession.this);
+            }
+        }
+
+        @Override
+        public void callSessionTransferFailed(@Nullable ImsReasonInfo reasonInfo) {
+            if (mListener != null) {
+                mListener.callSessionTransferFailed(ImsCallSession.this, reasonInfo);
+            }
+        }
+
         /**
          * Call quality updated
          */
diff --git a/telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl b/telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl
index cc2ebb9..36d2067 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl
@@ -148,6 +148,12 @@
     void callSessionRttAudioIndicatorChanged(in ImsStreamMediaProfile profile);
 
     /**
+     * Notifies the result of transfer request.
+     */
+    void callSessionTransferred();
+    void callSessionTransferFailed(in ImsReasonInfo reasonInfo);
+
+    /**
      * Notifies of a change to the call quality.
      * @param callQuality then updated call quality
      */
diff --git a/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java b/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java
old mode 100644
new mode 100755
index 75bd6a7..06aa642
--- a/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java
+++ b/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java
@@ -16,6 +16,8 @@
 
 package android.telephony.ims.compat.stub;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Message;
 import android.os.RemoteException;
@@ -197,6 +199,29 @@
     }
 
     /**
+     * Transfer an established call to given number, disconnecting the ongoing call
+     * when the transfer is complete.
+     *
+     * @param number number to transfer the call
+     * @param isConfirmationRequired when {@code true}, then the {@link ImsCallSessionImplBase}
+     * should wait until the transfer has successfully completed before disconnecting the current
+     * {@link ImsCallSessionImplBase}. When {@code false}, the {@link ImsCallSessionImplBase}
+     * should signal the network to perform the transfer, but should immediately disconnect the
+     * call regardless of the outcome of the transfer.
+     */
+    @Override
+    public void transfer(@NonNull String number, boolean isConfirmationRequired) {
+    }
+
+    /**
+     * Transfer an established call to an existing ongoing session.
+     * When the transfer is complete, the current call gets disconnected locally.
+     */
+    @Override
+    public void consultativeTransfer(@NonNull IImsCallSession transferToSession) {
+    }
+
+    /**
      * Rejects an incoming call or session update.
      *
      * @param reason reason code to reject an incoming call, defined in {@link ImsReasonInfo}.
@@ -610,6 +635,17 @@
         }
 
         @Override
+        public void callSessionTransferred() throws RemoteException {
+            mNewListener.callSessionTransferred();
+        }
+
+        @Override
+        public void callSessionTransferFailed(@Nullable ImsReasonInfo reasonInfo)
+                throws RemoteException {
+            mNewListener.callSessionTransferFailed(reasonInfo);
+        }
+
+        @Override
         public void callQualityChanged(CallQuality callQuality) throws RemoteException {
             mNewListener.callQualityChanged(callQuality);
         }
diff --git a/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java b/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java
index e8f69ea..73ba0e3 100644
--- a/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java
@@ -16,6 +16,7 @@
 
 package android.telephony.ims.stub;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.os.Message;
@@ -183,6 +184,18 @@
         }
 
         @Override
+        public void transfer(@NonNull String number, boolean isConfirmationRequired) {
+            ImsCallSessionImplBase.this.transfer(number, isConfirmationRequired);
+        }
+
+        @Override
+        public void consultativeTransfer(@NonNull IImsCallSession transferToSession) {
+            ImsCallSessionImplBase otherSession = new ImsCallSessionImplBase();
+            otherSession.setServiceImpl(transferToSession);
+            ImsCallSessionImplBase.this.transfer(otherSession);
+        }
+
+        @Override
         public void terminate(int reason) {
             ImsCallSessionImplBase.this.terminate(reason);
         }
@@ -423,6 +436,26 @@
     }
 
     /**
+     * Transfer an established call to given number
+     *
+     * @param number number to transfer the call
+     * @param isConfirmationRequired if {@code True}, indicates Assured transfer,
+     * if {@code False} it indicates Blind transfer.
+     * @hide
+     */
+    public void transfer(@NonNull String number, boolean isConfirmationRequired) {
+    }
+
+    /**
+     * Transfer an established call to another call session
+     *
+     * @param otherSession The other ImsCallSession to transfer the ongoing session to.
+     * @hide
+     */
+    public void transfer(@NonNull ImsCallSessionImplBase otherSession) {
+    }
+
+    /**
      * Terminates a call.
      *
      * @param reason reason code to terminate a call, defined in {@link ImsReasonInfo}.
diff --git a/telephony/java/com/android/ims/internal/IImsCallSession.aidl b/telephony/java/com/android/ims/internal/IImsCallSession.aidl
index 15234e5..0466efc 100644
--- a/telephony/java/com/android/ims/internal/IImsCallSession.aidl
+++ b/telephony/java/com/android/ims/internal/IImsCallSession.aidl
@@ -18,7 +18,6 @@
 
 import android.os.Message;
 import android.telephony.ims.aidl.IImsCallSessionListener;
-
 import android.telephony.ims.ImsCallProfile;
 import android.telephony.ims.ImsStreamMediaProfile;
 import com.android.ims.internal.IImsVideoCallProvider;
@@ -151,6 +150,22 @@
     void reject(int reason);
 
     /**
+     * Transfer an established call to given number
+     *
+     * @param number number to transfer the call
+     * @param isConfirmationRequired if {@code True}, indicates Assured transfer,
+     * if {@code False} it indicates Blind transfer.
+     */
+    void transfer(String number, boolean isConfirmationRequired);
+
+    /**
+     * Transfer an established call to another call session
+     *
+     * @param transferToSession The other ImsCallSession to transfer the ongoing session to.
+     */
+    void consultativeTransfer(in IImsCallSession transferToSession);
+
+    /**
      * Terminates a call.
      *
      * @see Listener#callSessionTerminated
diff --git a/telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl b/telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl
index b33a9f1..1c62cc4 100644
--- a/telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl
+++ b/telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl
@@ -184,6 +184,12 @@
     void callSessionRttAudioIndicatorChanged(in ImsStreamMediaProfile profile);
 
     /**
+     * Notifies about the response for call transfer request.
+     */
+    void callSessionTransferred();
+
+    void callSessionTransferFailed(in ImsReasonInfo reasonInfo);
+    /**
      * Notifies of a change to the call quality.
      * @param callQuality then updated call quality
      */
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index 616b6b0..248c117 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -28,7 +28,7 @@
         ":framework_native_aidl",
     ],
     libs: [
-        "framework-all",
+        "framework",
         "app-compat-annotations",
         "unsupportedappusage",
     ],
diff --git a/tests/BootImageProfileTest/TEST_MAPPING b/tests/BootImageProfileTest/DISABLED_TEST_MAPPING
similarity index 100%
rename from tests/BootImageProfileTest/TEST_MAPPING
rename to tests/BootImageProfileTest/DISABLED_TEST_MAPPING
diff --git a/tests/net/AndroidManifest.xml b/tests/net/AndroidManifest.xml
index 638b6d1..480b12b 100644
--- a/tests/net/AndroidManifest.xml
+++ b/tests/net/AndroidManifest.xml
@@ -50,6 +50,7 @@
 
     <application>
         <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.net.ipsec.ike" />
     </application>
 
     <instrumentation
diff --git a/tests/net/common/java/android/net/LinkPropertiesTest.java b/tests/net/common/java/android/net/LinkPropertiesTest.java
index 3f311c9..6ec2cd6 100644
--- a/tests/net/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/net/common/java/android/net/LinkPropertiesTest.java
@@ -315,7 +315,7 @@
         source.addDnsServer(DNS1);
         source.addDnsServer(DNS2);
         // set 2 gateways
-        source.addRoute(new RouteInfo(GATEWAY1));
+        source.addRoute(new RouteInfo(LINKADDRV4, GATEWAY1));
         source.addRoute(new RouteInfo(GATEWAY2));
         source.setMtu(MTU);
 
@@ -327,7 +327,7 @@
         target.addDnsServer(DNS2);
         target.addDnsServer(DNS1);
         target.addRoute(new RouteInfo(GATEWAY2));
-        target.addRoute(new RouteInfo(GATEWAY1));
+        target.addRoute(new RouteInfo(LINKADDRV4, GATEWAY1));
         target.setMtu(MTU);
 
         assertLinkPropertiesEqual(source, target);
@@ -364,12 +364,13 @@
 
     @Test
     public void testRouteInterfaces() {
-        LinkAddress prefix = new LinkAddress(address("2001:db8::"), 32);
+        LinkAddress prefix1 = new LinkAddress(address("2001:db8:1::"), 48);
+        LinkAddress prefix2 = new LinkAddress(address("2001:db8:2::"), 48);
         InetAddress address = ADDRV6;
 
         // Add a route with no interface to a LinkProperties with no interface. No errors.
         LinkProperties lp = new LinkProperties();
-        RouteInfo r = new RouteInfo(prefix, address, null);
+        RouteInfo r = new RouteInfo(prefix1, address, null);
         assertTrue(lp.addRoute(r));
         assertEquals(1, lp.getRoutes().size());
         assertAllRoutesHaveInterface(null, lp);
@@ -379,7 +380,7 @@
         assertEquals(1, lp.getRoutes().size());
 
         // Add a route with an interface. Expect an exception.
-        r = new RouteInfo(prefix, address, "wlan0");
+        r = new RouteInfo(prefix2, address, "wlan0");
         try {
           lp.addRoute(r);
           fail("Adding wlan0 route to LP with no interface, expect exception");
@@ -398,7 +399,7 @@
         } catch (IllegalArgumentException expected) {}
 
         // If the interface name matches, the route is added.
-        r = new RouteInfo(prefix, null, "wlan0");
+        r = new RouteInfo(prefix2, null, "wlan0");
         lp.setInterfaceName("wlan0");
         lp.addRoute(r);
         assertEquals(2, lp.getRoutes().size());
@@ -423,10 +424,12 @@
         assertEquals(3, lp.compareAllRoutes(lp2).added.size());
         assertEquals(3, lp.compareAllRoutes(lp2).removed.size());
 
-        // Check remove works
-        lp.removeRoute(new RouteInfo(prefix, address, null));
+        // Remove route with incorrect interface, no route removed.
+        lp.removeRoute(new RouteInfo(prefix2, null, null));
         assertEquals(3, lp.getRoutes().size());
-        lp.removeRoute(new RouteInfo(prefix, address, "wlan0"));
+
+        // Check remove works when interface is correct.
+        lp.removeRoute(new RouteInfo(prefix2, null, "wlan0"));
         assertEquals(2, lp.getRoutes().size());
         assertAllRoutesHaveInterface("wlan0", lp);
         assertAllRoutesNotHaveInterface("p2p0", lp);
diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt b/tests/net/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
index 490c467..23caf49 100644
--- a/tests/net/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
+++ b/tests/net/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
@@ -26,6 +26,7 @@
 import android.os.IBinder
 import com.android.networkstack.metrics.DataStallStatsUtils
 import com.android.networkstack.netlink.TcpSocketTracker
+import com.android.server.NetworkStackService
 import com.android.server.NetworkStackService.NetworkMonitorConnector
 import com.android.server.NetworkStackService.NetworkStackConnector
 import com.android.server.connectivity.NetworkMonitor
@@ -88,6 +89,7 @@
             val nm = NetworkMonitor(this@TestNetworkStackService, cb,
                     this.network,
                     mock(IpConnectivityLog::class.java), mock(SharedLog::class.java),
+                    mock(NetworkStackService.NetworkStackServiceManager::class.java),
                     NetworkMonitorDeps(privateDnsBypassNetwork),
                     mock(DataStallStatsUtils::class.java),
                     mock(TcpSocketTracker::class.java))
diff --git a/tests/net/java/android/net/Ikev2VpnProfileTest.java b/tests/net/java/android/net/Ikev2VpnProfileTest.java
index d6a2176..2273bc6 100644
--- a/tests/net/java/android/net/Ikev2VpnProfileTest.java
+++ b/tests/net/java/android/net/Ikev2VpnProfileTest.java
@@ -22,7 +22,6 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
 
 import android.test.mock.MockContext;
 
@@ -232,10 +231,12 @@
         builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
         final VpnProfile profile = builder.build().toVpnProfile();
 
+        final String expectedSecret = Ikev2VpnProfile.PREFIX_INLINE
+                + Ikev2VpnProfile.encodeForIpsecSecret(mPrivateKey.getEncoded());
         verifyVpnProfileCommon(profile);
         assertEquals(Ikev2VpnProfile.certificateToPemString(mUserCert), profile.ipsecUserCert);
         assertEquals(
-                Ikev2VpnProfile.encodeForIpsecSecret(mPrivateKey.getEncoded()),
+                expectedSecret,
                 profile.ipsecSecret);
         assertEquals(Ikev2VpnProfile.certificateToPemString(mServerRootCa), profile.ipsecCaCert);
 
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 8ed497b..86ba8af 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -204,6 +204,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.security.KeyStore;
 import android.system.Os;
 import android.test.mock.MockContentResolver;
 import android.text.TextUtils;
@@ -1019,7 +1020,7 @@
 
         public MockVpn(int userId) {
             super(startHandlerThreadAndReturnLooper(), mServiceContext, mNetworkManagementService,
-                    userId);
+                    userId, mock(KeyStore.class));
         }
 
         public void setNetworkAgent(TestNetworkAgentWrapper agent) {
@@ -1179,6 +1180,10 @@
                 Arrays.asList(new UserInfo[] {
                         new UserInfo(VPN_USER, "", 0),
                 }));
+        final ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.targetSdkVersion = Build.VERSION_CODES.Q;
+        when(mPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), any()))
+                .thenReturn(applicationInfo);
 
         // InstrumentationTestRunner prepares a looper, but AndroidJUnitRunner does not.
         // http://b/25897652 .
@@ -3040,7 +3045,7 @@
             networkCapabilities.addTransportType(TRANSPORT_WIFI)
                     .setNetworkSpecifier(new MatchAllNetworkSpecifier());
             mService.requestNetwork(networkCapabilities, null, 0, null,
-                    ConnectivityManager.TYPE_WIFI, TEST_PACKAGE_NAME);
+                    ConnectivityManager.TYPE_WIFI, mContext.getPackageName());
         });
 
         class NonParcelableSpecifier extends NetworkSpecifier {
@@ -6437,17 +6442,89 @@
         assertEquals(wifiLp, mService.getActiveLinkProperties());
     }
 
+    private void setupLocationPermissions(
+            int targetSdk, boolean locationToggle, String op, String perm) throws Exception {
+        final ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.targetSdkVersion = targetSdk;
+        when(mPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), any()))
+                .thenReturn(applicationInfo);
+
+        when(mLocationManager.isLocationEnabledForUser(any())).thenReturn(locationToggle);
+
+        if (op != null) {
+            when(mAppOpsManager.noteOp(eq(op), eq(Process.myUid()), eq(mContext.getPackageName())))
+                .thenReturn(AppOpsManager.MODE_ALLOWED);
+        }
+
+        if (perm != null) {
+            mServiceContext.setPermission(perm, PERMISSION_GRANTED);
+        }
+    }
+
+    private int getOwnerUidNetCapsForCallerPermission(int ownerUid, int callerUid) {
+        final NetworkCapabilities netCap = new NetworkCapabilities().setOwnerUid(ownerUid);
+
+        return mService
+                .maybeSanitizeLocationInfoForCaller(netCap, callerUid, mContext.getPackageName())
+                .getOwnerUid();
+    }
+
     @Test
-    public void testNetworkCapabilitiesRestrictedForCallerPermissions() {
-        int callerUid = Process.myUid();
-        final NetworkCapabilities originalNc = new NetworkCapabilities();
-        originalNc.setOwnerUid(callerUid);
+    public void testMaybeSanitizeLocationInfoForCallerWithFineLocationAfterQ() throws Exception {
+        setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION,
+                Manifest.permission.ACCESS_FINE_LOCATION);
 
-        final NetworkCapabilities newNc =
-                mService.networkCapabilitiesRestrictedForCallerPermissions(
-                        originalNc, Process.myPid(), callerUid);
+        final int myUid = Process.myUid();
+        assertEquals(myUid, getOwnerUidNetCapsForCallerPermission(myUid, myUid));
+    }
 
-        assertEquals(Process.INVALID_UID, newNc.getOwnerUid());
+    @Test
+    public void testMaybeSanitizeLocationInfoForCallerWithCoarseLocationPreQ() throws Exception {
+        setupLocationPermissions(Build.VERSION_CODES.P, true, AppOpsManager.OPSTR_COARSE_LOCATION,
+                Manifest.permission.ACCESS_COARSE_LOCATION);
+
+        final int myUid = Process.myUid();
+        assertEquals(myUid, getOwnerUidNetCapsForCallerPermission(myUid, myUid));
+    }
+
+    @Test
+    public void testMaybeSanitizeLocationInfoForCallerLocationOff() throws Exception {
+        // Test that even with fine location permission, and UIDs matching, the UID is sanitized.
+        setupLocationPermissions(Build.VERSION_CODES.Q, false, AppOpsManager.OPSTR_FINE_LOCATION,
+                Manifest.permission.ACCESS_FINE_LOCATION);
+
+        final int myUid = Process.myUid();
+        assertEquals(Process.INVALID_UID, getOwnerUidNetCapsForCallerPermission(myUid, myUid));
+    }
+
+    @Test
+    public void testMaybeSanitizeLocationInfoForCallerWrongUid() throws Exception {
+        // Test that even with fine location permission, not being the owner leads to sanitization.
+        setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION,
+                Manifest.permission.ACCESS_FINE_LOCATION);
+
+        final int myUid = Process.myUid();
+        assertEquals(Process.INVALID_UID, getOwnerUidNetCapsForCallerPermission(myUid + 1, myUid));
+    }
+
+    @Test
+    public void testMaybeSanitizeLocationInfoForCallerWithCoarseLocationAfterQ() throws Exception {
+        // Test that not having fine location permission leads to sanitization.
+        setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_COARSE_LOCATION,
+                Manifest.permission.ACCESS_COARSE_LOCATION);
+
+        // Test that without the location permission, the owner field is sanitized.
+        final int myUid = Process.myUid();
+        assertEquals(Process.INVALID_UID, getOwnerUidNetCapsForCallerPermission(myUid, myUid));
+    }
+
+    @Test
+    public void testMaybeSanitizeLocationInfoForCallerWithoutLocationPermission() throws Exception {
+        setupLocationPermissions(Build.VERSION_CODES.Q, true, null /* op */, null /* perm */);
+
+        // Test that without the location permission, the owner field is sanitized.
+        final int myUid = Process.myUid();
+        assertEquals(Process.INVALID_UID, getOwnerUidNetCapsForCallerPermission(myUid, myUid));
     }
 
     private void setupConnectionOwnerUid(int vpnOwnerUid, @VpnManager.VpnType int vpnType)
@@ -6733,21 +6810,6 @@
                         mContext.getOpPackageName()));
     }
 
-    private void setupLocationPermissions(
-            int targetSdk, boolean locationToggle, String op, String perm) throws Exception {
-        final ApplicationInfo applicationInfo = new ApplicationInfo();
-        applicationInfo.targetSdkVersion = targetSdk;
-        when(mPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), any()))
-                .thenReturn(applicationInfo);
-
-        when(mLocationManager.isLocationEnabledForUser(any())).thenReturn(locationToggle);
-
-        when(mAppOpsManager.noteOp(eq(op), eq(Process.myUid()), eq(mContext.getPackageName())))
-                .thenReturn(AppOpsManager.MODE_ALLOWED);
-
-        mServiceContext.setPermission(perm, PERMISSION_GRANTED);
-    }
-
     private void setUpConnectivityDiagnosticsCallback() throws Exception {
         final NetworkRequest request = new NetworkRequest.Builder().build();
         when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder);
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index ac1c518..1994d1f 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -59,9 +59,15 @@
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.net.ConnectivityManager;
+import android.net.Ikev2VpnProfile;
+import android.net.InetAddresses;
+import android.net.IpPrefix;
+import android.net.IpSecManager;
+import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo.DetailedState;
+import android.net.RouteInfo;
 import android.net.UidRange;
 import android.net.VpnManager;
 import android.net.VpnService;
@@ -72,6 +78,7 @@
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.Settings;
 import android.security.Credentials;
 import android.security.KeyStore;
 import android.util.ArrayMap;
@@ -83,6 +90,7 @@
 import com.android.internal.R;
 import com.android.internal.net.VpnConfig;
 import com.android.internal.net.VpnProfile;
+import com.android.server.IpSecService;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -92,6 +100,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.net.Inet4Address;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -124,6 +133,9 @@
     }
 
     static final String TEST_VPN_PKG = "com.dummy.vpn";
+    private static final String TEST_VPN_SERVER = "1.2.3.4";
+    private static final String TEST_VPN_IDENTITY = "identity";
+    private static final byte[] TEST_VPN_PSK = "psk".getBytes();
 
     /**
      * Names and UIDs for some fake packages. Important points:
@@ -150,23 +162,39 @@
     @Mock private Vpn.SystemServices mSystemServices;
     @Mock private Vpn.Ikev2SessionCreator mIkev2SessionCreator;
     @Mock private ConnectivityManager mConnectivityManager;
+    @Mock private IpSecService mIpSecService;
     @Mock private KeyStore mKeyStore;
-    private final VpnProfile mVpnProfile = new VpnProfile("key");
+    private final VpnProfile mVpnProfile;
+
+    private IpSecManager mIpSecManager;
+
+    public VpnTest() throws Exception {
+        // Build an actual VPN profile that is capable of being converted to and from an
+        // Ikev2VpnProfile
+        final Ikev2VpnProfile.Builder builder =
+                new Ikev2VpnProfile.Builder(TEST_VPN_SERVER, TEST_VPN_IDENTITY);
+        builder.setAuthPsk(TEST_VPN_PSK);
+        mVpnProfile = builder.build().toVpnProfile();
+    }
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
+        mIpSecManager = new IpSecManager(mContext, mIpSecService);
+
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         setMockedPackages(mPackages);
 
-        when(mContext.getPackageName()).thenReturn(Vpn.class.getPackage().getName());
+        when(mContext.getPackageName()).thenReturn(TEST_VPN_PKG);
+        when(mContext.getOpPackageName()).thenReturn(TEST_VPN_PKG);
         when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager);
         when(mContext.getSystemService(eq(Context.APP_OPS_SERVICE))).thenReturn(mAppOps);
         when(mContext.getSystemService(eq(Context.NOTIFICATION_SERVICE)))
                 .thenReturn(mNotificationManager);
         when(mContext.getSystemService(eq(Context.CONNECTIVITY_SERVICE)))
                 .thenReturn(mConnectivityManager);
+        when(mContext.getSystemService(eq(Context.IPSEC_SERVICE))).thenReturn(mIpSecManager);
         when(mContext.getString(R.string.config_customVpnAlwaysOnDisconnectedDialogComponent))
                 .thenReturn(Resources.getSystem().getString(
                         R.string.config_customVpnAlwaysOnDisconnectedDialogComponent));
@@ -260,17 +288,17 @@
         assertFalse(vpn.getLockdown());
 
         // Set always-on without lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, Collections.emptyList()));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, Collections.emptyList(), mKeyStore));
         assertTrue(vpn.getAlwaysOn());
         assertFalse(vpn.getLockdown());
 
         // Set always-on with lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.emptyList()));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.emptyList(), mKeyStore));
         assertTrue(vpn.getAlwaysOn());
         assertTrue(vpn.getLockdown());
 
         // Remove always-on configuration.
-        assertTrue(vpn.setAlwaysOnPackage(null, false, Collections.emptyList()));
+        assertTrue(vpn.setAlwaysOnPackage(null, false, Collections.emptyList(), mKeyStore));
         assertFalse(vpn.getAlwaysOn());
         assertFalse(vpn.getLockdown());
     }
@@ -284,11 +312,11 @@
         assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
 
         // Set always-on without lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, null));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, null, mKeyStore));
         assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
 
         // Set always-on with lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null, mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
             new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
             new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
@@ -297,7 +325,7 @@
         assertUnblocked(vpn, user.start + PKG_UIDS[1]);
 
         // Switch to another app.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null, mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
             new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
             new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
@@ -316,7 +344,8 @@
         final UidRange user = UidRange.createForUser(primaryUser.id);
 
         // Set always-on with lockdown and whitelist app PKGS[2] from lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.singletonList(PKGS[2])));
+        assertTrue(vpn.setAlwaysOnPackage(
+                PKGS[1], true, Collections.singletonList(PKGS[2]), mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
                 new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
                 new UidRange(user.start + PKG_UIDS[2] + 1, user.stop)
@@ -325,7 +354,8 @@
         assertUnblocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]);
 
         // Change whitelisted app to PKGS[3].
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.singletonList(PKGS[3])));
+        assertTrue(vpn.setAlwaysOnPackage(
+                PKGS[1], true, Collections.singletonList(PKGS[3]), mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
                 new UidRange(user.start + PKG_UIDS[2] + 1, user.stop)
         }));
@@ -337,7 +367,8 @@
         assertUnblocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[3]);
 
         // Change the VPN app.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList(PKGS[3])));
+        assertTrue(vpn.setAlwaysOnPackage(
+                PKGS[0], true, Collections.singletonList(PKGS[3]), mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
                 new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
                 new UidRange(user.start + PKG_UIDS[1] + 1, user.start + PKG_UIDS[3] - 1)
@@ -350,7 +381,7 @@
         assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[3]);
 
         // Remove the whitelist.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null, mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
                 new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[3] - 1),
                 new UidRange(user.start + PKG_UIDS[3] + 1, user.stop)
@@ -363,7 +394,8 @@
         assertUnblocked(vpn, user.start + PKG_UIDS[0]);
 
         // Add the whitelist.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList(PKGS[1])));
+        assertTrue(vpn.setAlwaysOnPackage(
+                PKGS[0], true, Collections.singletonList(PKGS[1]), mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
                 new UidRange(user.start + PKG_UIDS[0] + 1, user.stop)
         }));
@@ -375,12 +407,13 @@
         assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1]);
 
         // Try whitelisting a package with a comma, should be rejected.
-        assertFalse(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList("a.b,c.d")));
+        assertFalse(vpn.setAlwaysOnPackage(
+                PKGS[0], true, Collections.singletonList("a.b,c.d"), mKeyStore));
 
         // Pass a non-existent packages in the whitelist, they (and only they) should be ignored.
         // Whitelisted package should change from PGKS[1] to PKGS[2].
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true,
-                Arrays.asList("com.foo.app", PKGS[2], "com.bar.app")));
+        assertTrue(vpn.setAlwaysOnPackage(
+                PKGS[0], true, Arrays.asList("com.foo.app", PKGS[2], "com.bar.app"), mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[]{
                 new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[1] - 1),
                 new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
@@ -405,7 +438,7 @@
         final UidRange profile = UidRange.createForUser(tempProfile.id);
 
         // Set lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null, mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
             new UidRange(user.start, user.start + PKG_UIDS[3] - 1),
             new UidRange(user.start + PKG_UIDS[3] + 1, user.stop)
@@ -499,22 +532,22 @@
                 .thenReturn(Collections.singletonList(resInfo));
 
         // null package name should return false
-        assertFalse(vpn.isAlwaysOnPackageSupported(null));
+        assertFalse(vpn.isAlwaysOnPackageSupported(null, mKeyStore));
 
         // Pre-N apps are not supported
         appInfo.targetSdkVersion = VERSION_CODES.M;
-        assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0]));
+        assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0], mKeyStore));
 
         // N+ apps are supported by default
         appInfo.targetSdkVersion = VERSION_CODES.N;
-        assertTrue(vpn.isAlwaysOnPackageSupported(PKGS[0]));
+        assertTrue(vpn.isAlwaysOnPackageSupported(PKGS[0], mKeyStore));
 
         // Apps that opt out explicitly are not supported
         appInfo.targetSdkVersion = VERSION_CODES.CUR_DEVELOPMENT;
         Bundle metaData = new Bundle();
         metaData.putBoolean(VpnService.SERVICE_META_DATA_SUPPORTS_ALWAYS_ON, false);
         svcInfo.metaData = metaData;
-        assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0]));
+        assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0], mKeyStore));
     }
 
     @Test
@@ -531,7 +564,7 @@
                 .cancelAsUser(anyString(), anyInt(), eq(userHandle));
 
         // Start showing a notification for disconnected once always-on.
-        vpn.setAlwaysOnPackage(PKGS[0], false, null);
+        vpn.setAlwaysOnPackage(PKGS[0], false, null, mKeyStore);
         order.verify(mNotificationManager)
                 .notifyAsUser(anyString(), anyInt(), any(), eq(userHandle));
 
@@ -545,7 +578,7 @@
                 .notifyAsUser(anyString(), anyInt(), any(), eq(userHandle));
 
         // Notification should be cleared after unsetting always-on package.
-        vpn.setAlwaysOnPackage(null, false, null);
+        vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
         order.verify(mNotificationManager).cancelAsUser(anyString(), anyInt(), eq(userHandle));
     }
 
@@ -920,12 +953,68 @@
                         eq(AppOpsManager.MODE_IGNORED));
     }
 
+    private void setAndVerifyAlwaysOnPackage(Vpn vpn, int uid, boolean lockdownEnabled) {
+        assertTrue(vpn.setAlwaysOnPackage(TEST_VPN_PKG, lockdownEnabled, null, mKeyStore));
+
+        verify(mKeyStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
+        verify(mAppOps).setMode(
+                eq(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN), eq(uid), eq(TEST_VPN_PKG),
+                eq(AppOpsManager.MODE_ALLOWED));
+
+        verify(mSystemServices).settingsSecurePutStringForUser(
+                eq(Settings.Secure.ALWAYS_ON_VPN_APP), eq(TEST_VPN_PKG), eq(primaryUser.id));
+        verify(mSystemServices).settingsSecurePutIntForUser(
+                eq(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN), eq(lockdownEnabled ? 1 : 0),
+                eq(primaryUser.id));
+        verify(mSystemServices).settingsSecurePutStringForUser(
+                eq(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST), eq(""), eq(primaryUser.id));
+    }
+
+    @Test
+    public void testSetAndStartAlwaysOnVpn() throws Exception {
+        final Vpn vpn = createVpn(primaryUser.id);
+        setMockedUsers(primaryUser);
+
+        // UID checks must return a different UID; otherwise it'll be treated as already prepared.
+        final int uid = Process.myUid() + 1;
+        when(mPackageManager.getPackageUidAsUser(eq(TEST_VPN_PKG), anyInt()))
+                .thenReturn(uid);
+        when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
+                .thenReturn(mVpnProfile.encode());
+
+        setAndVerifyAlwaysOnPackage(vpn, uid, false);
+        assertTrue(vpn.startAlwaysOnVpn(mKeyStore));
+
+        // TODO: Test the Ikev2VpnRunner started up properly. Relies on utility methods added in
+        // a subsequent CL.
+    }
+
+    @Test
+    public void testStartLegacyVpn() throws Exception {
+        final Vpn vpn = createVpn(primaryUser.id);
+        setMockedUsers(primaryUser);
+
+        // Dummy egress interface
+        final String egressIface = "DUMMY0";
+        final LinkProperties lp = new LinkProperties();
+        lp.setInterfaceName(egressIface);
+
+        final RouteInfo defaultRoute = new RouteInfo(new IpPrefix(Inet4Address.ANY, 0),
+                        InetAddresses.parseNumericAddress("192.0.2.0"), egressIface);
+        lp.addRoute(defaultRoute);
+
+        vpn.startLegacyVpn(mVpnProfile, mKeyStore, lp);
+
+        // TODO: Test the Ikev2VpnRunner started up properly. Relies on utility methods added in
+        // a subsequent CL.
+    }
+
     /**
      * Mock some methods of vpn object.
      */
     private Vpn createVpn(@UserIdInt int userId) {
         return new Vpn(Looper.myLooper(), mContext, mNetService,
-                userId, mSystemServices, mIkev2SessionCreator);
+                userId, mKeyStore, mSystemServices, mIkev2SessionCreator);
     }
 
     private static void assertBlocked(Vpn vpn, int... uids) {
diff --git a/tools/hiddenapi/merge_csv.py b/tools/hiddenapi/merge_csv.py
index 9661927..6a5b0e1 100755
--- a/tools/hiddenapi/merge_csv.py
+++ b/tools/hiddenapi/merge_csv.py
@@ -14,26 +14,56 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 """
-Merge mutliple CSV files, possibly with different columns, writing to stdout.
+Merge multiple CSV files, possibly with different columns.
 """
 
+import argparse
 import csv
-import sys
+import io
 
-csv_readers = [
-    csv.DictReader(open(csv_file, 'r'), delimiter=',', quotechar='|')
-    for csv_file in sys.argv[1:]
-]
+from zipfile import ZipFile
 
-# Build union of all columns from source files:
+args_parser = argparse.ArgumentParser(description='Merge given CSV files into a single one.')
+args_parser.add_argument('--header', help='Comma separated field names; '
+                                          'if missing determines the header from input files.')
+args_parser.add_argument('--zip_input', help='ZIP archive with all CSV files to merge.')
+args_parser.add_argument('--output', help='Output file for merged CSV.',
+                         default='-', type=argparse.FileType('w'))
+args_parser.add_argument('files', nargs=argparse.REMAINDER)
+args = args_parser.parse_args()
+
+
+def dict_reader(input):
+    return csv.DictReader(input, delimiter=',', quotechar='|')
+
+
+if args.zip_input and len(args.files) > 0:
+    raise ValueError('Expecting either a single ZIP with CSV files'
+                     ' or a list of CSV files as input; not both.')
+
+csv_readers = []
+if len(args.files) > 0:
+    for file in args.files:
+        csv_readers.append(dict_reader(open(file, 'r')))
+elif args.zip_input:
+    with ZipFile(args.zip_input) as zip:
+        for entry in zip.namelist():
+            if entry.endswith('.uau'):
+                csv_readers.append(dict_reader(io.TextIOWrapper(zip.open(entry, 'r'))))
+
 headers = set()
-for reader in csv_readers:
-    headers = headers.union(reader.fieldnames)
+if args.header:
+    fieldnames = args.header.split(',')
+else:
+    # Build union of all columns from source files:
+    for reader in csv_readers:
+        headers = headers.union(reader.fieldnames)
+    fieldnames = sorted(headers)
 
 # Concatenate all files to output:
-out = csv.DictWriter(sys.stdout, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL,
-                     dialect='unix', fieldnames=sorted(headers))
-out.writeheader()
+writer = csv.DictWriter(args.output, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL,
+                        dialect='unix', fieldnames=fieldnames)
+writer.writeheader()
 for reader in csv_readers:
     for row in reader:
-        out.writerow(row)
+        writer.writerow(row)