Merge "Media: Align cast device names properly in RTL languages." into qt-dev
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index b9a4b52..f53ac1b 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -298,6 +298,7 @@
         VehicleMapServicePacketFailureReported vms_packet_failure_reported = 202;
         CarPowerStateChanged car_power_state_changed = 203;
         GarageModeInfo garage_mode_info = 204;
+        TestAtomReported test_atom_reported = 205 [(log_from_module) = "cts"];
     }
 
     // Pulled events will start at field 10000.
@@ -3342,6 +3343,23 @@
     optional int32 user_id = 8;
 }
 
+/* Test atom, is not logged anywhere */
+message TestAtomReported {
+    repeated AttributionNode attribution_node = 1;
+    optional int32 int_field = 2;
+    optional int64 long_field = 3;
+    optional float float_field = 4;
+    optional string string_field = 5;
+    optional bool boolean_field = 6;
+    enum State {
+        UNKNOWN = 0;
+        OFF = 1;
+        ON = 2;
+    }
+    optional State state = 7;
+    optional TrainExperimentIds bytes_field = 8 [(android.os.statsd.log_mode) = MODE_BYTES];
+}
+
 /** Represents USB port overheat event. */
 message UsbPortOverheatEvent {
     /* Temperature of USB port at USB plug event, in 1/10ths of degree C. */
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index b4c6d94..789351e 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -8832,8 +8832,8 @@
              * <p>Setting this flag is optional; it defaults to false.</p>
              */
             @NonNull
-            public BubbleMetadata.Builder setSuppressNotification(boolean shouldSupressNotif) {
-                setFlag(FLAG_SUPPRESS_NOTIFICATION, shouldSupressNotif);
+            public BubbleMetadata.Builder setSuppressNotification(boolean shouldSuppressNotif) {
+                setFlag(FLAG_SUPPRESS_NOTIFICATION, shouldSuppressNotif);
                 return this;
             }
 
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 8046776..2c5860a 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -825,7 +825,9 @@
      * @param sortOrder How to order the rows, formatted as an SQL ORDER BY
      *         clause (excluding the ORDER BY itself). Passing null will use the
      *         default sort order, which may be unordered.
-     * @return A Cursor object, which is positioned before the first entry, or null
+     * @return A Cursor object, which is positioned before the first entry. May return
+     *         <code>null</code> if the underlying content provider returns <code>null</code>,
+     *         or if it crashes.
      * @see Cursor
      */
     public final @Nullable Cursor query(@RequiresPermission.Read @NonNull Uri uri,
@@ -865,7 +867,9 @@
      * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
      * If the operation is canceled, then {@link OperationCanceledException} will be thrown
      * when the query is executed.
-     * @return A Cursor object, which is positioned before the first entry, or null
+     * @return A Cursor object, which is positioned before the first entry. May return
+     *         <code>null</code> if the underlying content provider returns <code>null</code>,
+     *         or if it crashes.
      * @see Cursor
      */
     public final @Nullable Cursor query(@RequiresPermission.Read @NonNull Uri uri,
@@ -902,7 +906,9 @@
      * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
      * If the operation is canceled, then {@link OperationCanceledException} will be thrown
      * when the query is executed.
-     * @return A Cursor object, which is positioned before the first entry, or null
+     * @return A Cursor object, which is positioned before the first entry. May return
+     *         <code>null</code> if the underlying content provider returns <code>null</code>,
+     *         or if it crashes.
      * @see Cursor
      */
     @Override
@@ -1799,7 +1805,8 @@
      * @param url The URL of the table to insert into.
      * @param values The initial values for the newly inserted row. The key is the column name for
      *               the field. Passing an empty ContentValues will create an empty row.
-     * @return the URL of the newly created row.
+     * @return the URL of the newly created row. May return <code>null</code> if the underlying
+     *         content provider returns <code>null</code>, or if it crashes.
      */
     @Override
     public final @Nullable Uri insert(@RequiresPermission.Write @NonNull Uri url,
diff --git a/core/java/android/net/SocketKeepalive.java b/core/java/android/net/SocketKeepalive.java
index 9d91620..46eddde 100644
--- a/core/java/android/net/SocketKeepalive.java
+++ b/core/java/android/net/SocketKeepalive.java
@@ -43,6 +43,10 @@
  * To stop an existing keepalive, call {@link SocketKeepalive#stop}. The system will call
  * {@link SocketKeepalive.Callback#onStopped} if the operation was successful or
  * {@link SocketKeepalive.Callback#onError} if an error occurred.
+ *
+ * The device SHOULD support keepalive offload. If it does not, it MUST reply with
+ * {@link SocketKeepalive.Callback#onError} with {@code ERROR_UNSUPPORTED} to any keepalive offload
+ * request. If it does, it MUST support at least 3 concurrent keepalive slots per transport.
  */
 public abstract class SocketKeepalive implements AutoCloseable {
     static final String TAG = "SocketKeepalive";
diff --git a/core/java/android/net/metrics/ApfProgramEvent.java b/core/java/android/net/metrics/ApfProgramEvent.java
index 2bd4378..e9c209c 100644
--- a/core/java/android/net/metrics/ApfProgramEvent.java
+++ b/core/java/android/net/metrics/ApfProgramEvent.java
@@ -176,7 +176,7 @@
         out.writeInt(filteredRas);
         out.writeInt(currentRas);
         out.writeInt(programLength);
-        out.writeInt(flags);
+        out.writeInt(this.flags);
     }
 
     /** @hide */
@@ -192,6 +192,18 @@
                 programLength, actualLifetime, lifetimeString, namesOf(flags));
     }
 
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null || !(obj.getClass().equals(ApfProgramEvent.class))) return false;
+        final ApfProgramEvent other = (ApfProgramEvent) obj;
+        return lifetime == other.lifetime
+                && actualLifetime == other.actualLifetime
+                && filteredRas == other.filteredRas
+                && currentRas == other.currentRas
+                && programLength == other.programLength
+                && flags == other.flags;
+    }
+
     /** @hide */
     public static final @android.annotation.NonNull Parcelable.Creator<ApfProgramEvent> CREATOR
             = new Parcelable.Creator<ApfProgramEvent>() {
diff --git a/core/java/android/net/metrics/ApfStats.java b/core/java/android/net/metrics/ApfStats.java
index 6c3b7af..b963777 100644
--- a/core/java/android/net/metrics/ApfStats.java
+++ b/core/java/android/net/metrics/ApfStats.java
@@ -275,6 +275,22 @@
                 .toString();
     }
 
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null || !(obj.getClass().equals(ApfStats.class))) return false;
+        final ApfStats other = (ApfStats) obj;
+        return durationMs == other.durationMs
+                && receivedRas == other.receivedRas
+                && matchingRas == other.matchingRas
+                && droppedRas == other.droppedRas
+                && zeroLifetimeRas == other.zeroLifetimeRas
+                && parseErrors == other.parseErrors
+                && programUpdates == other.programUpdates
+                && programUpdatesAll == other.programUpdatesAll
+                && programUpdatesAllowingMulticast == other.programUpdatesAllowingMulticast
+                && maxProgramSize == other.maxProgramSize;
+    }
+
     /** @hide */
     public static final @android.annotation.NonNull Parcelable.Creator<ApfStats> CREATOR = new Parcelable.Creator<ApfStats>() {
         public ApfStats createFromParcel(Parcel in) {
diff --git a/core/java/android/net/metrics/DhcpClientEvent.java b/core/java/android/net/metrics/DhcpClientEvent.java
index a3d0a20..2fed736 100644
--- a/core/java/android/net/metrics/DhcpClientEvent.java
+++ b/core/java/android/net/metrics/DhcpClientEvent.java
@@ -22,6 +22,7 @@
 import android.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.text.TextUtils;
 
 /**
  * An event recorded when a DhcpClient state machine transitions to a new state.
@@ -101,6 +102,14 @@
         return String.format("DhcpClientEvent(%s, %dms)", msg, durationMs);
     }
 
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null || !(obj.getClass().equals(DhcpClientEvent.class))) return false;
+        final DhcpClientEvent other = (DhcpClientEvent) obj;
+        return TextUtils.equals(msg, other.msg)
+                && durationMs == other.durationMs;
+    }
+
     /** @hide */
     public static final @android.annotation.NonNull Parcelable.Creator<DhcpClientEvent> CREATOR
         = new Parcelable.Creator<DhcpClientEvent>() {
diff --git a/core/java/android/net/metrics/IpManagerEvent.java b/core/java/android/net/metrics/IpManagerEvent.java
index 9d358d1..ba05c59 100644
--- a/core/java/android/net/metrics/IpManagerEvent.java
+++ b/core/java/android/net/metrics/IpManagerEvent.java
@@ -101,6 +101,14 @@
                 Decoder.constants.get(eventType), durationMs);
     }
 
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null || !(obj.getClass().equals(IpManagerEvent.class))) return false;
+        final IpManagerEvent other = (IpManagerEvent) obj;
+        return eventType == other.eventType
+                && durationMs == other.durationMs;
+    }
+
     final static class Decoder {
         static final SparseArray<String> constants = MessageUtils.findMessageNames(
                 new Class[]{IpManagerEvent.class},
diff --git a/core/java/android/net/metrics/IpReachabilityEvent.java b/core/java/android/net/metrics/IpReachabilityEvent.java
index 80c8211..d4ba294 100644
--- a/core/java/android/net/metrics/IpReachabilityEvent.java
+++ b/core/java/android/net/metrics/IpReachabilityEvent.java
@@ -93,6 +93,13 @@
         return String.format("IpReachabilityEvent(%s:%02x)", eventName, lo);
     }
 
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null || !(obj.getClass().equals(IpReachabilityEvent.class))) return false;
+        final IpReachabilityEvent other = (IpReachabilityEvent) obj;
+        return eventType == other.eventType;
+    }
+
     final static class Decoder {
         static final SparseArray<String> constants =
                 MessageUtils.findMessageNames(new Class[]{IpReachabilityEvent.class},
diff --git a/core/java/android/net/metrics/NetworkEvent.java b/core/java/android/net/metrics/NetworkEvent.java
index bed914d..0c57ec6 100644
--- a/core/java/android/net/metrics/NetworkEvent.java
+++ b/core/java/android/net/metrics/NetworkEvent.java
@@ -121,6 +121,14 @@
                 Decoder.constants.get(eventType), durationMs);
     }
 
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null || !(obj.getClass().equals(NetworkEvent.class))) return false;
+        final NetworkEvent other = (NetworkEvent) obj;
+        return eventType == other.eventType
+                && durationMs == other.durationMs;
+    }
+
     final static class Decoder {
         static final SparseArray<String> constants = MessageUtils.findMessageNames(
                 new Class[]{NetworkEvent.class}, new String[]{"NETWORK_"});
diff --git a/core/java/android/net/metrics/RaEvent.java b/core/java/android/net/metrics/RaEvent.java
index b2f6585..3fd87c2 100644
--- a/core/java/android/net/metrics/RaEvent.java
+++ b/core/java/android/net/metrics/RaEvent.java
@@ -97,6 +97,18 @@
                 .toString();
     }
 
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null || !(obj.getClass().equals(RaEvent.class))) return false;
+        final RaEvent other = (RaEvent) obj;
+        return routerLifetime == other.routerLifetime
+                && prefixValidLifetime == other.prefixValidLifetime
+                && prefixPreferredLifetime == other.prefixPreferredLifetime
+                && routeInfoLifetime == other.routeInfoLifetime
+                && rdnssLifetime == other.rdnssLifetime
+                && dnsslLifetime == other.dnsslLifetime;
+    }
+
     /** @hide */
     public static final @android.annotation.NonNull Parcelable.Creator<RaEvent> CREATOR = new Parcelable.Creator<RaEvent>() {
         public RaEvent createFromParcel(Parcel in) {
diff --git a/core/java/android/net/metrics/ValidationProbeEvent.java b/core/java/android/net/metrics/ValidationProbeEvent.java
index c9d7b1b..1aaa50d 100644
--- a/core/java/android/net/metrics/ValidationProbeEvent.java
+++ b/core/java/android/net/metrics/ValidationProbeEvent.java
@@ -170,6 +170,15 @@
                 getProbeName(probeType), returnCode, getValidationStage(probeType), durationMs);
     }
 
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null || !(obj.getClass().equals(ValidationProbeEvent.class))) return false;
+        final ValidationProbeEvent other = (ValidationProbeEvent) obj;
+        return durationMs == other.durationMs
+                && probeType == other.probeType
+                && returnCode == other.returnCode;
+    }
+
     final static class Decoder {
         static final SparseArray<String> constants = MessageUtils.findMessageNames(
                 new Class[]{ValidationProbeEvent.class},
diff --git a/core/java/android/net/util/KeepaliveUtils.java b/core/java/android/net/util/KeepaliveUtils.java
new file mode 100644
index 0000000..569fed1
--- /dev/null
+++ b/core/java/android/net/util/KeepaliveUtils.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.util;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.res.Resources;
+import android.net.NetworkCapabilities;
+import android.text.TextUtils;
+import android.util.AndroidRuntimeException;
+
+import com.android.internal.R;
+
+/**
+ * Collection of utilities for socket keepalive offload.
+ *
+ * @hide
+ */
+public final class KeepaliveUtils {
+
+    public static final String TAG = "KeepaliveUtils";
+
+    // Minimum supported keepalive count per transport if the network supports keepalive.
+    public static final int MIN_SUPPORTED_KEEPALIVE_COUNT = 3;
+
+    public static class KeepaliveDeviceConfigurationException extends AndroidRuntimeException {
+        public KeepaliveDeviceConfigurationException(final String msg) {
+            super(msg);
+        }
+    }
+
+    /**
+     * Read supported keepalive count for each transport type from overlay resource. This should be
+     * used to create a local variable store of resource customization, and use it as the input for
+     * {@link getSupportedKeepalivesForNetworkCapabilities}.
+     *
+     * @param context The context to read resource from.
+     * @return An array of supported keepalive count for each transport type.
+     */
+    @NonNull
+    public static int[] getSupportedKeepalives(@NonNull Context context) {
+        String[] res = null;
+        try {
+            res = context.getResources().getStringArray(
+                    R.array.config_networkSupportedKeepaliveCount);
+        } catch (Resources.NotFoundException unused) {
+        }
+        if (res == null) throw new KeepaliveDeviceConfigurationException("invalid resource");
+
+        final int[] ret = new int[NetworkCapabilities.MAX_TRANSPORT + 1];
+        for (final String row : res) {
+            if (TextUtils.isEmpty(row)) {
+                throw new KeepaliveDeviceConfigurationException("Empty string");
+            }
+            final String[] arr = row.split(",");
+            if (arr.length != 2) {
+                throw new KeepaliveDeviceConfigurationException("Invalid parameter length");
+            }
+
+            int transport;
+            int supported;
+            try {
+                transport = Integer.parseInt(arr[0]);
+                supported = Integer.parseInt(arr[1]);
+            } catch (NumberFormatException e) {
+                throw new KeepaliveDeviceConfigurationException("Invalid number format");
+            }
+
+            if (!NetworkCapabilities.isValidTransport(transport)) {
+                throw new KeepaliveDeviceConfigurationException("Invalid transport " + transport);
+            }
+
+            // Customized values should be either 0 to indicate the network doesn't support
+            // keepalive offload, or a positive value that is at least
+            // MIN_SUPPORTED_KEEPALIVE_COUNT if supported.
+            if (supported != 0 && supported < MIN_SUPPORTED_KEEPALIVE_COUNT) {
+                throw new KeepaliveDeviceConfigurationException(
+                        "Invalid supported count " + supported + " for "
+                                + NetworkCapabilities.transportNameOf(transport));
+            }
+            ret[transport] = supported;
+        }
+        return ret;
+    }
+
+    /**
+     * Get supported keepalive count for the given {@link NetworkCapabilities}.
+     *
+     * @param supportedKeepalives An array of supported keepalive count for each transport type.
+     * @param nc The {@link NetworkCapabilities} of the network the socket keepalive is on.
+     *
+     * @return Supported keepalive count for the given {@link NetworkCapabilities}.
+     */
+    public static int getSupportedKeepalivesForNetworkCapabilities(
+            @NonNull int[] supportedKeepalives, @NonNull NetworkCapabilities nc) {
+        final int[] transports = nc.getTransportTypes();
+        if (transports.length == 0) return 0;
+        int supportedCount = supportedKeepalives[transports[0]];
+        // Iterate through transports and return minimum supported value.
+        for (final int transport : transports) {
+            if (supportedCount > supportedKeepalives[transport]) {
+                supportedCount = supportedKeepalives[transport];
+            }
+        }
+        return supportedCount;
+    }
+}
diff --git a/core/java/android/util/MemoryIntArray.java b/core/java/android/util/MemoryIntArray.java
index 74fea3f..80b1607 100644
--- a/core/java/android/util/MemoryIntArray.java
+++ b/core/java/android/util/MemoryIntArray.java
@@ -20,9 +20,8 @@
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
 
-import dalvik.system.CloseGuard;
-
 import libcore.io.IoUtils;
+import dalvik.system.CloseGuard;
 
 import java.io.Closeable;
 import java.io.IOException;
@@ -57,7 +56,7 @@
 
     private final boolean mIsOwner;
     private final long mMemoryAddr;
-    private ParcelFileDescriptor mFd;
+    private int mFd = -1;
 
     /**
      * Creates a new instance.
@@ -72,8 +71,8 @@
         }
         mIsOwner = true;
         final String name = UUID.randomUUID().toString();
-        mFd = ParcelFileDescriptor.adoptFd(nativeCreate(name, size));
-        mMemoryAddr = nativeOpen(mFd.getFd(), mIsOwner);
+        mFd = nativeCreate(name, size);
+        mMemoryAddr = nativeOpen(mFd, mIsOwner);
         mCloseGuard.open("close");
     }
 
@@ -83,8 +82,8 @@
         if (pfd == null) {
             throw new IOException("No backing file descriptor");
         }
-        mFd = ParcelFileDescriptor.adoptFd(pfd.detachFd());
-        mMemoryAddr = nativeOpen(mFd.getFd(), mIsOwner);
+        mFd = pfd.detachFd();
+        mMemoryAddr = nativeOpen(mFd, mIsOwner);
         mCloseGuard.open("close");
     }
 
@@ -106,7 +105,7 @@
     public int get(int index) throws IOException {
         enforceNotClosed();
         enforceValidIndex(index);
-        return nativeGet(mFd.getFd(), mMemoryAddr, index);
+        return nativeGet(mFd, mMemoryAddr, index);
     }
 
     /**
@@ -122,7 +121,7 @@
         enforceNotClosed();
         enforceWritable();
         enforceValidIndex(index);
-        nativeSet(mFd.getFd(), mMemoryAddr, index, value);
+        nativeSet(mFd, mMemoryAddr, index, value);
     }
 
     /**
@@ -132,7 +131,7 @@
      */
     public int size() throws IOException {
         enforceNotClosed();
-        return nativeSize(mFd.getFd());
+        return nativeSize(mFd);
     }
 
     /**
@@ -143,9 +142,8 @@
     @Override
     public void close() throws IOException {
         if (!isClosed()) {
-            nativeClose(mFd.getFd(), mMemoryAddr, mIsOwner);
-            mFd.close();
-            mFd = null;
+            nativeClose(mFd, mMemoryAddr, mIsOwner);
+            mFd = -1;
             mCloseGuard.close();
         }
     }
@@ -154,7 +152,7 @@
      * @return Whether this array is closed and shouldn't be used.
      */
     public boolean isClosed() {
-        return mFd == null;
+        return mFd == -1;
     }
 
     @Override
@@ -177,8 +175,13 @@
 
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
-        // Don't let writing to a parcel to close our fd - plz
-        parcel.writeParcelable(mFd, flags & ~Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+        ParcelFileDescriptor pfd = ParcelFileDescriptor.adoptFd(mFd);
+        try {
+            // Don't let writing to a parcel to close our fd - plz
+            parcel.writeParcelable(pfd, flags & ~Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+        } finally {
+            pfd.detachFd();
+        }
     }
 
     @Override
@@ -192,13 +195,13 @@
         if (getClass() != obj.getClass()) {
             return false;
         }
-
-        return false;
+        MemoryIntArray other = (MemoryIntArray) obj;
+        return mFd == other.mFd;
     }
 
     @Override
     public int hashCode() {
-        return mFd.hashCode();
+        return mFd;
     }
 
     private void enforceNotClosed() {
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
index 4c32f03..d985528 100644
--- a/core/java/android/widget/RelativeLayout.java
+++ b/core/java/android/widget/RelativeLayout.java
@@ -1992,7 +1992,7 @@
                 // dependencies for a specific set of rules
                 for (int j = 0; j < rulesCount; j++) {
                     final int rule = rules[rulesFilter[j]];
-                    if (ResourceId.isValid(rule)) {
+                    if (rule > 0 || ResourceId.isValid(rule)) {
                         // The node this node depends on
                         final Node dependency = keyNodes.get(rule);
                         // Skip unknowns and self dependencies
diff --git a/core/java/com/android/internal/notification/SystemNotificationChannels.java b/core/java/com/android/internal/notification/SystemNotificationChannels.java
index accfc87..a35e42e 100644
--- a/core/java/com/android/internal/notification/SystemNotificationChannels.java
+++ b/core/java/com/android/internal/notification/SystemNotificationChannels.java
@@ -43,7 +43,12 @@
     public static String NETWORK_ALERTS = "NETWORK_ALERTS";
     public static String NETWORK_AVAILABLE = "NETWORK_AVAILABLE";
     public static String VPN = "VPN";
-    public static String DEVICE_ADMIN = "DEVICE_ADMIN";
+    /**
+     * @deprecated Legacy device admin channel with low importance which is no longer used,
+     *  Use the high importance {@link #DEVICE_ADMIN} channel instead.
+     */
+    @Deprecated public static String DEVICE_ADMIN_DEPRECATED = "DEVICE_ADMIN";
+    public static String DEVICE_ADMIN = "DEVICE_ADMIN_ALERTS";
     public static String ALERTS = "ALERTS";
     public static String RETAIL_MODE = "RETAIL_MODE";
     public static String USB = "USB";
@@ -128,7 +133,7 @@
         final NotificationChannel deviceAdmin = new NotificationChannel(
                 DEVICE_ADMIN,
                 context.getString(R.string.notification_channel_device_admin),
-                NotificationManager.IMPORTANCE_LOW);
+                NotificationManager.IMPORTANCE_HIGH);
         channelsList.add(deviceAdmin);
 
         final NotificationChannel alertsChannel = new NotificationChannel(
@@ -180,6 +185,12 @@
         nm.createNotificationChannels(channelsList);
     }
 
+    /** Remove notification channels which are no longer used */
+    public static void removeDeprecated(Context context) {
+        final NotificationManager nm = context.getSystemService(NotificationManager.class);
+        nm.deleteNotificationChannel(DEVICE_ADMIN_DEPRECATED);
+    }
+
     public static void createAccountChannelForPackage(String pkg, int uid, Context context) {
         final INotificationManager iNotificationManager = NotificationManager.getService();
         try {
diff --git a/core/jni/android_util_MemoryIntArray.cpp b/core/jni/android_util_MemoryIntArray.cpp
index b68f9ec..2dfbe3e 100644
--- a/core/jni/android_util_MemoryIntArray.cpp
+++ b/core/jni/android_util_MemoryIntArray.cpp
@@ -142,6 +142,8 @@
         jniThrowException(env, "java/io/IOException", "ashmem unpinning failed");
         return;
     }
+
+    close(fd);
 }
 
 static jint android_util_MemoryIntArray_get(JNIEnv* env, jobject clazz,
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ab708e5..0daebd7 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -358,6 +358,22 @@
          default value of that setting. -->
     <integer translatable="false" name="config_networkDefaultDailyMultipathQuotaBytes">2500000</integer>
 
+    <!-- Default supported concurrent socket keepalive slots per transport type, used by
+         ConnectivityManager.createSocketKeepalive() for calculating the number of keepalive
+         offload slots that should be reserved for privileged access. This string array should be
+         overridden by the device to present the capability of creating socket keepalives. -->
+    <!-- An Array of "[NetworkCapabilities.TRANSPORT_*],[supported keepalives] -->
+    <string-array translatable="false" name="config_networkSupportedKeepaliveCount">
+        <item>0,3</item>
+        <item>1,3</item>
+    </string-array>
+
+    <!-- Reserved privileged keepalive slots per transport. -->
+    <integer translatable="false" name="config_reservedPrivilegedKeepaliveSlots">2</integer>
+
+    <!-- Allowed unprivileged keepalive slots per uid. -->
+    <integer translatable="false" name="config_allowedUnprivilegedKeepalivePerUid">2</integer>
+
     <!-- List of regexpressions describing the interface (if any) that represent tetherable
          USB interfaces.  If the device doesn't want to support tethering over USB this should
          be empty.  An example would be "usb.*" -->
@@ -1094,6 +1110,11 @@
         -->
     </integer-array>
 
+    <!-- Color mode to use when accessibility transforms are enabled. This color mode must be
+         supported by the device, but not necessarily appear in config_availableColorModes. The
+         regularly selected color mode will be used if this value is negative. -->
+    <integer name="config_accessibilityColorMode">-1</integer>
+
     <!-- Indicate whether to allow the device to suspend when the screen is off
          due to the proximity sensor.  This resource should only be set to true
          if the sensor HAL correctly handles the proximity sensor as a wake-up source.
@@ -3281,6 +3302,10 @@
          {@link Window#setEnsuringNavigationBarContrastWhenTransparent}. -->
     <bool name="config_navBarNeedsScrim">true</bool>
 
+    <!-- Controls whether seamless rotation should be allowed even though the navbar can move
+         (which normally prevents seamless rotation). -->
+    <bool name="config_allowSeamlessRotationDespiteNavBarMoving">false</bool>
+
     <!-- Default insets [LEFT/RIGHTxTOP/BOTTOM] from the screen edge for picture-in-picture windows.
          These values are in DPs and will be converted to pixel sizes internally. -->
     <string translatable="false" name="config_defaultPictureInPictureScreenEdgeInsets">16x16</string>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index b07e7ef..5652c85 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -633,8 +633,8 @@
     <!-- Text shown when viewing channel settings for notifications related to vpn status -->
     <string name="notification_channel_vpn">VPN status</string>
 
-    <!-- Text shown when viewing channel settings for notifications related to remote device administration -->
-    <string name="notification_channel_device_admin">Device administration</string>
+    <!-- Notification channel name. This channel sends high-priority alerts from the user's IT admin for key updates about the user's work device or work profile. -->
+    <string name="notification_channel_device_admin">Alerts from your IT admin</string>
 
     <!-- Text shown when viewing channel settings for notifications related to important alerts -->
     <string name="notification_channel_alerts">Alerts</string>
@@ -5035,13 +5035,8 @@
     <!-- Notification content shown when new SMS/MMS is received while the device is locked [CHAR LIMIT=NONE] -->
     <string name="new_sms_notification_content">Open SMS app to view</string>
 
-    <!-- Notification title shown when user profile is credential encrypted and requires the user to unlock before some features are usable [CHAR LIMIT=30] -->
-    <string name="user_encrypted_title">Some functionality may be limited</string>
-    <!-- Notification message shown when user profile is credential encrypted and requires the user to unlock before some features are usable [CHAR LIMIT=30] -->
-    <string name="user_encrypted_message">Tap to unlock</string>
-    <!-- Notification detail shown when user profile is credential encrypted and requires the user to unlock before some features are usable [CHAR LIMIT=30] -->
-    <string name="user_encrypted_detail">User data locked</string>
-
+    <!-- Notification title shown when user profile is credential encrypted and requires the user to unlock before some features are usable [CHAR LIMIT=36] -->
+    <string name="profile_encrypted_title">Some functionality may be limited</string>
     <!-- Notification detail shown when work profile is credential encrypted and requires the user to unlock before some features are usable [CHAR LIMIT=30] -->
     <string name="profile_encrypted_detail">Work profile locked</string>
     <!-- Notification message shown when work profile is credential encrypted and requires the user to unlock before some features are usable [CHAR LIMIT=30] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 29181c8..1ef2eb4 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2026,6 +2026,9 @@
   <java-symbol type="array" name="config_apfEthTypeBlackList" />
   <java-symbol type="integer" name="config_networkDefaultDailyMultipathQuotaBytes" />
   <java-symbol type="integer" name="config_networkMeteredMultipathPreference" />
+  <java-symbol type="array" name="config_networkSupportedKeepaliveCount" />
+  <java-symbol type="integer" name="config_reservedPrivilegedKeepaliveSlots" />
+  <java-symbol type="integer" name="config_allowedUnprivilegedKeepalivePerUid" />
   <java-symbol type="integer" name="config_notificationsBatteryFullARGB" />
   <java-symbol type="integer" name="config_notificationsBatteryLedOff" />
   <java-symbol type="integer" name="config_notificationsBatteryLedOn" />
@@ -2872,6 +2875,7 @@
   <java-symbol type="bool" name="config_navBarTapThrough" />
   <java-symbol type="bool" name="config_navBarAlwaysShowOnSideEdgeGesture" />
   <java-symbol type="bool" name="config_navBarNeedsScrim" />
+  <java-symbol type="bool" name="config_allowSeamlessRotationDespiteNavBarMoving" />
   <java-symbol type="dimen" name="config_backGestureInset" />
   <java-symbol type="color" name="system_bar_background_semi_transparent" />
 
@@ -3024,9 +3028,7 @@
   <java-symbol type="string" name="now_string_shortest" />
 
   <!-- Encryption notification while accounts are locked by credential encryption -->
-  <java-symbol type="string" name="user_encrypted_title" />
-  <java-symbol type="string" name="user_encrypted_message" />
-  <java-symbol type="string" name="user_encrypted_detail" />
+  <java-symbol type="string" name="profile_encrypted_title" />
   <java-symbol type="string" name="profile_encrypted_detail" />
   <java-symbol type="string" name="profile_encrypted_message" />
   <java-symbol type="drawable" name="ic_user_secure" />
@@ -3185,6 +3187,7 @@
   <java-symbol type="array" name="config_nightDisplayColorTemperatureCoefficients" />
   <java-symbol type="array" name="config_nightDisplayColorTemperatureCoefficientsNative" />
   <java-symbol type="array" name="config_availableColorModes" />
+  <java-symbol type="integer" name="config_accessibilityColorMode" />
   <java-symbol type="bool" name="config_displayWhiteBalanceAvailable" />
   <java-symbol type="bool" name="config_displayWhiteBalanceEnabledDefault" />
   <java-symbol type="integer" name="config_displayWhiteBalanceColorTemperatureMin" />
diff --git a/core/tests/bugreports/Android.bp b/core/tests/bugreports/Android.bp
new file mode 100644
index 0000000..d3bf0dd
--- /dev/null
+++ b/core/tests/bugreports/Android.bp
@@ -0,0 +1,27 @@
+// 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.
+
+android_test {
+    name: "BugreportManagerTestCases",
+    srcs: ["src/**/*.java"],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+    static_libs: ["androidx.test.rules", "truth-prebuilt"],
+    test_suites: ["general-tests"],
+    sdk_version: "test_current",
+    platform_apis: true,
+}
+
diff --git a/core/tests/bugreports/AndroidManifest.xml b/core/tests/bugreports/AndroidManifest.xml
new file mode 100644
index 0000000..0cfb874
--- /dev/null
+++ b/core/tests/bugreports/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          android:installLocation="internalOnly"
+          package="com.android.os.bugreports.tests">
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+              android:targetPackage="com.android.os.bugreports.tests"
+              android:label="Unit tests of BugreportManager" />
+</manifest>
diff --git a/core/tests/bugreports/AndroidTest.xml b/core/tests/bugreports/AndroidTest.xml
new file mode 100644
index 0000000..410ca60
--- /dev/null
+++ b/core/tests/bugreports/AndroidTest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for BugreportManager test cases">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-instrumentation" />
+
+    <option name="config-descriptor:metadata" key="component" value="framework"/>
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true"/>
+        <option name="test-file-name" value="BugreportManagerTestCases.apk"/>
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="com.android.os.bugreports.tests"/>
+        <!-- test-timeout unit is ms, value = 30 min -->
+        <option name="test-timeout" value="1800000" />
+        <option name="runtime-hint" value="30m" />
+    </test>
+</configuration>
diff --git a/core/tests/bugreports/config/test-sysconfig.xml b/core/tests/bugreports/config/test-sysconfig.xml
new file mode 100644
index 0000000..09c69ba
--- /dev/null
+++ b/core/tests/bugreports/config/test-sysconfig.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- WARNING: This is a test config. -->
+<config>
+    <bugreport-whitelisted package="com.android.os.bugreports.tests" />
+</config>
diff --git a/core/tests/bugreports/run.sh b/core/tests/bugreports/run.sh
new file mode 100755
index 0000000..0103398
--- /dev/null
+++ b/core/tests/bugreports/run.sh
@@ -0,0 +1,61 @@
+#!/bin/bash
+
+# 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.
+
+# Script to run bugreport unitests
+# Must run on a rooted device.
+# Must run lunch before running the script
+# Usage: ${ANDROID_BUILD_TOP}/frameworks/base/core/tests/bugreports/run.sh
+
+# NOTE: This script replaces the framework-sysconfig.xml on your device, so use with caution.
+# It tries to replace it when done, but if the script does not finish cleanly
+# (for e.g. force stopped mid-way) your device will be left in an inconsistent state.
+# Reflashing will restore the right config.
+
+TMP_SYS_CONFIG=/var/tmp/framework-sysconfig.xml
+
+if [[ -z $ANDROID_PRODUCT_OUT ]]; then
+  echo "Please lunch before running this test."
+  exit 0
+fi
+
+# Print every command to console.
+set -x
+
+make -j BugreportManagerTestCases &&
+    adb root &&
+    adb remount &&
+    adb wait-for-device &&
+    # Save the sysconfig file in a tmp location and push the test config in
+    adb pull /system/etc/sysconfig/framework-sysconfig.xml "${TMP_SYS_CONFIG}" &&
+    adb push $ANDROID_BUILD_TOP/frameworks/base/core/tests/bugreports/config/test-sysconfig.xml /system/etc/sysconfig/framework-sysconfig.xml &&
+    # The test app needs to be a priv-app.
+    adb push $OUT/testcases/BugreportManagerTestCases/*/BugreportManagerTestCases.apk /system/priv-app ||
+    exit 1
+
+adb reboot &&
+adb wait-for-device &&
+atest BugreportManagerTest || echo "Tests FAILED!"
+
+# Restore the saved config file
+if [ -f "${TMP_SYS_CONFIG}" ]; then
+    SIZE=$(stat --printf="%s" "${TMP_SYS_CONFIG}")
+    if [ SIZE > 0 ]; then
+        adb remount &&
+        adb wait-for-device &&
+        adb push "${TMP_SYS_CONFIG}" /system/etc/sysconfig/framework-sysconfig.xml &&
+        rm "${TMP_SYS_CONFIG}"
+    fi
+fi
diff --git a/core/tests/bugreports/src/android/server/bugreports/BugreportManagerTest.java b/core/tests/bugreports/src/android/server/bugreports/BugreportManagerTest.java
new file mode 100644
index 0000000..220f854
--- /dev/null
+++ b/core/tests/bugreports/src/android/server/bugreports/BugreportManagerTest.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.os.bugreports.tests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import android.Manifest;
+import android.content.Context;
+import android.os.BugreportManager;
+import android.os.BugreportManager.BugreportCallback;
+import android.os.BugreportParams;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+
+/**
+ * Tests for BugreportManager API.
+ */
+@RunWith(JUnit4.class)
+public class BugreportManagerTest {
+    @Rule public TestName name = new TestName();
+
+    private static final String TAG = "BugreportManagerTest";
+    private static final long BUGREPORT_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(10);
+
+    private Handler mHandler;
+    private Executor mExecutor;
+    private BugreportManager mBrm;
+    private ParcelFileDescriptor mBugreportFd;
+    private ParcelFileDescriptor mScreenshotFd;
+
+    @Before
+    public void setup() throws Exception {
+        mHandler = createHandler();
+        mExecutor = (runnable) -> {
+            if (mHandler != null) {
+                mHandler.post(() -> {
+                    runnable.run();
+                });
+            }
+        };
+
+        mBrm = getBugreportManager();
+        mBugreportFd = parcelFd("bugreport_" + name.getMethodName(), ".zip");
+        mScreenshotFd = parcelFd("screenshot_" + name.getMethodName(), ".png");
+
+        getPermissions();
+    }
+
+    @After
+    public void teardown() throws Exception {
+        dropPermissions();
+    }
+
+
+    @Test
+    public void normalFlow_wifi() throws Exception {
+        BugreportCallbackImpl callback = new BugreportCallbackImpl();
+        mBrm.startBugreport(mBugreportFd, mScreenshotFd, wifi(), mExecutor, callback);
+        waitTillDoneOrTimeout(callback);
+
+        assertThat(callback.isDone()).isTrue();
+        // Wifi bugreports should not receive any progress.
+        assertThat(callback.hasReceivedProgress()).isFalse();
+        // TODO: Because of b/130234145, consent dialog is not shown; so we get a timeout error.
+        // When the bug is fixed, accept consent via UIAutomator and verify contents
+        // of mBugreportFd.
+        assertThat(callback.getErrorCode()).isEqualTo(
+                BugreportCallback.BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT);
+        assertFdsAreClosed(mBugreportFd, mScreenshotFd);
+    }
+
+    @Test
+    public void normalFlow_interactive() throws Exception {
+        BugreportCallbackImpl callback = new BugreportCallbackImpl();
+        mBrm.startBugreport(mBugreportFd, mScreenshotFd, interactive(), mExecutor, callback);
+
+        waitTillDoneOrTimeout(callback);
+        assertThat(callback.isDone()).isTrue();
+        // Interactive bugreports show progress updates.
+        assertThat(callback.hasReceivedProgress()).isTrue();
+        assertThat(callback.getErrorCode()).isEqualTo(
+                BugreportCallback.BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT);
+        assertFdsAreClosed(mBugreportFd, mScreenshotFd);
+    }
+
+    @Test
+    public void simultaneousBugreportsNotAllowed() throws Exception {
+        // Start bugreport #1
+        BugreportCallbackImpl callback = new BugreportCallbackImpl();
+        mBrm.startBugreport(mBugreportFd, mScreenshotFd, wifi(), mExecutor, callback);
+
+        // Before #1 is done, try to start #2.
+        assertThat(callback.isDone()).isFalse();
+        BugreportCallbackImpl callback2 = new BugreportCallbackImpl();
+        ParcelFileDescriptor bugreportFd2 = parcelFd("bugreport_2_" + name.getMethodName(), ".zip");
+        ParcelFileDescriptor screenshotFd2 =
+                parcelFd("screenshot_2_" + name.getMethodName(), ".png");
+        mBrm.startBugreport(bugreportFd2, screenshotFd2, wifi(), mExecutor, callback2);
+        Thread.sleep(500 /* .5s */);
+
+        // Verify #2 encounters an error.
+        assertThat(callback2.getErrorCode()).isEqualTo(
+                BugreportCallback.BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS);
+        assertFdsAreClosed(bugreportFd2, screenshotFd2);
+
+        // Cancel #1 so we can move on to the next test.
+        mBrm.cancelBugreport();
+        Thread.sleep(500 /* .5s */);
+        assertThat(callback.isDone()).isTrue();
+        assertFdsAreClosed(mBugreportFd, mScreenshotFd);
+    }
+
+    @Test
+    public void cancelBugreport() throws Exception {
+        // Start a bugreport.
+        BugreportCallbackImpl callback = new BugreportCallbackImpl();
+        mBrm.startBugreport(mBugreportFd, mScreenshotFd, wifi(), mExecutor, callback);
+
+        // Verify it's not finished yet.
+        assertThat(callback.isDone()).isFalse();
+
+        // Try to cancel it, but first without DUMP permission.
+        dropPermissions();
+        try {
+            mBrm.cancelBugreport();
+            fail("Expected cancelBugreport to throw SecurityException without DUMP permission");
+        } catch (SecurityException expected) {
+        }
+        assertThat(callback.isDone()).isFalse();
+
+        // Try again, with DUMP permission.
+        getPermissions();
+        mBrm.cancelBugreport();
+        Thread.sleep(500 /* .5s */);
+        assertThat(callback.isDone()).isTrue();
+        assertFdsAreClosed(mBugreportFd, mScreenshotFd);
+    }
+
+    @Test
+    public void insufficientPermissions_throwsException() throws Exception {
+        dropPermissions();
+
+        BugreportCallbackImpl callback = new BugreportCallbackImpl();
+        try {
+            mBrm.startBugreport(mBugreportFd, mScreenshotFd, wifi(), mExecutor, callback);
+            fail("Expected startBugreport to throw SecurityException without DUMP permission");
+        } catch (SecurityException expected) {
+        }
+        assertFdsAreClosed(mBugreportFd, mScreenshotFd);
+    }
+
+    @Test
+    public void invalidBugreportMode_throwsException() throws Exception {
+        BugreportCallbackImpl callback = new BugreportCallbackImpl();
+
+        try {
+            mBrm.startBugreport(mBugreportFd, mScreenshotFd,
+                    new BugreportParams(25) /* unknown bugreport mode */, mExecutor, callback);
+            fail("Expected to throw IllegalArgumentException with unknown bugreport mode");
+        } catch (IllegalArgumentException expected) {
+        }
+        assertFdsAreClosed(mBugreportFd, mScreenshotFd);
+    }
+
+    private Handler createHandler() {
+        HandlerThread handlerThread = new HandlerThread("BugreportManagerTest");
+        handlerThread.start();
+        return new Handler(handlerThread.getLooper());
+    }
+
+    /* Implementatiion of {@link BugreportCallback} that offers wrappers around execution result */
+    private static final class BugreportCallbackImpl extends BugreportCallback {
+        private int mErrorCode = -1;
+        private boolean mSuccess = false;
+        private boolean mReceivedProgress = false;
+        private final Object mLock = new Object();
+
+        @Override
+        public void onProgress(float progress) {
+            synchronized (mLock) {
+                mReceivedProgress = true;
+            }
+        }
+
+        @Override
+        public void onError(int errorCode) {
+            synchronized (mLock) {
+                mErrorCode = errorCode;
+                Log.d(TAG, "bugreport errored.");
+            }
+        }
+
+        @Override
+        public void onFinished() {
+            synchronized (mLock) {
+                Log.d(TAG, "bugreport finished.");
+                mSuccess =  true;
+            }
+        }
+
+        /* Indicates completion; and ended up with a success or error. */
+        public boolean isDone() {
+            synchronized (mLock) {
+                return (mErrorCode != -1) || mSuccess;
+            }
+        }
+
+        public int getErrorCode() {
+            synchronized (mLock) {
+                return mErrorCode;
+            }
+        }
+
+        public boolean isSuccess() {
+            synchronized (mLock) {
+                return mSuccess;
+            }
+        }
+
+        public boolean hasReceivedProgress() {
+            synchronized (mLock) {
+                return mReceivedProgress;
+            }
+        }
+    }
+
+    public static BugreportManager getBugreportManager() {
+        Context context = InstrumentationRegistry.getContext();
+        BugreportManager bm =
+                (BugreportManager) context.getSystemService(Context.BUGREPORT_SERVICE);
+        if (bm == null) {
+            throw new AssertionError("Failed to get BugreportManager");
+        }
+        return bm;
+    }
+
+    private static ParcelFileDescriptor parcelFd(String prefix, String extension) throws Exception {
+        File f = File.createTempFile(prefix, extension);
+        f.setReadable(true, true);
+        f.setWritable(true, true);
+
+        return ParcelFileDescriptor.open(f,
+                ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_APPEND);
+    }
+
+    private static void dropPermissions() {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+
+    private static void getPermissions() {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity(Manifest.permission.DUMP);
+    }
+
+    private static void assertFdIsClosed(ParcelFileDescriptor pfd) {
+        try {
+            int fd = pfd.getFd();
+            fail("Expected ParcelFileDescriptor argument to be closed, but got: " + fd);
+        } catch (IllegalStateException expected) {
+        }
+    }
+
+    private static void assertFdsAreClosed(ParcelFileDescriptor... pfds) {
+        for (int i = 0; i <  pfds.length; i++) {
+            assertFdIsClosed(pfds[i]);
+        }
+    }
+
+    private static long now() {
+        return System.currentTimeMillis();
+    }
+
+    private static boolean shouldTimeout(long startTimeMs) {
+        return now() - startTimeMs >= BUGREPORT_TIMEOUT_MS;
+    }
+
+    private static void waitTillDoneOrTimeout(BugreportCallbackImpl callback) throws Exception {
+        long startTimeMs = now();
+        while (!callback.isDone()) {
+            Thread.sleep(1000 /* 1s */);
+            if (shouldTimeout(startTimeMs)) {
+                break;
+            }
+            Log.d(TAG, "Waited " + (now() - startTimeMs) + "ms");
+        }
+    }
+
+    /*
+     * Returns a {@link BugreportParams} for wifi only bugreport.
+     *
+     * <p>Wifi bugreports have minimal content and are fast to run. They also suppress progress
+     * updates.
+     */
+    private static BugreportParams wifi() {
+        return new BugreportParams(BugreportParams.BUGREPORT_MODE_WIFI);
+    }
+
+    /*
+     * Returns a {@link BugreportParams} for interactive bugreport that offers progress updates.
+     *
+     * <p>This is the typical bugreport taken by users. This can take on the order of minutes to
+     * finish.
+     */
+    private static BugreportParams interactive() {
+        return new BugreportParams(BugreportParams.BUGREPORT_MODE_INTERACTIVE);
+    }
+}
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
index e3eb2a3..c2fa8b2b 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
@@ -40,6 +40,7 @@
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -411,6 +412,7 @@
         assertThat(textLanguage, isTextLanguage("ja"));
     }
 
+    @Ignore  // Doesn't work without a language-based model.
     @Test
     public void testSuggestConversationActions_textReplyOnly_maxOne() {
         if (isTextClassifierDisabled()) return;
@@ -438,6 +440,7 @@
         Truth.assertThat(conversationAction.getTextReply()).isNotNull();
     }
 
+    @Ignore  // Doesn't work without a language-based model.
     @Test
     public void testSuggestConversationActions_textReplyOnly_noMax() {
         if (isTextClassifierDisabled()) return;
@@ -493,6 +496,7 @@
         Truth.assertThat(actionIntent.getData()).isEqualTo(Uri.parse("https://www.android.com"));
     }
 
+    @Ignore  // Doesn't work without a language-based model.
     @Test
     public void testSuggestConversationActions_copy() {
         if (isTextClassifierDisabled()) return;
diff --git a/core/tests/utiltests/Android.mk b/core/tests/utiltests/Android.mk
index 343c07a..9ef73e9 100644
--- a/core/tests/utiltests/Android.mk
+++ b/core/tests/utiltests/Android.mk
@@ -18,6 +18,7 @@
     androidx.test.rules \
     frameworks-base-testutils \
     mockito-target-minus-junit4 \
+    androidx.test.ext.junit
 
 LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base android.test.mock
 
diff --git a/core/tests/utiltests/jni/android_util_MemoryIntArrayTest.cpp b/core/tests/utiltests/jni/android_util_MemoryIntArrayTest.cpp
index 4b14284..57ee2d5 100644
--- a/core/tests/utiltests/jni/android_util_MemoryIntArrayTest.cpp
+++ b/core/tests/utiltests/jni/android_util_MemoryIntArrayTest.cpp
@@ -21,6 +21,36 @@
 #include <sys/ioctl.h>
 #include <sys/mman.h>
 
+jint android_util_MemoryIntArrayTest_createAshmem(__attribute__((unused)) JNIEnv* env,
+        __attribute__((unused)) jobject clazz,
+        jstring name, jint size)
+{
+
+    if (name == NULL) {
+        return -1;
+    }
+
+    if (size < 0) {
+        return -1;
+    }
+
+    const char* nameStr = env->GetStringUTFChars(name, NULL);
+    const int ashmemSize = sizeof(std::atomic_int) * size;
+    int fd = ashmem_create_region(nameStr, ashmemSize);
+    env->ReleaseStringUTFChars(name, nameStr);
+
+    if (fd < 0) {
+        return -1;
+    }
+
+    int setProtResult = ashmem_set_prot_region(fd, PROT_READ | PROT_WRITE);
+    if (setProtResult < 0) {
+        return -1;
+    }
+
+    return fd;
+}
+
 void android_util_MemoryIntArrayTest_setAshmemSize(__attribute__((unused)) JNIEnv* env,
         __attribute__((unused)) jobject clazz, jint fd, jint size)
 {
diff --git a/core/tests/utiltests/jni/registration.cpp b/core/tests/utiltests/jni/registration.cpp
index d4fc2fb..0c84d98 100644
--- a/core/tests/utiltests/jni/registration.cpp
+++ b/core/tests/utiltests/jni/registration.cpp
@@ -16,14 +16,25 @@
 
 #include <jni.h>
 
+extern jint android_util_MemoryIntArrayTest_createAshmem(JNIEnv* env,
+        jobject clazz, jstring name, jint size);
 extern void android_util_MemoryIntArrayTest_setAshmemSize(JNIEnv* env,
        jobject clazz, jint fd, jint size);
 
 extern "C" {
+    JNIEXPORT jint JNICALL Java_android_util_MemoryIntArrayTest_nativeCreateAshmem(
+            JNIEnv * env, jobject obj, jstring name, jint size);
     JNIEXPORT void JNICALL Java_android_util_MemoryIntArrayTest_nativeSetAshmemSize(
             JNIEnv * env, jobject obj, jint fd, jint size);
 };
 
+JNIEXPORT jint JNICALL Java_android_util_MemoryIntArrayTest_nativeCreateAshmem(
+        __attribute__((unused)) JNIEnv * env,__attribute__((unused)) jobject obj,
+        jstring name, jint size)
+{
+    return android_util_MemoryIntArrayTest_createAshmem(env, obj, name, size);
+}
+
 JNIEXPORT void JNICALL Java_android_util_MemoryIntArrayTest_nativeSetAshmemSize(
         __attribute__((unused)) JNIEnv * env,__attribute__((unused)) jobject obj,
         jint fd, jint size)
diff --git a/core/tests/utiltests/src/android/util/MemoryIntArrayTest.java b/core/tests/utiltests/src/android/util/MemoryIntArrayTest.java
index 2daefe7..1966e12 100644
--- a/core/tests/utiltests/src/android/util/MemoryIntArrayTest.java
+++ b/core/tests/utiltests/src/android/util/MemoryIntArrayTest.java
@@ -23,9 +23,8 @@
 import static org.junit.Assert.fail;
 
 import android.os.Parcel;
-import android.os.ParcelFileDescriptor;
 
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import libcore.io.IoUtils;
 
@@ -36,6 +35,8 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 @RunWith(AndroidJUnit4.class)
 public class MemoryIntArrayTest {
     static {
@@ -255,11 +256,13 @@
                 // Create a MemoryIntArray to muck with
                 MemoryIntArray array = new MemoryIntArray(1);
 
-                // Grab the internal ashmem fd.
-                Field fdField = MemoryIntArray.class.getDeclaredField("mFd");
-                fdField.setAccessible(true);
-                int fd = ((ParcelFileDescriptor)fdField.get(array)).getFd();
-                assertTrue("fd must be valid", fd != -1);
+                // Create the fd to stuff in the MemoryIntArray
+                final int fd = nativeCreateAshmem("foo", 1);
+
+                // Replace the fd with our ahsmem region
+                Field fdFiled = MemoryIntArray.class.getDeclaredField("mFd");
+                fdFiled.setAccessible(true);
+                fdFiled.set(array, fd);
 
                 CountDownLatch countDownLatch = new CountDownLatch(2);
 
@@ -294,9 +297,10 @@
         }
 
         if (!success) {
-            fail("MemoryIntArray should catch ashmem size changing under it");
+            fail("MemoryIntArray should catch ahshmem size changing under it");
         }
     }
 
+    private native int nativeCreateAshmem(String name, int size);
     private native void nativeSetAshmemSize(int fd, int size);
 }
diff --git a/packages/CarSystemUI/AndroidManifest.xml b/packages/CarSystemUI/AndroidManifest.xml
index 4e8a3a3..195d4fe 100644
--- a/packages/CarSystemUI/AndroidManifest.xml
+++ b/packages/CarSystemUI/AndroidManifest.xml
@@ -19,6 +19,6 @@
         package="com.android.systemui"
         android:sharedUserId="android.uid.systemui"
         coreApp="true">
-
-
+    <!-- This permission is required to monitor car power state. -->
+    <uses-permission android:name="android.car.permission.CAR_POWER" />
 </manifest>
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index febf8b8..835cedf 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -25,6 +25,7 @@
 import android.car.Car;
 import android.car.drivingstate.CarDrivingStateEvent;
 import android.car.drivingstate.CarUxRestrictionsManager;
+import android.car.hardware.power.CarPowerManager.CarPowerStateListener;
 import android.content.Context;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
@@ -121,8 +122,11 @@
     private boolean mDeviceIsProvisioned = true;
     private HvacController mHvacController;
     private DrivingStateHelper mDrivingStateHelper;
-    private static FlingAnimationUtils sFlingAnimationUtils;
+    private PowerManagerHelper mPowerManagerHelper;
+    private FlingAnimationUtils mFlingAnimationUtils;
     private SwitchToGuestTimer mSwitchToGuestTimer;
+    private NotificationDataManager mNotificationDataManager;
+    private NotificationClickHandlerFactory mNotificationClickHandlerFactory;
 
     // The container for the notifications.
     private CarNotificationView mNotificationView;
@@ -156,6 +160,20 @@
     // If notification shade is being swiped vertically to close.
     private boolean mIsSwipingVerticallyToClose;
 
+    private final CarPowerStateListener mCarPowerStateListener =
+            (int state) -> {
+                // When the car powers on, clear all notifications and mute/unread states.
+                Log.d(TAG, "New car power state: " + state);
+                if (state == CarPowerStateListener.ON) {
+                    if (mNotificationClickHandlerFactory != null) {
+                        mNotificationClickHandlerFactory.clearAllNotifications();
+                    }
+                    if (mNotificationDataManager != null) {
+                        mNotificationDataManager.clearAll();
+                    }
+                }
+            };
+
     @Override
     public void start() {
         // get the provisioned state before calling the parent class since it's that flow that
@@ -172,7 +190,7 @@
                 R.integer.notification_settle_open_percentage);
         mSettleClosePercentage = mContext.getResources().getInteger(
                 R.integer.notification_settle_close_percentage);
-        sFlingAnimationUtils = new FlingAnimationUtils(mContext,
+        mFlingAnimationUtils = new FlingAnimationUtils(mContext,
                 FLING_ANIMATION_MAX_TIME, FLING_SPEED_UP_FACTOR);
 
         createBatteryController();
@@ -204,6 +222,9 @@
         mDrivingStateHelper = new DrivingStateHelper(mContext, this::onDrivingStateChanged);
         mDrivingStateHelper.connectToCarService();
 
+        mPowerManagerHelper = new PowerManagerHelper(mContext, mCarPowerStateListener);
+        mPowerManagerHelper.connectToCarService();
+
         mSwitchToGuestTimer = new SwitchToGuestTimer(mContext);
     }
 
@@ -308,7 +329,6 @@
         }
     }
 
-
     @Override
     protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
         super.makeStatusBarView(result);
@@ -407,7 +427,7 @@
                 }
         );
 
-        NotificationClickHandlerFactory clickHandlerFactory = new NotificationClickHandlerFactory(
+        mNotificationClickHandlerFactory = new NotificationClickHandlerFactory(
                 mBarService,
                 launchResult -> {
                     if (launchResult == ActivityManager.START_TASK_TO_FRONT
@@ -422,26 +442,27 @@
         CarUxRestrictionManagerWrapper carUxRestrictionManagerWrapper =
                 new CarUxRestrictionManagerWrapper();
         carUxRestrictionManagerWrapper.setCarUxRestrictionsManager(carUxRestrictionsManager);
-        NotificationDataManager notificationDataManager = new NotificationDataManager();
 
-        notificationDataManager.setOnUnseenCountUpdateListener(
+        mNotificationDataManager = new NotificationDataManager();
+        mNotificationDataManager.setOnUnseenCountUpdateListener(
                 () -> {
                     // TODO: Update Notification Icon based on unseen count
                     Log.d(TAG, "unseen count: " +
-                            notificationDataManager.getUnseenNotificationCount());
+                            mNotificationDataManager.getUnseenNotificationCount());
                 });
 
         CarHeadsUpNotificationManager carHeadsUpNotificationManager =
-                new CarSystemUIHeadsUpNotificationManager(mContext, clickHandlerFactory,
-                        notificationDataManager);
+                new CarSystemUIHeadsUpNotificationManager(mContext,
+                        mNotificationClickHandlerFactory, mNotificationDataManager);
+        mNotificationClickHandlerFactory.setNotificationDataManager(mNotificationDataManager);
 
         carNotificationListener.registerAsSystemService(mContext, carUxRestrictionManagerWrapper,
-                carHeadsUpNotificationManager, notificationDataManager);
+                carHeadsUpNotificationManager, mNotificationDataManager);
 
         mNotificationView = mStatusBarWindow.findViewById(R.id.notification_view);
         View glassPane = mStatusBarWindow.findViewById(R.id.glass_pane);
-        mNotificationView.setClickHandlerFactory(clickHandlerFactory);
-        mNotificationView.setNotificationDataManager(notificationDataManager);
+        mNotificationView.setClickHandlerFactory(mNotificationClickHandlerFactory);
+        mNotificationView.setNotificationDataManager(mNotificationDataManager);
 
         // The glass pane is used to view touch events before passed to the notification list.
         // This allows us to initialize gesture listeners and detect when to close the notifications
@@ -528,7 +549,7 @@
                 PreprocessingManager.getInstance(mContext),
                 carNotificationListener,
                 carUxRestrictionManagerWrapper,
-                notificationDataManager);
+                mNotificationDataManager);
         mNotificationViewController.enable();
     }
 
@@ -639,7 +660,7 @@
                 }
             }
         });
-        sFlingAnimationUtils.apply(animator, from, to, Math.abs(velocity));
+        mFlingAnimationUtils.apply(animator, from, to, Math.abs(velocity));
         animator.start();
     }
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/PowerManagerHelper.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/PowerManagerHelper.java
new file mode 100644
index 0000000..8de1439
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/PowerManagerHelper.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.car;
+
+import android.annotation.NonNull;
+import android.car.Car;
+import android.car.CarNotConnectedException;
+import android.car.hardware.power.CarPowerManager;
+import android.car.hardware.power.CarPowerManager.CarPowerStateListener;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.util.Log;
+
+/**
+ * Helper class for connecting to the {@link CarPowerManager} and listening for power state changes.
+ */
+public class PowerManagerHelper {
+    public static final String TAG = "PowerManagerHelper";
+
+    private final Context mContext;
+    private final CarPowerStateListener mCarPowerStateListener;
+
+    private Car mCar;
+    private CarPowerManager mCarPowerManager;
+
+    private final ServiceConnection mCarConnectionListener =
+            new ServiceConnection() {
+                public void onServiceConnected(ComponentName name, IBinder service) {
+                    Log.d(TAG, "Car Service connected");
+                    try {
+                        mCarPowerManager = (CarPowerManager) mCar.getCarManager(Car.POWER_SERVICE);
+                        if (mCarPowerManager != null) {
+                            mCarPowerManager.setListener(mCarPowerStateListener);
+                        } else {
+                            Log.e(TAG, "CarPowerManager service not available");
+                        }
+                    } catch (CarNotConnectedException e) {
+                        Log.e(TAG, "Car not connected", e);
+                    }
+                }
+
+                @Override
+                public void onServiceDisconnected(ComponentName name) {
+                    destroyCarPowerManager();
+                }
+            };
+
+    PowerManagerHelper(Context context, @NonNull CarPowerStateListener listener) {
+        mContext = context;
+        mCarPowerStateListener = listener;
+    }
+
+    /**
+     * Connect to Car service.
+     */
+    void connectToCarService() {
+        mCar = Car.createCar(mContext, mCarConnectionListener);
+        if (mCar != null) {
+            mCar.connect();
+        }
+    }
+
+    /**
+     * Disconnects from Car service.
+     */
+    void disconnectFromCarService() {
+        if (mCar != null) {
+            mCar.disconnect();
+        }
+    }
+
+    private void destroyCarPowerManager() {
+        if (mCarPowerManager != null) {
+            mCarPowerManager.clearListener();
+        }
+    }
+}
diff --git a/packages/NetworkStack/src/android/net/ip/IpClient.java b/packages/NetworkStack/src/android/net/ip/IpClient.java
index dc74c04..266b1b0 100644
--- a/packages/NetworkStack/src/android/net/ip/IpClient.java
+++ b/packages/NetworkStack/src/android/net/ip/IpClient.java
@@ -372,10 +372,6 @@
     private boolean mMulticastFiltering;
     private long mStartTimeMillis;
 
-    /* This must match the definition in KeepaliveTracker.KeepaliveInfo */
-    private static final int TYPE_NATT = 1;
-    private static final int TYPE_TCP = 2;
-
     /**
      * Reading the snapshot is an asynchronous operation initiated by invoking
      * Callback.startReadPacketFilter() and completed when the WiFi Service responds with an
@@ -705,7 +701,7 @@
      * keepalive offload.
      */
     public void addKeepalivePacketFilter(int slot, @NonNull TcpKeepalivePacketDataParcelable pkt) {
-        sendMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF, slot, TYPE_TCP, pkt);
+        sendMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF, slot, 0 /* Unused */, pkt);
     }
 
     /**
@@ -714,7 +710,7 @@
      */
     public void addNattKeepalivePacketFilter(int slot,
             @NonNull NattKeepalivePacketDataParcelable pkt) {
-        sendMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF, slot, TYPE_NATT, pkt);
+        sendMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF, slot, 0 /* Unused */ , pkt);
     }
 
     /**
@@ -1626,13 +1622,12 @@
 
                 case CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF: {
                     final int slot = msg.arg1;
-                    final int type = msg.arg2;
 
                     if (mApfFilter != null) {
-                        if (type == TYPE_NATT) {
+                        if (msg.obj instanceof NattKeepalivePacketDataParcelable) {
                             mApfFilter.addNattKeepalivePacketFilter(slot,
                                     (NattKeepalivePacketDataParcelable) msg.obj);
-                        } else {
+                        } else if (msg.obj instanceof TcpKeepalivePacketDataParcelable) {
                             mApfFilter.addTcpKeepalivePacketFilter(slot,
                                     (TcpKeepalivePacketDataParcelable) msg.obj);
                         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java b/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
index cb0b7c2..98eb573 100644
--- a/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
@@ -286,8 +286,7 @@
 
     /** Returns the state representing empty mobile signal with the given number of levels. */
     public static int getEmptyState(int numLevels) {
-        // TODO empty state == 0 state. does there need to be a new drawable for this?
-        return getState(0, numLevels, false);
+        return getState(0, numLevels, true);
     }
 
     /** Returns the state representing carrier change with the given number of levels. */
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 4d332b9..05af4e1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -1240,7 +1240,7 @@
                 // Special handling for PSK-SAE transition mode. If the AP has advertised both,
                 // we compare the key with both PSK and SAE for a match.
                 return TextUtils.equals(mKey.substring(0, mKey.lastIndexOf(',')),
-                        compareTo.substring(0, mKey.lastIndexOf(',')));
+                        compareTo.substring(0, compareTo.lastIndexOf(',')));
             }
         }
         return mKey.equals(compareTo);
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 715e1eb..dd72d57 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -225,7 +225,7 @@
     <bool name="def_charging_sounds_enabled">true</bool>
 
     <!-- Default for Settings.Secure.NOTIFICATION_BUBBLES -->
-    <bool name="def_notification_bubbles">true</bool>
+    <bool name="def_notification_bubbles">false</bool>
 
     <!-- Default for Settings.Secure.AWARE_ENABLED -->
     <bool name="def_aware_enabled">false</bool>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 82592ce..6558c87a 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3237,7 +3237,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 179;
+            private static final int SETTINGS_VERSION = 180;
 
             private final int mUserId;
 
@@ -4387,6 +4387,19 @@
                     currentVersion = 179;
                 }
 
+                if (currentVersion == 179) {
+                    // Version 178: Reset the default for Secure Settings: NOTIFICATION_BUBBLES
+                    // This is originally set in version 173, however, the default value changed
+                    // so this step is to ensure the value is updated to the correct defaulte
+                    final SettingsState secureSettings = getSecureSettingsLocked(userId);
+
+                    secureSettings.insertSettingLocked(Secure.NOTIFICATION_BUBBLES,
+                            getContext().getResources().getBoolean(
+                                    R.bool.def_notification_bubbles) ? "1" : "0", null,
+                                    true, SettingsState.SYSTEM_PACKAGE_NAME);
+
+                    currentVersion = 180;
+                }
 
                 // vXXX: Add new settings above this point.
 
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index c2495b5..9425941 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -58,6 +58,7 @@
         "androidx.arch.core_core-runtime",
         "androidx.lifecycle_lifecycle-extensions",
         "androidx.dynamicanimation_dynamicanimation",
+        "iconloader_base",
         "SystemUI-tags",
         "SystemUI-proto",
         "dagger2-2.19",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 02d826f..4b4912c 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -444,7 +444,7 @@
         <activity
             android:name=".media.MediaProjectionPermissionActivity"
             android:exported="true"
-            android:theme="@style/Theme.MediaProjectionAlertDialog"
+            android:theme="@style/Theme.SystemUI.MediaProjectionAlertDialog"
             android:finishOnCloseSystemDialogs="true"
             android:launchMode="singleTop"
             android:excludeFromRecents="true"
diff --git a/packages/SystemUI/res/drawable-night/ic_media_projection_permission.xml b/packages/SystemUI/res/drawable-night/ic_media_projection_permission.xml
new file mode 100644
index 0000000..f20babf
--- /dev/null
+++ b/packages/SystemUI/res/drawable-night/ic_media_projection_permission.xml
@@ -0,0 +1,26 @@
+<!--
+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.
+-->
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+    android:insetLeft="2.5dp"
+    android:insetRight="2.5dp">
+    <vector android:width="24dp"
+            android:height="24dp"
+            android:viewportWidth="24.0"
+            android:viewportHeight="24.0">
+        <path android:fillColor="#FFFFFFFF" android:pathData="M1,18v3h3C4,19.34 2.66,18 1,18zM1,14v2c2.76,0 5,2.24 5,5h2C8,17.13 4.87,14 1,14zM1,10v2c4.97,0 9,4.03 9,9h2C12,14.92 7.07,10 1,10zM21,3H3C1.9,3 1,3.9 1,5v3h2V5h18v14h-7v2h7c1.1,0 2,-0.9 2,-2V5C23,3.9 22.1,3 21,3z"/>
+        <path android:fillColor="#FF0000" android:pathData="M19,7H5v1.63c3.96,1.28 7.09,4.41 8.37,8.37H19V7z"/>
+    </vector>
+</inset>
diff --git a/packages/SystemUI/res/layout/media_projection_dialog_title.xml b/packages/SystemUI/res/layout/media_projection_dialog_title.xml
index c4d784b..b9e39da 100644
--- a/packages/SystemUI/res/layout/media_projection_dialog_title.xml
+++ b/packages/SystemUI/res/layout/media_projection_dialog_title.xml
@@ -15,25 +15,26 @@
     limitations under the License.
 -->
 
-<RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content"
+<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"
         xmlns:android="http://schemas.android.com/apk/res/android"
-        android:theme="@style/Theme.MediaProjectionAlertDialog"
+        android:theme="@style/Theme.SystemUI.MediaProjectionAlertDialog"
         android:paddingStart="?android:attr/dialogPreferredPadding"
         android:paddingEnd="?android:attr/dialogPreferredPadding"
-        android:paddingTop="?android:attr/dialogPreferredPadding">
+        android:orientation="vertical">
     <ImageView
         android:id="@+id/dialog_icon"
         android:src="@drawable/ic_media_projection_permission"
-        android:layout_height="wrap_content"
-        android:layout_width="match_parent"
-        android:layout_marginBottom="20dp"
-        android:layout_centerInParent="true"/>
+        android:layout_height="24dp"
+        android:layout_width="24dp"
+        android:layout_marginTop="18dp"
+        android:layout_marginBottom="12dp"
+        android:layout_gravity="center_horizontal" />
     <TextView
         android:id="@+id/dialog_title"
         android:gravity="center"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_below="@id/dialog_icon"
-        android:textColor="?android:attr/colorPrimary"
+        android:textSize="20sp"
+        android:textColor="?android:attr/textColorPrimary"
         android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Title" />
-</RelativeLayout>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/super_status_bar.xml b/packages/SystemUI/res/layout/super_status_bar.xml
index 4cf5f85..a914930 100644
--- a/packages/SystemUI/res/layout/super_status_bar.xml
+++ b/packages/SystemUI/res/layout/super_status_bar.xml
@@ -69,7 +69,7 @@
         android:layout_height="match_parent"
         android:importantForAccessibility="no"
         sysui:ignoreRightInset="true"
-        />
+    />
 
     <LinearLayout
         android:id="@+id/lock_icon_container"
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 16328f8..59ed5ce 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -349,7 +349,7 @@
     <style name="AutoSizingList">
         <item name="enableAutoSizing">true</item>
     </style>
-    <style name="Theme.MediaProjectionAlertDialog" parent="android:Theme.DeviceDefault">
+    <style name="Theme.SystemUI.MediaProjectionAlertDialog">
         <item name="android:windowIsTranslucent">true</item>
         <item name="android:windowBackground">@android:color/transparent</item>
         <item name="android:windowContentOverlay">@null</item>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index 6709804..577e3bb 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -134,9 +134,4 @@
      * Sent when some system ui state changes.
      */
     void onSystemUiStateChanged(int stateFlags) = 16;
-
-    /**
-     * Sent when the scrim colors (based on wallpaper) change.
-     */
-    void onScrimColorsChanged(int color, int type) = 17;
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index d051def..cd0019c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -180,7 +180,7 @@
                 }
                 int index = event.findPointerIndex(mActivePointerId);
                 int touchSlop = mViewConfiguration.getScaledTouchSlop();
-                if (mCurrentSecurityView != null
+                if (mCurrentSecurityView != null && index != -1
                         && mStartTouchY - event.getY(index) > touchSlop) {
                     mIsDragging = true;
                     return true;
diff --git a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
index de10690..05665b5 100644
--- a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
+++ b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.colorextraction;
 
-import android.annotation.ColorInt;
-import android.annotation.IntDef;
 import android.app.WallpaperColors;
 import android.app.WallpaperManager;
 import android.content.Context;
@@ -36,13 +34,10 @@
 import com.android.internal.colorextraction.types.Tonal;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dumpable;
-import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
 
 import javax.inject.Inject;
@@ -55,41 +50,23 @@
 public class SysuiColorExtractor extends ColorExtractor implements Dumpable,
         ConfigurationController.ConfigurationListener {
     private static final String TAG = "SysuiColorExtractor";
-
-    public static final int SCRIM_TYPE_REGULAR = 1;
-    public static final int SCRIM_TYPE_LIGHT = 2;
-    public static final int SCRIM_TYPE_DARK = 3;
-
-    @IntDef(prefix = {"SCRIM_TYPE_"}, value = {
-            SCRIM_TYPE_REGULAR,
-            SCRIM_TYPE_LIGHT,
-            SCRIM_TYPE_DARK
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface ScrimType {
-    }
-
     private final Tonal mTonal;
-    private final OverviewProxyService mOverviewProxyService;
     private boolean mWallpaperVisible;
     private boolean mHasBackdrop;
     // Colors to return when the wallpaper isn't visible
     private final GradientColors mWpHiddenColors;
 
     @Inject
-    public SysuiColorExtractor(Context context, ConfigurationController configurationController,
-            OverviewProxyService overviewProxyService) {
-        this(context, new Tonal(context), configurationController, true, overviewProxyService);
+    public SysuiColorExtractor(Context context, ConfigurationController configurationController) {
+        this(context, new Tonal(context), configurationController, true);
     }
 
     @VisibleForTesting
     public SysuiColorExtractor(Context context, ExtractionType type,
-            ConfigurationController configurationController, boolean registerVisibility,
-            OverviewProxyService overviewProxyService) {
+            ConfigurationController configurationController, boolean registerVisibility) {
         super(context, type, false /* immediately */);
         mTonal = type instanceof Tonal ? (Tonal) type : new Tonal(context);
         mWpHiddenColors = new GradientColors();
-        mOverviewProxyService = overviewProxyService;
         configurationController.addCallback(this);
 
         WallpaperColors systemColors = getWallpaperColors(WallpaperManager.FLAG_SYSTEM);
@@ -133,35 +110,17 @@
             return;
         }
 
+        super.onColorsChanged(colors, which);
+
         if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
             updateDefaultGradients(colors);
         }
-        super.onColorsChanged(colors, which);
     }
 
     @Override
     public void onUiModeChanged() {
         WallpaperColors systemColors = getWallpaperColors(WallpaperManager.FLAG_SYSTEM);
         updateDefaultGradients(systemColors);
-        triggerColorsChanged(WallpaperManager.FLAG_SYSTEM);
-    }
-
-    @Override
-    protected void triggerColorsChanged(int which) {
-        super.triggerColorsChanged(which);
-
-        if (mWpHiddenColors != null && (which & WallpaperManager.FLAG_SYSTEM) != 0) {
-            @ColorInt int colorInt = mWpHiddenColors.getMainColor();
-            @ScrimType int scrimType;
-            if (colorInt == Tonal.MAIN_COLOR_LIGHT) {
-                scrimType = SCRIM_TYPE_LIGHT;
-            } else if (colorInt == Tonal.MAIN_COLOR_DARK) {
-                scrimType = SCRIM_TYPE_DARK;
-            } else {
-                scrimType = SCRIM_TYPE_REGULAR;
-            }
-            mOverviewProxyService.onScrimColorsChanged(colorInt, scrimType);
-        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 98f36e4..ec6cfe9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1785,31 +1785,37 @@
         public void run() {
             Trace.beginSection("KeyguardViewMediator.mKeyGuardGoingAwayRunnable");
             if (DEBUG) Log.d(TAG, "keyguardGoingAway");
-            try {
-                mStatusBarKeyguardViewManager.keyguardGoingAway();
+            mStatusBarKeyguardViewManager.keyguardGoingAway();
 
-                int flags = 0;
-                if (mStatusBarKeyguardViewManager.shouldDisableWindowAnimationsForUnlock()
-                        || (mWakeAndUnlocking && !mPulsing)) {
-                    flags |= WindowManagerPolicyConstants
-                            .KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
-                }
-                if (mStatusBarKeyguardViewManager.isGoingToNotificationShade()
-                        || (mWakeAndUnlocking && mPulsing)) {
-                    flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
-                }
-                if (mStatusBarKeyguardViewManager.isUnlockWithWallpaper()) {
-                    flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
-                }
-
-                mUpdateMonitor.setKeyguardGoingAway(true /* goingAway */);
-                // Don't actually hide the Keyguard at the moment, wait for window
-                // manager until it tells us it's safe to do so with
-                // startKeyguardExitAnimation.
-                ActivityTaskManager.getService().keyguardGoingAway(flags);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error while calling WindowManager", e);
+            int flags = 0;
+            if (mStatusBarKeyguardViewManager.shouldDisableWindowAnimationsForUnlock()
+                    || (mWakeAndUnlocking && !mPulsing)) {
+                flags |= WindowManagerPolicyConstants
+                        .KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
             }
+            if (mStatusBarKeyguardViewManager.isGoingToNotificationShade()
+                    || (mWakeAndUnlocking && mPulsing)) {
+                flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
+            }
+            if (mStatusBarKeyguardViewManager.isUnlockWithWallpaper()) {
+                flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
+            }
+
+            mUpdateMonitor.setKeyguardGoingAway(true /* goingAway */);
+
+            // Don't actually hide the Keyguard at the moment, wait for window
+            // manager until it tells us it's safe to do so with
+            // startKeyguardExitAnimation.
+            // Posting to mUiOffloadThread to ensure that calls to ActivityTaskManager will be in
+            // order.
+            final int keyguardFlag = flags;
+            mUiOffloadThread.submit(() -> {
+                try {
+                    ActivityTaskManager.getService().keyguardGoingAway(keyguardFlag);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Error while calling WindowManager", e);
+                }
+            });
             Trace.endSection();
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index c5591cf..78c7cd4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -31,7 +31,6 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
 
-import android.annotation.ColorInt;
 import android.annotation.FloatRange;
 import android.app.ActivityTaskManager;
 import android.content.BroadcastReceiver;
@@ -60,7 +59,6 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.SysUiServiceProvider;
-import com.android.systemui.colorextraction.SysuiColorExtractor.ScrimType;
 import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
@@ -537,16 +535,6 @@
         dispatchNavButtonBounds();
     }
 
-    public void onScrimColorsChanged(@ColorInt int color, @ScrimType int type) {
-        if (mOverviewProxy != null) {
-            try {
-                mOverviewProxy.onScrimColorsChanged(color, type);
-            } catch (RemoteException e) {
-                Log.e(TAG_OPS, "Failed to call onScrimColorsChanged()", e);
-            }
-        }
-    }
-
     private void dispatchNavButtonBounds() {
         if (mOverviewProxy != null && mActiveNavBarRegion != null) {
             try {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
index 1074f3a..3450a8a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -89,6 +89,7 @@
     private float mDozeAmount;
     private int mIconRes;
     private boolean mWasPulsingOnThisFrame;
+    private boolean mWakeAndUnlockRunning;
 
     private final Runnable mDrawOffTimeout = () -> update(true /* forceUpdate */);
     private final DockManager.DockEventListener mDockEventListener =
@@ -277,7 +278,8 @@
             mLastBouncerVisible = mBouncerVisible;
         }
 
-        boolean invisible = mDozing && (!mPulsing || mDocked);
+        boolean onAodNotPulsingOrDocked = mDozing && (!mPulsing || mDocked);
+        boolean invisible = onAodNotPulsingOrDocked || mWakeAndUnlockRunning;
         setVisibility(invisible ? INVISIBLE : VISIBLE);
         updateClickability();
     }
@@ -450,4 +452,23 @@
     public void onUnlockMethodStateChanged() {
         update();
     }
+
+    /**
+     * We need to hide the lock whenever there's a fingerprint unlock, otherwise you'll see the
+     * icon on top of the black front scrim.
+     */
+    public void onBiometricAuthModeChanged(boolean wakeAndUnlock) {
+        if (wakeAndUnlock) {
+            mWakeAndUnlockRunning = true;
+        }
+        update();
+    }
+
+    /**
+     * Triggered after the unlock animation is over and the user is looking at launcher.
+     */
+    public void onKeyguardFadedAway() {
+        mWakeAndUnlockRunning = false;
+        update();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index b4b4235..17f0d5a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -538,6 +538,7 @@
             }
             if (mKeyguardMonitor.isKeyguardFadingAway()) {
                 mStatusBarKeyguardViewManager.onKeyguardFadedAway();
+                mStatusBarWindow.onKeyguardFadedAway();
             }
         }
 
@@ -3798,6 +3799,7 @@
     public void notifyBiometricAuthModeChanged() {
         updateDozing();
         updateScrimController();
+        mStatusBarWindow.onBiometricAuthModeChanged(mBiometricUnlockController.isWakeAndUnlock());
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 9f538bb..0b83b69 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -265,6 +265,21 @@
         mLockIcon.setPulsing(pulsing);
     }
 
+    /**
+     * Called when the biometric authentication mode changes.
+     * @param wakeAndUnlock If the type is {@link BiometricUnlockController#isWakeAndUnlock()}
+     */
+    public void onBiometricAuthModeChanged(boolean wakeAndUnlock) {
+        mLockIcon.onBiometricAuthModeChanged(wakeAndUnlock);
+    }
+
+    /**
+     * Called after finished unlocking and the status bar window is already collapsed.
+     */
+    public void onKeyguardFadedAway() {
+        mLockIcon.onKeyguardFadedAway();
+    }
+
     public void setStatusBarView(PhoneStatusBarView statusBarView) {
         mStatusBarView = statusBarView;
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java b/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
index 3d3c295..67df60a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
@@ -34,14 +34,10 @@
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.internal.colorextraction.types.Tonal;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 
 /**
  * Tests color extraction generation.
@@ -57,13 +53,6 @@
             ColorExtractor.TYPE_NORMAL,
             ColorExtractor.TYPE_DARK,
             ColorExtractor.TYPE_EXTRA_DARK};
-    @Mock
-    private OverviewProxyService mOverviewProxyService;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-    }
 
     @Test
     public void getColors_usesGreyIfWallpaperNotVisible() {
@@ -129,8 +118,7 @@
         Tonal tonal = mock(Tonal.class);
         ConfigurationController configurationController = mock(ConfigurationController.class);
         SysuiColorExtractor sysuiColorExtractor = new SysuiColorExtractor(getContext(),
-                tonal, configurationController, false /* registerVisibility */,
-                mOverviewProxyService);
+                tonal, configurationController, false /* registerVisibility */);
         verify(configurationController).addCallback(eq(sysuiColorExtractor));
 
         reset(tonal);
@@ -145,7 +133,7 @@
                     outGradientColorsNormal.set(colors);
                     outGradientColorsDark.set(colors);
                     outGradientColorsExtraDark.set(colors);
-                }, mock(ConfigurationController.class), false, mOverviewProxyService);
+                }, mock(ConfigurationController.class), false);
     }
 
     private void simulateEvent(SysuiColorExtractor extractor) {
diff --git a/packages/overlays/NavigationBarModeGesturalOverlay/res/values/config.xml b/packages/overlays/NavigationBarModeGesturalOverlay/res/values/config.xml
index 9694e76..f1d2e0b 100644
--- a/packages/overlays/NavigationBarModeGesturalOverlay/res/values/config.xml
+++ b/packages/overlays/NavigationBarModeGesturalOverlay/res/values/config.xml
@@ -37,6 +37,10 @@
      {@link Window#setEnsuringNavigationBarContrastWhenTransparent}. -->
     <bool name="config_navBarNeedsScrim">false</bool>
 
+    <!-- Controls whether seamless rotation should be allowed even though the navbar can move
+         (which normally prevents seamless rotation). -->
+    <bool name="config_allowSeamlessRotationDespiteNavBarMoving">true</bool>
+
     <!-- Controls whether the side edge gestures can always trigger the transient nav bar to
          show. -->
     <bool name="config_navBarAlwaysShowOnSideEdgeGesture">true</bool>
diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
index 35f7ea3..3de2537 100644
--- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
+++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
@@ -29,6 +29,7 @@
 import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS;
 import static android.net.SocketKeepalive.ERROR_INVALID_NETWORK;
 import static android.net.SocketKeepalive.ERROR_INVALID_SOCKET;
+import static android.net.SocketKeepalive.ERROR_UNSUPPORTED;
 import static android.net.SocketKeepalive.MAX_INTERVAL_SEC;
 import static android.net.SocketKeepalive.MIN_INTERVAL_SEC;
 import static android.net.SocketKeepalive.NO_KEEPALIVE;
@@ -46,6 +47,7 @@
 import android.net.SocketKeepalive.InvalidSocketException;
 import android.net.TcpKeepalivePacketData;
 import android.net.util.IpUtils;
+import android.net.util.KeepaliveUtils;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -57,6 +59,7 @@
 import android.util.Log;
 import android.util.Pair;
 
+import com.android.internal.R;
 import com.android.internal.util.HexDump;
 import com.android.internal.util.IndentingPrintWriter;
 
@@ -65,6 +68,7 @@
 import java.net.InetSocketAddress;
 import java.net.SocketAddress;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 
 /**
@@ -90,10 +94,29 @@
     @NonNull
     private final Context mContext;
 
+    // Supported keepalive count for each transport type, can be configured through
+    // config_networkSupportedKeepaliveCount. For better error handling, use
+    // {@link getSupportedKeepalivesForNetworkCapabilities} instead of direct access.
+    @NonNull
+    private final int[] mSupportedKeepalives;
+
+    // Reserved privileged keepalive slots per transport. Caller's permission will be enforced if
+    // the number of remaining keepalive slots is less than or equal to the threshold.
+    private final int mReservedPrivilegedSlots;
+
+    // Allowed unprivileged keepalive slots per uid. Caller's permission will be enforced if
+    // the number of remaining keepalive slots is less than or equal to the threshold.
+    private final int mAllowedUnprivilegedSlotsForUid;
+
     public KeepaliveTracker(Context context, Handler handler) {
         mConnectivityServiceHandler = handler;
         mTcpController = new TcpKeepaliveController(handler);
         mContext = context;
+        mSupportedKeepalives = KeepaliveUtils.getSupportedKeepalives(mContext);
+        mReservedPrivilegedSlots = mContext.getResources().getInteger(
+                R.integer.config_reservedPrivilegedKeepaliveSlots);
+        mAllowedUnprivilegedSlotsForUid = mContext.getResources().getInteger(
+                R.integer.config_allowedUnprivilegedKeepalivePerUid);
     }
 
     /**
@@ -115,11 +138,6 @@
         public static final int TYPE_NATT = 1;
         public static final int TYPE_TCP = 2;
 
-        // Max allowed unprivileged keepalive slots per network. Caller's permission will be
-        // enforced if number of existing keepalives reach this limit.
-        // TODO: consider making this limit configurable via resources.
-        private static final int MAX_UNPRIVILEGED_SLOTS = 3;
-
         // Keepalive slot. A small integer that identifies this keepalive among the ones handled
         // by this network.
         private int mSlot = NO_KEEPALIVE;
@@ -191,6 +209,7 @@
                 case NOT_STARTED : return "NOT_STARTED";
                 case STARTING : return "STARTING";
                 case STARTED : return "STARTED";
+                case STOPPING : return "STOPPING";
             }
             throw new IllegalArgumentException("Unknown state");
         }
@@ -246,24 +265,54 @@
 
         private int checkPermission() {
             final HashMap<Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(mNai);
-            int unprivilegedCount = 0;
             if (networkKeepalives == null) {
                 return ERROR_INVALID_NETWORK;
             }
-            for (KeepaliveInfo ki : networkKeepalives.values()) {
-                if (!ki.mPrivileged) {
-                    unprivilegedCount++;
-                }
-                if (unprivilegedCount >= MAX_UNPRIVILEGED_SLOTS) {
-                    return mPrivileged ? SUCCESS : ERROR_INSUFFICIENT_RESOURCES;
+
+            if (mPrivileged) return SUCCESS;
+
+            final int supported = KeepaliveUtils.getSupportedKeepalivesForNetworkCapabilities(
+                    mSupportedKeepalives, mNai.networkCapabilities);
+
+            int takenUnprivilegedSlots = 0;
+            for (final KeepaliveInfo ki : networkKeepalives.values()) {
+                if (!ki.mPrivileged) ++takenUnprivilegedSlots;
+            }
+            if (takenUnprivilegedSlots > supported - mReservedPrivilegedSlots) {
+                return ERROR_INSUFFICIENT_RESOURCES;
+            }
+
+            // Count unprivileged keepalives for the same uid across networks.
+            int unprivilegedCountSameUid = 0;
+            for (final HashMap<Integer, KeepaliveInfo> kaForNetwork : mKeepalives.values()) {
+                for (final KeepaliveInfo ki : kaForNetwork.values()) {
+                    if (ki.mUid == mUid) {
+                        unprivilegedCountSameUid++;
+                    }
                 }
             }
+            if (unprivilegedCountSameUid > mAllowedUnprivilegedSlotsForUid) {
+                return ERROR_INSUFFICIENT_RESOURCES;
+            }
+            return SUCCESS;
+        }
+
+        private int checkLimit() {
+            final HashMap<Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(mNai);
+            if (networkKeepalives == null) {
+                return ERROR_INVALID_NETWORK;
+            }
+            final int supported = KeepaliveUtils.getSupportedKeepalivesForNetworkCapabilities(
+                    mSupportedKeepalives, mNai.networkCapabilities);
+            if (supported == 0) return ERROR_UNSUPPORTED;
+            if (networkKeepalives.size() > supported) return ERROR_INSUFFICIENT_RESOURCES;
             return SUCCESS;
         }
 
         private int isValid() {
             synchronized (mNai) {
                 int error = checkInterval();
+                if (error == SUCCESS) error = checkLimit();
                 if (error == SUCCESS) error = checkPermission();
                 if (error == SUCCESS) error = checkNetworkConnected();
                 if (error == SUCCESS) error = checkSourceAddress();
@@ -278,6 +327,8 @@
                 Log.d(TAG, "Starting keepalive " + mSlot + " on " + mNai.name());
                 switch (mType) {
                     case TYPE_NATT:
+                        mNai.asyncChannel.sendMessage(
+                                CMD_ADD_KEEPALIVE_PACKET_FILTER, slot, 0 /* Unused */, mPacket);
                         mNai.asyncChannel
                                 .sendMessage(CMD_START_SOCKET_KEEPALIVE, slot, mInterval, mPacket);
                         break;
@@ -288,9 +339,8 @@
                             handleStopKeepalive(mNai, mSlot, ERROR_INVALID_SOCKET);
                             return;
                         }
-                        mNai.asyncChannel
-                                .sendMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER, slot, 0 /* Unused */,
-                                        mPacket);
+                        mNai.asyncChannel.sendMessage(
+                                CMD_ADD_KEEPALIVE_PACKET_FILTER, slot, 0 /* Unused */, mPacket);
                         // TODO: check result from apf and notify of failure as needed.
                         mNai.asyncChannel
                                 .sendMessage(CMD_START_SOCKET_KEEPALIVE, slot, mInterval, mPacket);
@@ -314,18 +364,30 @@
                     Log.e(TAG, "Cannot stop unowned keepalive " + mSlot + " on " + mNai.network);
                 }
             }
-            if (NOT_STARTED != mStartedState) {
-                mStartedState = STOPPING;
-                Log.d(TAG, "Stopping keepalive " + mSlot + " on " + mNai.name());
-                if (mType == TYPE_NATT) {
-                    mNai.asyncChannel.sendMessage(CMD_STOP_SOCKET_KEEPALIVE, mSlot);
-                } else if (mType == TYPE_TCP) {
-                    mNai.asyncChannel.sendMessage(CMD_STOP_SOCKET_KEEPALIVE, mSlot);
-                    mNai.asyncChannel.sendMessage(CMD_REMOVE_KEEPALIVE_PACKET_FILTER, mSlot);
-                    mTcpController.stopSocketMonitor(mSlot);
-                } else {
-                    Log.wtf(TAG, "Stopping keepalive with unknown type: " + mType);
-                }
+            Log.d(TAG, "Stopping keepalive " + mSlot + " on " + mNai.name() + ": " + reason);
+            switch (mStartedState) {
+                case NOT_STARTED:
+                    // Remove the reference of the keepalive that meet error before starting,
+                    // e.g. invalid parameter.
+                    cleanupStoppedKeepalive(mNai, mSlot);
+                    break;
+                case STOPPING:
+                    // Keepalive is already in stopping state, ignore.
+                    return;
+                default:
+                    mStartedState = STOPPING;
+                    switch (mType) {
+                        case TYPE_TCP:
+                            mTcpController.stopSocketMonitor(mSlot);
+                            // fall through
+                        case TYPE_NATT:
+                            mNai.asyncChannel.sendMessage(CMD_STOP_SOCKET_KEEPALIVE, mSlot);
+                            mNai.asyncChannel.sendMessage(CMD_REMOVE_KEEPALIVE_PACKET_FILTER,
+                                    mSlot);
+                            break;
+                        default:
+                            Log.wtf(TAG, "Stopping keepalive with unknown type: " + mType);
+                    }
             }
 
             // Close the duplicated fd that maintains the lifecycle of socket whenever
@@ -400,14 +462,18 @@
     }
 
     public void handleStopAllKeepalives(NetworkAgentInfo nai, int reason) {
-        HashMap <Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(nai);
+        final HashMap<Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(nai);
         if (networkKeepalives != null) {
-            for (KeepaliveInfo ki : networkKeepalives.values()) {
+            final ArrayList<KeepaliveInfo> kalist = new ArrayList(networkKeepalives.values());
+            for (KeepaliveInfo ki : kalist) {
                 ki.stop(reason);
+                // Clean up keepalives since the network agent is disconnected and unable to pass
+                // back asynchronous result of stop().
+                cleanupStoppedKeepalive(nai, ki.mSlot);
             }
-            networkKeepalives.clear();
-            mKeepalives.remove(nai);
         }
+        // Clean up keepalives will be done as a result of calling ki.stop() after the slots are
+        // freed.
     }
 
     public void handleStopKeepalive(NetworkAgentInfo nai, int slot, int reason) {
@@ -423,8 +489,24 @@
             return;
         }
         ki.stop(reason);
+        // Clean up keepalives will be done as a result of calling ki.stop() after the slots are
+        // freed.
+    }
+
+    private void cleanupStoppedKeepalive(NetworkAgentInfo nai, int slot) {
+        String networkName = (nai == null) ? "(null)" : nai.name();
+        HashMap<Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(nai);
+        if (networkKeepalives == null) {
+            Log.e(TAG, "Attempt to remove keepalive on nonexistent network " + networkName);
+            return;
+        }
+        KeepaliveInfo ki = networkKeepalives.get(slot);
+        if (ki == null) {
+            Log.e(TAG, "Attempt to remove nonexistent keepalive " + slot + " on " + networkName);
+            return;
+        }
         networkKeepalives.remove(slot);
-        Log.d(TAG, "Stopped keepalive " + slot + " on " + networkName + ", "
+        Log.d(TAG, "Remove keepalive " + slot + " on " + networkName + ", "
                 + networkKeepalives.size() + " remains.");
         if (networkKeepalives.isEmpty()) {
             mKeepalives.remove(nai);
@@ -495,10 +577,11 @@
                 handleStopKeepalive(nai, slot, reason);
             }
         } else if (KeepaliveInfo.STOPPING == ki.mStartedState) {
-            // The message indicated result of stopping : don't call handleStopKeepalive.
+            // The message indicated result of stopping : clean up keepalive slots.
             Log.d(TAG, "Stopped keepalive " + slot + " on " + nai.name()
                     + " stopped: " + reason);
             ki.mStartedState = KeepaliveInfo.NOT_STARTED;
+            cleanupStoppedKeepalive(nai, slot);
         } else {
             Log.wtf(TAG, "Event " + message.what + "," + slot + "," + reason
                     + " for keepalive in wrong state: " + ki.toString());
@@ -642,6 +725,9 @@
     }
 
     public void dump(IndentingPrintWriter pw) {
+        pw.println("Supported Socket keepalives: " + Arrays.toString(mSupportedKeepalives));
+        pw.println("Reserved Privileged keepalives: " + mReservedPrivilegedSlots);
+        pw.println("Allowed Unprivileged keepalives per uid: " + mAllowedUnprivilegedSlotsForUid);
         pw.println("Socket keepalives:");
         pw.increaseIndent();
         for (NetworkAgentInfo nai : mKeepalives.keySet()) {
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 7e79a12..d8b7c2e 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -3256,14 +3256,17 @@
                 }
             }
 
-            // On account add, check if there are any settings to be restored.
-            for (AccountAndUser aau : mRunningAccounts) {
-                if (!containsAccountAndUser(oldAccounts, aau.account, aau.userId)) {
-                    if (Log.isLoggable(TAG, Log.DEBUG)) {
-                        Log.d(TAG, "Account " + aau.account + " added, checking sync restore data");
+            if (syncTargets != null) {
+                // On account add, check if there are any settings to be restored.
+                for (AccountAndUser aau : mRunningAccounts) {
+                    if (!containsAccountAndUser(oldAccounts, aau.account, aau.userId)) {
+                        if (Log.isLoggable(TAG, Log.DEBUG)) {
+                            Log.d(TAG, "Account " + aau.account
+                                    + " added, checking sync restore data");
+                        }
+                        AccountSyncSettingsBackupHelper.accountAdded(mContext, syncTargets.userId);
+                        break;
                     }
-                    AccountSyncSettingsBackupHelper.accountAdded(mContext, syncTargets.userId);
-                    break;
                 }
             }
 
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index 85fb1e0..ad81ca2 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -775,10 +775,10 @@
         final ContentResolver cr = getContext().getContentResolver();
         if (isAccessibilityEnabled()) {
             // There are restrictions on the available color modes combined with a11y transforms.
-            if (isColorModeAvailable(COLOR_MODE_SATURATED)) {
-                return COLOR_MODE_SATURATED;
-            } else if (isColorModeAvailable(COLOR_MODE_AUTOMATIC)) {
-                return COLOR_MODE_AUTOMATIC;
+            final int a11yColorMode = getContext().getResources().getInteger(
+                    R.integer.config_accessibilityColorMode);
+            if (a11yColorMode >= 0) {
+                return a11yColorMode;
             }
         }
 
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index b13cd1e..c2125b0 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -506,7 +506,7 @@
     private void showEncryptionNotificationForProfile(UserHandle user) {
         Resources r = mContext.getResources();
         CharSequence title = r.getText(
-                com.android.internal.R.string.user_encrypted_title);
+                com.android.internal.R.string.profile_encrypted_title);
         CharSequence message = r.getText(
                 com.android.internal.R.string.profile_encrypted_message);
         CharSequence detail = r.getText(
@@ -534,7 +534,7 @@
         if (!StorageManager.isFileEncryptedNativeOrEmulated()) return;
 
         Notification notification =
-                new Notification.Builder(mContext, SystemNotificationChannels.SECURITY)
+                new Notification.Builder(mContext, SystemNotificationChannels.DEVICE_ADMIN)
                         .setSmallIcon(com.android.internal.R.drawable.ic_user_secure)
                         .setWhen(0)
                         .setOngoing(true)
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 5d667b6..9e509f4 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -282,13 +282,14 @@
                 throw new IllegalArgumentException("package name must not be empty");
             }
 
+            final UserHandle callingUser = Binder.getCallingUserHandle();
             long callingToken = Binder.clearCallingIdentity();
 
             MediaProjection projection;
             try {
                 ApplicationInfo ai;
                 try {
-                    ai = mPackageManager.getApplicationInfo(packageName, 0);
+                    ai = mPackageManager.getApplicationInfoAsUser(packageName, 0, callingUser);
                 } catch (NameNotFoundException e) {
                     throw new IllegalArgumentException("No package matching :" + packageName);
                 }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 44f7677..35f2149 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -363,7 +363,8 @@
                                 System.currentTimeMillis() - session.getUpdatedMillis();
                         final boolean valid;
                         if (session.isStaged()) {
-                            if (timeSinceUpdate >= MAX_TIME_SINCE_UPDATE_MILLIS) {
+                            if (timeSinceUpdate >= MAX_TIME_SINCE_UPDATE_MILLIS
+                                    && session.isStagedAndInTerminalState()) {
                                 valid = false;
                             } else {
                                 valid = true;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 0760ca3..9d70209 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -20096,14 +20096,14 @@
                     + userId + ":");
             filter.dump(new LogPrinter(Log.INFO, TAG), "  ");
         }
-        if (!updateDefaultHomeNotLocked(userId)) {
-            postPreferredActivityChangedBroadcast(userId);
-        }
         synchronized (mPackages) {
             final PreferredIntentResolver pir = mSettings.editPreferredActivitiesLPw(userId);
             pir.addFilter(new PreferredActivity(filter, match, set, activity, always));
             scheduleWritePackageRestrictionsLocked(userId);
         }
+        if (!updateDefaultHomeNotLocked(userId)) {
+            postPreferredActivityChangedBroadcast(userId);
+        }
     }
 
     private void postPreferredActivityChangedBroadcast(int userId) {
@@ -20418,13 +20418,13 @@
                     + " for user " + userId + ":");
             filter.dump(new LogPrinter(Log.INFO, TAG), "  ");
         }
-        updateDefaultHomeNotLocked(userId);
-        postPreferredActivityChangedBroadcast(userId);
         synchronized (mPackages) {
             mSettings.editPersistentPreferredActivitiesLPw(userId).addFilter(
                     new PersistentPreferredActivity(filter, activity));
             scheduleWritePackageRestrictionsLocked(userId);
         }
+        updateDefaultHomeNotLocked(userId);
+        postPreferredActivityChangedBroadcast(userId);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index b8504db..197a3cf 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -255,6 +255,7 @@
     private volatile boolean mNavigationBarCanMove;
     private volatile boolean mNavigationBarLetsThroughTaps;
     private volatile boolean mNavigationBarAlwaysShowOnSideGesture;
+    private volatile boolean mAllowSeamlessRotationDespiteNavBarMoving;
 
     // Written by vr manager thread, only read in this class.
     private volatile boolean mPersistentVrModeEnabled;
@@ -2726,6 +2727,8 @@
         mNavigationBarCanMove =
                 mDisplayContent.mBaseDisplayWidth != mDisplayContent.mBaseDisplayHeight
                         && res.getBoolean(R.bool.config_navBarCanMove);
+        mAllowSeamlessRotationDespiteNavBarMoving =
+                res.getBoolean(R.bool.config_allowSeamlessRotationDespiteNavBarMoving);
     }
 
     /**
@@ -3508,8 +3511,9 @@
         }
         // If the navigation bar can't change sides, then it will
         // jump when we change orientations and we don't rotate
-        // seamlessly.
-        if (!navigationBarCanMove()) {
+        // seamlessly - unless that is allowed, eg. with gesture
+        // navigation where the navbar is low-profile enough that this isn't very noticeable.
+        if (!navigationBarCanMove() && !mAllowSeamlessRotationDespiteNavBarMoving) {
             return false;
         }
 
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index df36b09..169f03b 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -133,27 +133,33 @@
      * Update the Keyguard showing state.
      */
     void setKeyguardShown(boolean keyguardShowing, boolean aodShowing) {
-        boolean showingChanged = keyguardShowing != mKeyguardShowing || aodShowing != mAodShowing;
         // If keyguard is going away, but SystemUI aborted the transition, need to reset state.
-        showingChanged |= mKeyguardGoingAway && keyguardShowing;
-        if (!showingChanged) {
+        final boolean keyguardChanged = keyguardShowing != mKeyguardShowing
+                || mKeyguardGoingAway && keyguardShowing;
+        final boolean aodChanged = aodShowing != mAodShowing;
+        if (!keyguardChanged && !aodChanged) {
             return;
         }
         mKeyguardShowing = keyguardShowing;
         mAodShowing = aodShowing;
         mWindowManager.setAodShowing(aodShowing);
-        if (showingChanged) {
+
+        if (keyguardChanged) {
+            // Irrelevant to AOD.
             dismissDockedStackIfNeeded();
             setKeyguardGoingAway(false);
-            // TODO(b/113840485): Check usage for non-default display
-            mWindowManager.setKeyguardOrAodShowingOnDefaultDisplay(
-                    isKeyguardOrAodShowing(DEFAULT_DISPLAY));
             if (keyguardShowing) {
                 mDismissalRequested = false;
             }
         }
-        mRootActivityContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+        // TODO(b/113840485): Check usage for non-default display
+        mWindowManager.setKeyguardOrAodShowingOnDefaultDisplay(
+                isKeyguardOrAodShowing(DEFAULT_DISPLAY));
+
+        // Update the sleep token first such that ensureActivitiesVisible has correct sleep token
+        // state when evaluating visibilities.
         updateKeyguardSleepToken();
+        mRootActivityContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
     }
 
     /**
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 204a1ea..fb3076b 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -59,6 +59,7 @@
 #include <android_view_PointerIcon.h>
 #include <android/graphics/GraphicsJNI.h>
 
+#include <nativehelper/ScopedLocalFrame.h>
 #include <nativehelper/ScopedLocalRef.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
 #include <nativehelper/ScopedUtfChars.h>
@@ -723,6 +724,7 @@
     ATRACE_CALL();
 
     JNIEnv* env = jniEnv();
+    ScopedLocalFrame localFrame(env);
 
     jobject tokenObj = javaObjectForIBinder(env, token);
     jstring reasonObj = env->NewStringUTF(reason.c_str());
@@ -735,8 +737,6 @@
     } else {
         assert(newTimeout >= 0);
     }
-
-    env->DeleteLocalRef(reasonObj);
     return newTimeout;
 }
 
@@ -747,6 +747,7 @@
     ATRACE_CALL();
 
     JNIEnv* env = jniEnv();
+    ScopedLocalFrame localFrame(env);
 
     jobject tokenObj = javaObjectForIBinder(env, token);
     if (tokenObj) {
@@ -764,6 +765,7 @@
     ATRACE_CALL();
 
     JNIEnv* env = jniEnv();
+    ScopedLocalFrame localFrame(env);
 
     jobject oldTokenObj = javaObjectForIBinder(env, oldToken);
     jobject newTokenObj = javaObjectForIBinder(env, newToken);
@@ -1139,6 +1141,7 @@
     nsecs_t result = 0;
     if (policyFlags & POLICY_FLAG_TRUSTED) {
         JNIEnv* env = jniEnv();
+        ScopedLocalFrame localFrame(env);
 
         // Token may be null
         jobject tokenObj = javaObjectForIBinder(env, token);
@@ -1173,6 +1176,7 @@
     bool result = false;
     if (policyFlags & POLICY_FLAG_TRUSTED) {
         JNIEnv* env = jniEnv();
+        ScopedLocalFrame localFrame(env);
 
         // Note: tokenObj may be null.
         jobject tokenObj = javaObjectForIBinder(env, token);
@@ -1224,6 +1228,7 @@
 void NativeInputManager::onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) {
     ATRACE_CALL();
     JNIEnv* env = jniEnv();
+    ScopedLocalFrame localFrame(env);
 
     jobject touchedTokenObj = javaObjectForIBinder(env, touchedToken);
     env->CallVoidMethod(mServiceObj, gServiceClassInfo.onPointerDownOutsideFocus, touchedTokenObj);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportUtils.java b/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportUtils.java
index 344077c..0838fbc 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportUtils.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportUtils.java
@@ -20,7 +20,6 @@
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.os.UserHandle;
@@ -65,7 +64,7 @@
                 dialogIntent, 0, null, UserHandle.CURRENT);
 
         Notification.Builder builder =
-                new Notification.Builder(context, SystemNotificationChannels.DEVELOPER)
+                new Notification.Builder(context, SystemNotificationChannels.DEVICE_ADMIN)
                         .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
                         .setOngoing(true)
                         .setLocalOnly(true)
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 24540fd..8c65fa8 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1400,6 +1400,7 @@
 
             traceBeginAndSlog("StartNotificationManager");
             mSystemServiceManager.startService(NotificationManagerService.class);
+            SystemNotificationChannels.removeDeprecated(context);
             SystemNotificationChannels.createAll(context);
             notification = INotificationManager.Stub.asInterface(
                     ServiceManager.getService(Context.NOTIFICATION_SERVICE));
diff --git a/tests/net/common/Android.bp b/tests/net/common/Android.bp
index 3b2e34a..07525a6 100644
--- a/tests/net/common/Android.bp
+++ b/tests/net/common/Android.bp
@@ -23,6 +23,7 @@
         "androidx.test.rules",
         "frameworks-net-testutils",
         "junit",
+        "mockito-target-minus-junit4",
     ],
     libs: [
         "android.test.base.stubs",
diff --git a/tests/net/common/java/android/net/CaptivePortalTest.java b/tests/net/common/java/android/net/CaptivePortalTest.java
new file mode 100644
index 0000000..eed7159f
--- /dev/null
+++ b/tests/net/common/java/android/net/CaptivePortalTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.RemoteException;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class CaptivePortalTest {
+    private static final int DEFAULT_TIMEOUT_MS = 5000;
+    private static final String TEST_PACKAGE_NAME = "com.google.android.test";
+
+    private final class MyCaptivePortalImpl extends ICaptivePortal.Stub {
+        int mCode = -1;
+        String mPackageName = null;
+
+        @Override
+        public void appResponse(final int response) throws RemoteException {
+            mCode = response;
+        }
+
+        @Override
+        public void logEvent(int eventId, String packageName) throws RemoteException {
+            mCode = eventId;
+            mPackageName = packageName;
+        }
+    }
+
+    private interface TestFunctor {
+        void useCaptivePortal(CaptivePortal o);
+    }
+
+    private MyCaptivePortalImpl runCaptivePortalTest(TestFunctor f) {
+        final MyCaptivePortalImpl cp = new MyCaptivePortalImpl();
+        f.useCaptivePortal(new CaptivePortal(cp.asBinder()));
+        return cp;
+    }
+
+    @Test
+    public void testReportCaptivePortalDismissed() {
+        final MyCaptivePortalImpl result =
+                runCaptivePortalTest(c -> c.reportCaptivePortalDismissed());
+        assertEquals(result.mCode, CaptivePortal.APP_RETURN_DISMISSED);
+    }
+
+    @Test
+    public void testIgnoreNetwork() {
+        final MyCaptivePortalImpl result = runCaptivePortalTest(c -> c.ignoreNetwork());
+        assertEquals(result.mCode, CaptivePortal.APP_RETURN_UNWANTED);
+    }
+
+    @Test
+    public void testUseNetwork() {
+        final MyCaptivePortalImpl result = runCaptivePortalTest(c -> c.useNetwork());
+        assertEquals(result.mCode, CaptivePortal.APP_RETURN_WANTED_AS_IS);
+    }
+
+    @Test
+    public void testLogEvent() {
+        final MyCaptivePortalImpl result = runCaptivePortalTest(c -> c.logEvent(
+                MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_ACTIVITY,
+                TEST_PACKAGE_NAME));
+        assertEquals(result.mCode, MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_ACTIVITY);
+        assertEquals(result.mPackageName, TEST_PACKAGE_NAME);
+    }
+}
diff --git a/tests/net/common/java/android/net/LinkPropertiesTest.java b/tests/net/common/java/android/net/LinkPropertiesTest.java
index 709f5f6..e1c4238 100644
--- a/tests/net/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/net/common/java/android/net/LinkPropertiesTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -46,28 +47,80 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class LinkPropertiesTest {
-    private static InetAddress ADDRV4 = NetworkUtils.numericToInetAddress("75.208.6.1");
-    private static InetAddress ADDRV6 = NetworkUtils.numericToInetAddress(
+    private static final InetAddress ADDRV4 = InetAddresses.parseNumericAddress("75.208.6.1");
+    private static final InetAddress ADDRV6 = InetAddresses.parseNumericAddress(
             "2001:0db8:85a3:0000:0000:8a2e:0370:7334");
-    private static InetAddress DNS1 = NetworkUtils.numericToInetAddress("75.208.7.1");
-    private static InetAddress DNS2 = NetworkUtils.numericToInetAddress("69.78.7.1");
-    private static InetAddress DNS6 = NetworkUtils.numericToInetAddress("2001:4860:4860::8888");
-    private static InetAddress PCSCFV6 =  NetworkUtils.numericToInetAddress(
+    private static final InetAddress DNS1 = InetAddresses.parseNumericAddress("75.208.7.1");
+    private static final InetAddress DNS2 = InetAddresses.parseNumericAddress("69.78.7.1");
+    private static final InetAddress DNS6 = InetAddresses.parseNumericAddress(
+            "2001:4860:4860::8888");
+    private static final InetAddress PRIVDNS1 = InetAddresses.parseNumericAddress("1.1.1.1");
+    private static final InetAddress PRIVDNS2 = InetAddresses.parseNumericAddress("1.0.0.1");
+    private static final InetAddress PRIVDNS6 = InetAddresses.parseNumericAddress(
+            "2606:4700:4700::1111");
+    private static final InetAddress PCSCFV4 = InetAddresses.parseNumericAddress("10.77.25.37");
+    private static final InetAddress PCSCFV6 = InetAddresses.parseNumericAddress(
             "2001:0db8:85a3:0000:0000:8a2e:0370:1");
-    private static InetAddress GATEWAY1 = NetworkUtils.numericToInetAddress("75.208.8.1");
-    private static InetAddress GATEWAY2 = NetworkUtils.numericToInetAddress("69.78.8.1");
-    private static InetAddress GATEWAY61 = NetworkUtils.numericToInetAddress("fe80::6:0000:613");
-    private static InetAddress GATEWAY62 = NetworkUtils.numericToInetAddress("fe80::6:2222");
-    private static String NAME = "qmi0";
-    private static int MTU = 1500;
-
-    private static LinkAddress LINKADDRV4 = new LinkAddress(ADDRV4, 32);
-    private static LinkAddress LINKADDRV6 = new LinkAddress(ADDRV6, 128);
-    private static LinkAddress LINKADDRV6LINKLOCAL = new LinkAddress("fe80::1/64");
+    private static final InetAddress GATEWAY1 = InetAddresses.parseNumericAddress("75.208.8.1");
+    private static final InetAddress GATEWAY2 = InetAddresses.parseNumericAddress("69.78.8.1");
+    private static final InetAddress GATEWAY61 = InetAddresses.parseNumericAddress(
+            "fe80::6:0000:613");
+    private static final InetAddress GATEWAY62 = InetAddresses.parseNumericAddress("fe80::6:2222");
+    private static final String NAME = "qmi0";
+    private static final String DOMAINS = "google.com";
+    private static final String PRIV_DNS_SERVER_NAME = "private.dns.com";
+    private static final String TCP_BUFFER_SIZES = "524288,1048576,2097152,262144,524288,1048576";
+    private static final int MTU = 1500;
+    private static final LinkAddress LINKADDRV4 = new LinkAddress(ADDRV4, 32);
+    private static final LinkAddress LINKADDRV6 = new LinkAddress(ADDRV6, 128);
+    private static final LinkAddress LINKADDRV6LINKLOCAL = new LinkAddress("fe80::1/64");
 
     // TODO: replace all calls to NetworkUtils.numericToInetAddress with calls to this method.
     private InetAddress Address(String addrString) {
-        return NetworkUtils.numericToInetAddress(addrString);
+        return InetAddresses.parseNumericAddress(addrString);
+    }
+
+    private void checkEmpty(final LinkProperties lp) {
+        assertEquals(0, lp.getAllInterfaceNames().size());
+        assertEquals(0, lp.getAllAddresses().size());
+        assertEquals(0, lp.getDnsServers().size());
+        assertEquals(0, lp.getValidatedPrivateDnsServers().size());
+        assertEquals(0, lp.getPcscfServers().size());
+        assertEquals(0, lp.getAllRoutes().size());
+        assertEquals(0, lp.getAllLinkAddresses().size());
+        assertEquals(0, lp.getStackedLinks().size());
+        assertEquals(0, lp.getMtu());
+        assertNull(lp.getPrivateDnsServerName());
+        assertNull(lp.getDomains());
+        assertNull(lp.getHttpProxy());
+        assertNull(lp.getTcpBufferSizes());
+        assertNull(lp.getNat64Prefix());
+        assertFalse(lp.isProvisioned());
+        assertFalse(lp.isIpv4Provisioned());
+        assertFalse(lp.isIpv6Provisioned());
+        assertFalse(lp.isPrivateDnsActive());
+    }
+
+    private LinkProperties makeTestObject() {
+        final LinkProperties lp = new LinkProperties();
+        lp.setInterfaceName(NAME);
+        lp.addLinkAddress(LINKADDRV4);
+        lp.addLinkAddress(LINKADDRV6);
+        lp.addDnsServer(DNS1);
+        lp.addDnsServer(DNS2);
+        lp.addValidatedPrivateDnsServer(PRIVDNS1);
+        lp.addValidatedPrivateDnsServer(PRIVDNS2);
+        lp.setUsePrivateDns(true);
+        lp.setPrivateDnsServerName(PRIV_DNS_SERVER_NAME);
+        lp.addPcscfServer(PCSCFV6);
+        lp.setDomains(DOMAINS);
+        lp.addRoute(new RouteInfo(GATEWAY1));
+        lp.addRoute(new RouteInfo(GATEWAY2));
+        lp.setHttpProxy(ProxyInfo.buildDirectProxy("test", 8888));
+        lp.setMtu(MTU);
+        lp.setTcpBufferSizes(TCP_BUFFER_SIZES);
+        lp.setNat64Prefix(new IpPrefix("2001:db8:0:64::/96"));
+        return lp;
     }
 
     public void assertLinkPropertiesEqual(LinkProperties source, LinkProperties target) {
@@ -170,8 +223,7 @@
         target.clear();
         target.setInterfaceName(NAME);
         // change link addresses
-        target.addLinkAddress(new LinkAddress(
-                NetworkUtils.numericToInetAddress("75.208.6.2"), 32));
+        target.addLinkAddress(new LinkAddress(Address("75.208.6.2"), 32));
         target.addLinkAddress(LINKADDRV6);
         target.addDnsServer(DNS1);
         target.addDnsServer(DNS2);
@@ -186,7 +238,7 @@
         target.addLinkAddress(LINKADDRV4);
         target.addLinkAddress(LINKADDRV6);
         // change dnses
-        target.addDnsServer(NetworkUtils.numericToInetAddress("75.208.7.2"));
+        target.addDnsServer(Address("75.208.7.2"));
         target.addDnsServer(DNS2);
         target.addPcscfServer(PCSCFV6);
         target.addRoute(new RouteInfo(GATEWAY1));
@@ -198,11 +250,10 @@
         target.setInterfaceName(NAME);
         target.addLinkAddress(LINKADDRV4);
         target.addLinkAddress(LINKADDRV6);
-        target.addDnsServer(NetworkUtils.numericToInetAddress("75.208.7.2"));
+        target.addDnsServer(Address("75.208.7.2"));
         target.addDnsServer(DNS2);
         // change pcscf
-        target.addPcscfServer(NetworkUtils.numericToInetAddress(
-            "2001::1"));
+        target.addPcscfServer(Address("2001::1"));
         target.addRoute(new RouteInfo(GATEWAY1));
         target.addRoute(new RouteInfo(GATEWAY2));
         target.setMtu(MTU);
@@ -215,7 +266,7 @@
         target.addDnsServer(DNS1);
         target.addDnsServer(DNS2);
         // change gateway
-        target.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress("75.208.8.2")));
+        target.addRoute(new RouteInfo(Address("75.208.8.2")));
         target.addRoute(new RouteInfo(GATEWAY2));
         target.setMtu(MTU);
         assertFalse(source.equals(target));
@@ -285,10 +336,15 @@
         }
     }
 
+    private void assertAllRoutesNotHaveInterface(String iface, LinkProperties lp) {
+        for (RouteInfo r : lp.getRoutes()) {
+            assertNotEquals(iface, r.getInterface());
+        }
+    }
+
     @Test
     public void testRouteInterfaces() {
-        LinkAddress prefix = new LinkAddress(
-            NetworkUtils.numericToInetAddress("2001:db8::"), 32);
+        LinkAddress prefix = new LinkAddress(Address("2001:db8::"), 32);
         InetAddress address = ADDRV6;
 
         // Add a route with no interface to a LinkProperties with no interface. No errors.
@@ -312,6 +368,8 @@
         // Change the interface name. All the routes should change their interface name too.
         lp.setInterfaceName("rmnet0");
         assertAllRoutesHaveInterface("rmnet0", lp);
+        assertAllRoutesNotHaveInterface(null, lp);
+        assertAllRoutesNotHaveInterface("wlan0", lp);
 
         // Now add a route with the wrong interface. This causes an exception too.
         try {
@@ -325,6 +383,7 @@
         lp.addRoute(r);
         assertEquals(2, lp.getRoutes().size());
         assertAllRoutesHaveInterface("wlan0", lp);
+        assertAllRoutesNotHaveInterface("rmnet0", lp);
 
         // Routes with null interfaces are converted to wlan0.
         r = RouteInfo.makeHostRoute(ADDRV6, null);
@@ -334,14 +393,23 @@
 
         // Check comparisons work.
         LinkProperties lp2 = new LinkProperties(lp);
-        assertAllRoutesHaveInterface("wlan0", lp);
+        assertAllRoutesHaveInterface("wlan0", lp2);
         assertEquals(0, lp.compareAllRoutes(lp2).added.size());
         assertEquals(0, lp.compareAllRoutes(lp2).removed.size());
 
         lp2.setInterfaceName("p2p0");
         assertAllRoutesHaveInterface("p2p0", lp2);
+        assertAllRoutesNotHaveInterface("wlan0", lp2);
         assertEquals(3, lp.compareAllRoutes(lp2).added.size());
         assertEquals(3, lp.compareAllRoutes(lp2).removed.size());
+
+        // Check remove works
+        lp.removeRoute(new RouteInfo(prefix, address, null));
+        assertEquals(3, lp.getRoutes().size());
+        lp.removeRoute(new RouteInfo(prefix, address, "wlan0"));
+        assertEquals(2, lp.getRoutes().size());
+        assertAllRoutesHaveInterface("wlan0", lp);
+        assertAllRoutesNotHaveInterface("p2p0", lp);
     }
 
     @Test
@@ -488,18 +556,26 @@
     }
 
     @Test
-    public void testSetLinkAddresses() {
-        LinkProperties lp = new LinkProperties();
+    public void testLinkAddresses() {
+        final LinkProperties lp = new LinkProperties();
         lp.addLinkAddress(LINKADDRV4);
         lp.addLinkAddress(LINKADDRV6);
 
-        LinkProperties lp2 = new LinkProperties();
+        final LinkProperties lp2 = new LinkProperties();
         lp2.addLinkAddress(LINKADDRV6);
 
-        assertFalse(lp.equals(lp2));
+        final LinkProperties lp3 = new LinkProperties();
+        final List<LinkAddress> linkAddresses = Arrays.asList(LINKADDRV4);
+        lp3.setLinkAddresses(linkAddresses);
 
-        lp2.setLinkAddresses(lp.getLinkAddresses());
-        assertTrue(lp.equals(lp));
+        assertFalse(lp.equals(lp2));
+        assertFalse(lp2.equals(lp3));
+
+        lp.removeLinkAddress(LINKADDRV4);
+        assertTrue(lp.equals(lp2));
+
+        lp2.setLinkAddresses(lp3.getLinkAddresses());
+        assertTrue(lp2.equals(lp3));
     }
 
     @Test
@@ -675,9 +751,9 @@
         assertTrue(v4lp.isReachable(DNS2));
 
         final LinkProperties v6lp = new LinkProperties();
-        final InetAddress kLinkLocalDns = NetworkUtils.numericToInetAddress("fe80::6:1");
-        final InetAddress kLinkLocalDnsWithScope = NetworkUtils.numericToInetAddress("fe80::6:2%43");
-        final InetAddress kOnLinkDns = NetworkUtils.numericToInetAddress("2001:db8:85a3::53");
+        final InetAddress kLinkLocalDns = Address("fe80::6:1");
+        final InetAddress kLinkLocalDnsWithScope = Address("fe80::6:2%43");
+        final InetAddress kOnLinkDns = Address("2001:db8:85a3::53");
         assertFalse(v6lp.isReachable(kLinkLocalDns));
         assertFalse(v6lp.isReachable(kLinkLocalDnsWithScope));
         assertFalse(v6lp.isReachable(kOnLinkDns));
@@ -686,8 +762,7 @@
         // Add a link-local route, making the link-local DNS servers reachable. Because
         // we assume the presence of an IPv6 link-local address, link-local DNS servers
         // are considered reachable, but only those with a non-zero scope identifier.
-        assertTrue(v6lp.addRoute(new RouteInfo(
-                new IpPrefix(NetworkUtils.numericToInetAddress("fe80::"), 64))));
+        assertTrue(v6lp.addRoute(new RouteInfo(new IpPrefix(Address("fe80::"), 64))));
         assertFalse(v6lp.isReachable(kLinkLocalDns));
         assertTrue(v6lp.isReachable(kLinkLocalDnsWithScope));
         assertFalse(v6lp.isReachable(kOnLinkDns));
@@ -703,8 +778,7 @@
         // Add a global route on link, but no global address yet. DNS servers reachable
         // via a route that doesn't require a gateway: give them the benefit of the
         // doubt and hope the link-local source address suffices for communication.
-        assertTrue(v6lp.addRoute(new RouteInfo(
-                new IpPrefix(NetworkUtils.numericToInetAddress("2001:db8:85a3::"), 64))));
+        assertTrue(v6lp.addRoute(new RouteInfo(new IpPrefix(Address("2001:db8:85a3::"), 64))));
         assertFalse(v6lp.isReachable(kLinkLocalDns));
         assertTrue(v6lp.isReachable(kLinkLocalDnsWithScope));
         assertTrue(v6lp.isReachable(kOnLinkDns));
@@ -766,8 +840,8 @@
         LinkProperties rmnet1 = new LinkProperties();
         rmnet1.setInterfaceName("rmnet1");
         rmnet1.addLinkAddress(new LinkAddress("10.0.0.3/8"));
-        RouteInfo defaultRoute1 = new RouteInfo((IpPrefix) null,
-                NetworkUtils.numericToInetAddress("10.0.0.1"), rmnet1.getInterfaceName());
+        RouteInfo defaultRoute1 = new RouteInfo((IpPrefix) null, Address("10.0.0.1"),
+                rmnet1.getInterfaceName());
         RouteInfo directRoute1 = new RouteInfo(new IpPrefix("10.0.0.0/8"), null,
                 rmnet1.getInterfaceName());
         rmnet1.addRoute(defaultRoute1);
@@ -785,8 +859,8 @@
         rmnet2.setInterfaceName("rmnet2");
         rmnet2.addLinkAddress(new LinkAddress("fe80::cafe/64"));
         rmnet2.addLinkAddress(new LinkAddress("2001:db8::2/64"));
-        RouteInfo defaultRoute2 = new RouteInfo((IpPrefix) null,
-                NetworkUtils.numericToInetAddress("2001:db8::1"), rmnet2.getInterfaceName());
+        RouteInfo defaultRoute2 = new RouteInfo((IpPrefix) null, Address("2001:db8::1"),
+                rmnet2.getInterfaceName());
         RouteInfo directRoute2 = new RouteInfo(new IpPrefix("2001:db8::/64"), null,
                 rmnet2.getInterfaceName());
         RouteInfo linkLocalRoute2 = new RouteInfo(new IpPrefix("fe80::/64"), null,
@@ -876,4 +950,111 @@
         LinkProperties empty = new LinkProperties();
         TestUtils.assertParcelingIsLossless(empty);
     }
+
+    @Test
+    public void testConstructor() {
+        LinkProperties lp = new LinkProperties();
+        checkEmpty(lp);
+        assertLinkPropertiesEqual(lp, new LinkProperties(lp));
+        assertLinkPropertiesEqual(lp, new LinkProperties());
+
+        lp = makeTestObject();
+        assertLinkPropertiesEqual(lp, new LinkProperties(lp));
+    }
+
+    @Test
+    public void testDnsServers() {
+        final LinkProperties lp = new LinkProperties();
+        final List<InetAddress> dnsServers = Arrays.asList(DNS1, DNS2);
+        lp.setDnsServers(dnsServers);
+        assertEquals(2, lp.getDnsServers().size());
+        assertEquals(DNS1, lp.getDnsServers().get(0));
+        assertEquals(DNS2, lp.getDnsServers().get(1));
+
+        lp.removeDnsServer(DNS1);
+        assertEquals(1, lp.getDnsServers().size());
+        assertEquals(DNS2, lp.getDnsServers().get(0));
+
+        lp.addDnsServer(DNS6);
+        assertEquals(2, lp.getDnsServers().size());
+        assertEquals(DNS2, lp.getDnsServers().get(0));
+        assertEquals(DNS6, lp.getDnsServers().get(1));
+    }
+
+    @Test
+    public void testValidatedPrivateDnsServers() {
+        final LinkProperties lp = new LinkProperties();
+        final List<InetAddress> privDnsServers = Arrays.asList(PRIVDNS1, PRIVDNS2);
+        lp.setValidatedPrivateDnsServers(privDnsServers);
+        assertEquals(2, lp.getValidatedPrivateDnsServers().size());
+        assertEquals(PRIVDNS1, lp.getValidatedPrivateDnsServers().get(0));
+        assertEquals(PRIVDNS2, lp.getValidatedPrivateDnsServers().get(1));
+
+        lp.removeValidatedPrivateDnsServer(PRIVDNS1);
+        assertEquals(1, lp.getValidatedPrivateDnsServers().size());
+        assertEquals(PRIVDNS2, lp.getValidatedPrivateDnsServers().get(0));
+
+        lp.addValidatedPrivateDnsServer(PRIVDNS6);
+        assertEquals(2, lp.getValidatedPrivateDnsServers().size());
+        assertEquals(PRIVDNS2, lp.getValidatedPrivateDnsServers().get(0));
+        assertEquals(PRIVDNS6, lp.getValidatedPrivateDnsServers().get(1));
+    }
+
+    @Test
+    public void testPcscfServers() {
+        final LinkProperties lp = new LinkProperties();
+        final List<InetAddress> pcscfServers = Arrays.asList(PCSCFV4);
+        lp.setPcscfServers(pcscfServers);
+        assertEquals(1, lp.getPcscfServers().size());
+        assertEquals(PCSCFV4, lp.getPcscfServers().get(0));
+
+        lp.removePcscfServer(PCSCFV4);
+        assertEquals(0, lp.getPcscfServers().size());
+
+        lp.addPcscfServer(PCSCFV6);
+        assertEquals(1, lp.getPcscfServers().size());
+        assertEquals(PCSCFV6, lp.getPcscfServers().get(0));
+    }
+
+    @Test
+    public void testTcpBufferSizes() {
+        final LinkProperties lp = makeTestObject();
+        assertEquals(TCP_BUFFER_SIZES, lp.getTcpBufferSizes());
+
+        lp.setTcpBufferSizes(null);
+        assertNull(lp.getTcpBufferSizes());
+    }
+
+    @Test
+    public void testHasIpv6DefaultRoute() {
+        final LinkProperties lp = makeTestObject();
+        assertFalse(lp.hasIPv6DefaultRoute());
+
+        lp.addRoute(new RouteInfo(GATEWAY61));
+        assertTrue(lp.hasIPv6DefaultRoute());
+    }
+
+    @Test
+    public void testHttpProxy() {
+        final LinkProperties lp = makeTestObject();
+        assertTrue(lp.getHttpProxy().equals(ProxyInfo.buildDirectProxy("test", 8888)));
+    }
+
+    @Test
+    public void testPrivateDnsServerName() {
+        final LinkProperties lp = makeTestObject();
+        assertEquals(PRIV_DNS_SERVER_NAME, lp.getPrivateDnsServerName());
+
+        lp.setPrivateDnsServerName(null);
+        assertNull(lp.getPrivateDnsServerName());
+    }
+
+    @Test
+    public void testUsePrivateDns() {
+        final LinkProperties lp = makeTestObject();
+        assertTrue(lp.isPrivateDnsActive());
+
+        lp.clear();
+        assertFalse(lp.isPrivateDnsActive());
+    }
 }
diff --git a/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java b/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java
index 3ed8a86..0ce7c91 100644
--- a/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java
+++ b/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java
@@ -17,7 +17,9 @@
 package android.net.apf;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -32,8 +34,12 @@
 @SmallTest
 public class ApfCapabilitiesTest {
     @Test
-    public void testParcelUnparcel() {
+    public void testConstructAndParcel() {
         final ApfCapabilities caps = new ApfCapabilities(123, 456, 789);
+        assertEquals(123, caps.apfVersionSupported);
+        assertEquals(456, caps.maximumApfProgramSize);
+        assertEquals(789, caps.apfPacketFormat);
+
         ParcelableTestUtil.assertFieldCountEquals(3, ApfCapabilities.class);
 
         TestUtils.assertParcelingIsLossless(caps);
@@ -46,4 +52,14 @@
         assertNotEquals(new ApfCapabilities(1, 3, 3), new ApfCapabilities(1, 2, 3));
         assertNotEquals(new ApfCapabilities(1, 2, 4), new ApfCapabilities(1, 2, 3));
     }
+
+    @Test
+    public void testHasDataAccess() {
+        //hasDataAccess is only supported starting at apf version 4.
+        ApfCapabilities caps = new ApfCapabilities(1 /* apfVersionSupported */, 2, 3);
+        assertFalse(caps.hasDataAccess());
+
+        caps = new ApfCapabilities(4 /* apfVersionSupported */, 5, 6);
+        assertTrue(caps.hasDataAccess());
+    }
 }
diff --git a/tests/net/common/java/android/net/metrics/ApfProgramEventTest.kt b/tests/net/common/java/android/net/metrics/ApfProgramEventTest.kt
new file mode 100644
index 0000000..8d055c9
--- /dev/null
+++ b/tests/net/common/java/android/net/metrics/ApfProgramEventTest.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.metrics;
+
+import android.os.Parcelable
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.internal.util.ParcelableTestUtil
+import com.android.internal.util.TestUtils
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class ApfProgramEventTest {
+    private fun <T: Parcelable> testParcel(obj: T, fieldCount: Int) {
+        ParcelableTestUtil.assertFieldCountEquals(fieldCount, obj::class.java)
+        TestUtils.assertParcelingIsLossless(obj)
+    }
+
+    private infix fun Int.hasFlag(flag: Int) = (this and (1 shl flag)) != 0
+
+    @Test
+    fun testBuilderAndParcel() {
+        val apfProgramEvent = ApfProgramEvent.Builder()
+                .setLifetime(1)
+                .setActualLifetime(2)
+                .setFilteredRas(3)
+                .setCurrentRas(4)
+                .setProgramLength(5)
+                .setFlags(true, true)
+                .build()
+
+        assertEquals(1, apfProgramEvent.lifetime)
+        assertEquals(2, apfProgramEvent.actualLifetime)
+        assertEquals(3, apfProgramEvent.filteredRas)
+        assertEquals(4, apfProgramEvent.currentRas)
+        assertEquals(5, apfProgramEvent.programLength)
+        assertEquals(ApfProgramEvent.flagsFor(true, true), apfProgramEvent.flags)
+
+        testParcel(apfProgramEvent, 6)
+    }
+
+    @Test
+    fun testFlagsFor() {
+        var flags = ApfProgramEvent.flagsFor(false, false)
+        assertFalse(flags hasFlag ApfProgramEvent.FLAG_HAS_IPV4_ADDRESS)
+        assertFalse(flags hasFlag ApfProgramEvent.FLAG_MULTICAST_FILTER_ON)
+
+        flags = ApfProgramEvent.flagsFor(true, false)
+        assertTrue(flags hasFlag ApfProgramEvent.FLAG_HAS_IPV4_ADDRESS)
+        assertFalse(flags hasFlag ApfProgramEvent.FLAG_MULTICAST_FILTER_ON)
+
+        flags = ApfProgramEvent.flagsFor(false, true)
+        assertFalse(flags hasFlag ApfProgramEvent.FLAG_HAS_IPV4_ADDRESS)
+        assertTrue(flags hasFlag ApfProgramEvent.FLAG_MULTICAST_FILTER_ON)
+
+        flags = ApfProgramEvent.flagsFor(true, true)
+        assertTrue(flags hasFlag ApfProgramEvent.FLAG_HAS_IPV4_ADDRESS)
+        assertTrue(flags hasFlag ApfProgramEvent.FLAG_MULTICAST_FILTER_ON)
+    }
+}
diff --git a/tests/net/common/java/android/net/metrics/ApfStatsTest.kt b/tests/net/common/java/android/net/metrics/ApfStatsTest.kt
new file mode 100644
index 0000000..f8eb40c
--- /dev/null
+++ b/tests/net/common/java/android/net/metrics/ApfStatsTest.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.metrics
+
+import android.os.Parcelable
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.internal.util.ParcelableTestUtil
+import com.android.internal.util.TestUtils
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class ApfStatsTest {
+    private fun <T: Parcelable> testParcel(obj: T, fieldCount: Int) {
+        ParcelableTestUtil.assertFieldCountEquals(fieldCount, obj::class.java)
+        TestUtils.assertParcelingIsLossless(obj)
+    }
+
+    @Test
+    fun testBuilderAndParcel() {
+        val apfStats = ApfStats.Builder()
+                .setDurationMs(Long.MAX_VALUE)
+                .setReceivedRas(1)
+                .setMatchingRas(2)
+                .setDroppedRas(3)
+                .setZeroLifetimeRas(4)
+                .setParseErrors(5)
+                .setProgramUpdates(6)
+                .setProgramUpdatesAll(7)
+                .setProgramUpdatesAllowingMulticast(8)
+                .setMaxProgramSize(9)
+                .build()
+
+        assertEquals(Long.MAX_VALUE, apfStats.durationMs)
+        assertEquals(1, apfStats.receivedRas)
+        assertEquals(2, apfStats.matchingRas)
+        assertEquals(3, apfStats.droppedRas)
+        assertEquals(4, apfStats.zeroLifetimeRas)
+        assertEquals(5, apfStats.parseErrors)
+        assertEquals(6, apfStats.programUpdates)
+        assertEquals(7, apfStats.programUpdatesAll)
+        assertEquals(8, apfStats.programUpdatesAllowingMulticast)
+        assertEquals(9, apfStats.maxProgramSize)
+
+        testParcel(apfStats, 10)
+    }
+}
diff --git a/tests/net/common/java/android/net/metrics/DhcpClientEventTest.kt b/tests/net/common/java/android/net/metrics/DhcpClientEventTest.kt
new file mode 100644
index 0000000..36e9f8c
--- /dev/null
+++ b/tests/net/common/java/android/net/metrics/DhcpClientEventTest.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.metrics
+
+import android.os.Parcelable
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.internal.util.ParcelableTestUtil
+import com.android.internal.util.TestUtils
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val FAKE_MESSAGE = "test"
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class DhcpClientEventTest {
+    private fun <T: Parcelable> testParcel(obj: T, fieldCount: Int) {
+        ParcelableTestUtil.assertFieldCountEquals(fieldCount, obj::class.java)
+        TestUtils.assertParcelingIsLossless(obj)
+    }
+
+    @Test
+    fun testBuilderAndParcel() {
+        val dhcpClientEvent = DhcpClientEvent.Builder()
+                .setMsg(FAKE_MESSAGE)
+                .setDurationMs(Integer.MAX_VALUE)
+                .build()
+
+        assertEquals(FAKE_MESSAGE, dhcpClientEvent.msg)
+        assertEquals(Integer.MAX_VALUE, dhcpClientEvent.durationMs)
+
+        testParcel(dhcpClientEvent, 2)
+    }
+}
diff --git a/tests/net/common/java/android/net/metrics/DhcpErrorEventTest.kt b/tests/net/common/java/android/net/metrics/DhcpErrorEventTest.kt
index e191953..e9d5e6d 100644
--- a/tests/net/common/java/android/net/metrics/DhcpErrorEventTest.kt
+++ b/tests/net/common/java/android/net/metrics/DhcpErrorEventTest.kt
@@ -13,9 +13,7 @@
 import org.junit.runner.RunWith
 
 private const val TEST_ERROR_CODE = 12345
-/**
- * DHCP Optional Type: DHCP Subnet Mask (Copy from DhcpPacket.java)
- */
+//DHCP Optional Type: DHCP Subnet Mask (Copy from DhcpPacket.java due to it's protected)
 private const val DHCP_SUBNET_MASK = 1
 
 @RunWith(AndroidJUnit4::class)
diff --git a/tests/net/common/java/android/net/metrics/IpConnectivityLogTest.java b/tests/net/common/java/android/net/metrics/IpConnectivityLogTest.java
new file mode 100644
index 0000000..d4780d3
--- /dev/null
+++ b/tests/net/common/java/android/net/metrics/IpConnectivityLogTest.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.metrics;
+
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.net.ConnectivityMetricsEvent;
+import android.net.IIpConnectivityMetrics;
+import android.net.Network;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.BitUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class IpConnectivityLogTest {
+    private static final int FAKE_NET_ID = 100;
+    private static final int[] FAKE_TRANSPORT_TYPES = BitUtils.unpackBits(TRANSPORT_WIFI);
+    private static final long FAKE_TIME_STAMP = System.currentTimeMillis();
+    private static final String FAKE_INTERFACE_NAME = "test";
+    private static final IpReachabilityEvent FAKE_EV =
+            new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED);
+
+    @Mock IIpConnectivityMetrics mMockService;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testLoggingEvents() throws Exception {
+        IpConnectivityLog logger = new IpConnectivityLog(mMockService);
+
+        assertTrue(logger.log(FAKE_EV));
+        assertTrue(logger.log(FAKE_TIME_STAMP, FAKE_EV));
+        assertTrue(logger.log(FAKE_NET_ID, FAKE_TRANSPORT_TYPES, FAKE_EV));
+        assertTrue(logger.log(new Network(FAKE_NET_ID), FAKE_TRANSPORT_TYPES, FAKE_EV));
+        assertTrue(logger.log(FAKE_INTERFACE_NAME, FAKE_EV));
+        assertTrue(logger.log(makeExpectedEvent(FAKE_TIME_STAMP, FAKE_NET_ID, TRANSPORT_WIFI,
+                FAKE_INTERFACE_NAME)));
+
+        List<ConnectivityMetricsEvent> got = verifyEvents(6);
+        assertEventsEqual(makeExpectedEvent(got.get(0).timestamp, 0, 0, null), got.get(0));
+        assertEventsEqual(makeExpectedEvent(FAKE_TIME_STAMP, 0, 0, null), got.get(1));
+        assertEventsEqual(makeExpectedEvent(got.get(2).timestamp, FAKE_NET_ID,
+                TRANSPORT_WIFI, null), got.get(2));
+        assertEventsEqual(makeExpectedEvent(got.get(3).timestamp, FAKE_NET_ID,
+                TRANSPORT_WIFI, null), got.get(3));
+        assertEventsEqual(makeExpectedEvent(got.get(4).timestamp, 0, 0, FAKE_INTERFACE_NAME),
+                got.get(4));
+        assertEventsEqual(makeExpectedEvent(FAKE_TIME_STAMP, FAKE_NET_ID,
+                TRANSPORT_WIFI, FAKE_INTERFACE_NAME), got.get(5));
+    }
+
+    @Test
+    public void testLoggingEventsWithMultipleCallers() throws Exception {
+        IpConnectivityLog logger = new IpConnectivityLog(mMockService);
+
+        final int nCallers = 10;
+        final int nEvents = 10;
+        for (int n = 0; n < nCallers; n++) {
+            final int i = n;
+            new Thread() {
+                public void run() {
+                    for (int j = 0; j < nEvents; j++) {
+                        assertTrue(logger.log(makeExpectedEvent(
+                                FAKE_TIME_STAMP + i * 100 + j,
+                                FAKE_NET_ID + i * 100 + j,
+                                ((i + j) % 2 == 0) ? TRANSPORT_WIFI : TRANSPORT_CELLULAR,
+                                FAKE_INTERFACE_NAME)));
+                    }
+                }
+            }.start();
+        }
+
+        List<ConnectivityMetricsEvent> got = verifyEvents(nCallers * nEvents, 200);
+        Collections.sort(got, EVENT_COMPARATOR);
+        Iterator<ConnectivityMetricsEvent> iter = got.iterator();
+        for (int i = 0; i < nCallers; i++) {
+            for (int j = 0; j < nEvents; j++) {
+                final long expectedTimestamp = FAKE_TIME_STAMP + i * 100 + j;
+                final int expectedNetId = FAKE_NET_ID + i * 100 + j;
+                final long expectedTransports =
+                        ((i + j) % 2 == 0) ? TRANSPORT_WIFI : TRANSPORT_CELLULAR;
+                assertEventsEqual(makeExpectedEvent(expectedTimestamp, expectedNetId,
+                        expectedTransports, FAKE_INTERFACE_NAME), iter.next());
+            }
+        }
+    }
+
+    private List<ConnectivityMetricsEvent> verifyEvents(int n, int timeoutMs) throws Exception {
+        ArgumentCaptor<ConnectivityMetricsEvent> captor =
+                ArgumentCaptor.forClass(ConnectivityMetricsEvent.class);
+        verify(mMockService, timeout(timeoutMs).times(n)).logEvent(captor.capture());
+        return captor.getAllValues();
+    }
+
+    private List<ConnectivityMetricsEvent> verifyEvents(int n) throws Exception {
+        return verifyEvents(n, 10);
+    }
+
+
+    private ConnectivityMetricsEvent makeExpectedEvent(long timestamp, int netId, long transports,
+            String ifname) {
+        ConnectivityMetricsEvent ev = new ConnectivityMetricsEvent();
+        ev.timestamp = timestamp;
+        ev.data = FAKE_EV;
+        ev.netId = netId;
+        ev.transports = transports;
+        ev.ifname = ifname;
+        return ev;
+    }
+
+    /** Outer equality for ConnectivityMetricsEvent to avoid overriding equals() and hashCode(). */
+    private void assertEventsEqual(ConnectivityMetricsEvent expected,
+            ConnectivityMetricsEvent got) {
+        assertEquals(expected.data, got.data);
+        assertEquals(expected.timestamp, got.timestamp);
+        assertEquals(expected.netId, got.netId);
+        assertEquals(expected.transports, got.transports);
+        assertEquals(expected.ifname, got.ifname);
+    }
+
+    static final Comparator<ConnectivityMetricsEvent> EVENT_COMPARATOR =
+            Comparator.comparingLong((ev) -> ev.timestamp);
+}
diff --git a/tests/net/common/java/android/net/metrics/IpManagerEventTest.kt b/tests/net/common/java/android/net/metrics/IpManagerEventTest.kt
new file mode 100644
index 0000000..5144ca5
--- /dev/null
+++ b/tests/net/common/java/android/net/metrics/IpManagerEventTest.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.metrics
+
+import android.os.Parcelable
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.internal.util.ParcelableTestUtil
+import com.android.internal.util.TestUtils
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class IpManagerEventTest {
+    private fun <T: Parcelable> testParcel(obj: T, fieldCount: Int) {
+        ParcelableTestUtil.assertFieldCountEquals(fieldCount, obj::class.java)
+        TestUtils.assertParcelingIsLossless(obj)
+    }
+
+    @Test
+    fun testConstructorAndParcel() {
+        (IpManagerEvent.PROVISIONING_OK..IpManagerEvent.ERROR_INTERFACE_NOT_FOUND).forEach {
+            val ipManagerEvent = IpManagerEvent(it, Long.MAX_VALUE)
+            assertEquals(it, ipManagerEvent.eventType)
+            assertEquals(Long.MAX_VALUE, ipManagerEvent.durationMs)
+
+            testParcel(ipManagerEvent, 2)
+        }
+    }
+}
diff --git a/tests/net/common/java/android/net/metrics/IpReachabilityEventTest.kt b/tests/net/common/java/android/net/metrics/IpReachabilityEventTest.kt
new file mode 100644
index 0000000..d76ebf6
--- /dev/null
+++ b/tests/net/common/java/android/net/metrics/IpReachabilityEventTest.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.metrics
+
+import android.os.Parcelable
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.internal.util.ParcelableTestUtil
+import com.android.internal.util.TestUtils
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class IpReachabilityEventTest {
+    private fun <T: Parcelable> testParcel(obj: T, fieldCount: Int) {
+        ParcelableTestUtil.assertFieldCountEquals(fieldCount, obj::class.java)
+        TestUtils.assertParcelingIsLossless(obj)
+    }
+
+    @Test
+    fun testConstructorAndParcel() {
+        (IpReachabilityEvent.PROBE..IpReachabilityEvent.PROVISIONING_LOST_ORGANIC).forEach {
+            val ipReachabilityEvent = IpReachabilityEvent(it)
+            assertEquals(it, ipReachabilityEvent.eventType)
+
+            testParcel(ipReachabilityEvent, 1)
+        }
+    }
+}
diff --git a/tests/net/common/java/android/net/metrics/NetworkEventTest.kt b/tests/net/common/java/android/net/metrics/NetworkEventTest.kt
new file mode 100644
index 0000000..8b52e81
--- /dev/null
+++ b/tests/net/common/java/android/net/metrics/NetworkEventTest.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.metrics
+
+import android.os.Parcelable
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.internal.util.ParcelableTestUtil
+import com.android.internal.util.TestUtils
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class NetworkEventTest {
+    private fun <T: Parcelable> testParcel(obj: T, fieldCount: Int) {
+        ParcelableTestUtil.assertFieldCountEquals(fieldCount, obj::class.java)
+        TestUtils.assertParcelingIsLossless(obj)
+    }
+
+    @Test
+    fun testConstructorAndParcel() {
+        (NetworkEvent.NETWORK_CONNECTED..NetworkEvent.NETWORK_PARTIAL_CONNECTIVITY).forEach {
+            var networkEvent = NetworkEvent(it)
+            assertEquals(it, networkEvent.eventType)
+            assertEquals(0, networkEvent.durationMs)
+
+            networkEvent = NetworkEvent(it, Long.MAX_VALUE)
+            assertEquals(it, networkEvent.eventType)
+            assertEquals(Long.MAX_VALUE, networkEvent.durationMs)
+
+            testParcel(networkEvent, 2)
+        }
+    }
+}
diff --git a/tests/net/common/java/android/net/metrics/RaEventTest.kt b/tests/net/common/java/android/net/metrics/RaEventTest.kt
new file mode 100644
index 0000000..f38d328
--- /dev/null
+++ b/tests/net/common/java/android/net/metrics/RaEventTest.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.metrics
+
+import android.os.Parcelable
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.internal.util.ParcelableTestUtil
+import com.android.internal.util.TestUtils
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val NO_LIFETIME: Long = -1L
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class RaEventTest {
+    private fun <T: Parcelable> testParcel(obj: T, fieldCount: Int) {
+        ParcelableTestUtil.assertFieldCountEquals(fieldCount, obj::class.java)
+        TestUtils.assertParcelingIsLossless(obj)
+    }
+
+    @Test
+    fun testConstructorAndParcel() {
+        var raEvent = RaEvent.Builder().build()
+        assertEquals(NO_LIFETIME, raEvent.routerLifetime)
+        assertEquals(NO_LIFETIME, raEvent.prefixValidLifetime)
+        assertEquals(NO_LIFETIME, raEvent.prefixPreferredLifetime)
+        assertEquals(NO_LIFETIME, raEvent.routeInfoLifetime)
+        assertEquals(NO_LIFETIME, raEvent.rdnssLifetime)
+        assertEquals(NO_LIFETIME, raEvent.dnsslLifetime)
+
+        raEvent = RaEvent.Builder()
+                .updateRouterLifetime(1)
+                .updatePrefixValidLifetime(2)
+                .updatePrefixPreferredLifetime(3)
+                .updateRouteInfoLifetime(4)
+                .updateRdnssLifetime(5)
+                .updateDnsslLifetime(6)
+                .build()
+        assertEquals(1, raEvent.routerLifetime)
+        assertEquals(2, raEvent.prefixValidLifetime)
+        assertEquals(3, raEvent.prefixPreferredLifetime)
+        assertEquals(4, raEvent.routeInfoLifetime)
+        assertEquals(5, raEvent.rdnssLifetime)
+        assertEquals(6, raEvent.dnsslLifetime)
+
+        raEvent = RaEvent.Builder()
+                .updateRouterLifetime(Long.MIN_VALUE)
+                .updateRouterLifetime(Long.MAX_VALUE)
+                .build()
+        assertEquals(Long.MIN_VALUE, raEvent.routerLifetime)
+
+        raEvent = RaEvent(1, 2, 3, 4, 5, 6)
+        assertEquals(1, raEvent.routerLifetime)
+        assertEquals(2, raEvent.prefixValidLifetime)
+        assertEquals(3, raEvent.prefixPreferredLifetime)
+        assertEquals(4, raEvent.routeInfoLifetime)
+        assertEquals(5, raEvent.rdnssLifetime)
+        assertEquals(6, raEvent.dnsslLifetime)
+
+        testParcel(raEvent, 6)
+    }
+}
diff --git a/tests/net/common/java/android/net/metrics/ValidationProbeEventTest.kt b/tests/net/common/java/android/net/metrics/ValidationProbeEventTest.kt
new file mode 100644
index 0000000..c0cef8f
--- /dev/null
+++ b/tests/net/common/java/android/net/metrics/ValidationProbeEventTest.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.metrics
+
+import android.os.Parcelable
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.internal.util.ParcelableTestUtil
+import com.android.internal.util.TestUtils
+import java.lang.reflect.Modifier
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val FIRST_VALIDATION: Int = 1 shl 8
+private const val REVALIDATION: Int = 2 shl 8
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class ValidationProbeEventTest {
+    private fun <T: Parcelable> testParcel(obj: T, fieldCount: Int) {
+        ParcelableTestUtil.assertFieldCountEquals(fieldCount, obj::class.java)
+        TestUtils.assertParcelingIsLossless(obj)
+    }
+
+    private infix fun Int.hasType(type: Int) = (type and this) == type
+
+    @Test
+    fun testBuilderAndParcel() {
+        var validationProbeEvent = ValidationProbeEvent.Builder()
+                .setProbeType(ValidationProbeEvent.PROBE_DNS, false).build()
+
+        assertTrue(validationProbeEvent.probeType hasType REVALIDATION)
+
+        validationProbeEvent = ValidationProbeEvent.Builder()
+                .setDurationMs(Long.MAX_VALUE)
+                .setProbeType(ValidationProbeEvent.PROBE_DNS, true)
+                .setReturnCode(ValidationProbeEvent.DNS_SUCCESS)
+                .build()
+
+        assertEquals(Long.MAX_VALUE, validationProbeEvent.durationMs)
+        assertTrue(validationProbeEvent.probeType hasType ValidationProbeEvent.PROBE_DNS)
+        assertTrue(validationProbeEvent.probeType hasType FIRST_VALIDATION)
+        assertEquals(ValidationProbeEvent.DNS_SUCCESS, validationProbeEvent.returnCode)
+
+        testParcel(validationProbeEvent, 3)
+    }
+
+    @Test
+    fun testGetProbeName() {
+        val probeFields = ValidationProbeEvent::class.java.declaredFields.filter {
+            it.type == Int::class.javaPrimitiveType
+              && Modifier.isPublic(it.modifiers) && Modifier.isStatic(it.modifiers)
+              && it.name.contains("PROBE")
+        }
+
+        probeFields.forEach {
+            val intValue = it.getInt(null)
+            val stringValue = ValidationProbeEvent.getProbeName(intValue)
+            assertEquals(it.name, stringValue)
+        }
+
+    }
+}
diff --git a/tests/net/java/android/net/util/KeepaliveUtilsTest.kt b/tests/net/java/android/net/util/KeepaliveUtilsTest.kt
new file mode 100644
index 0000000..814e06e
--- /dev/null
+++ b/tests/net/java/android/net/util/KeepaliveUtilsTest.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.util
+
+import android.content.Context
+import android.content.res.Resources
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.MAX_TRANSPORT
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
+import android.net.NetworkCapabilities.TRANSPORT_VPN
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import org.junit.Assert.assertArrayEquals
+import org.junit.Assert.assertEquals
+import org.junit.Assert.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+
+/**
+ * Tests for [KeepaliveUtils].
+ *
+ * Build, install and run with:
+ * atest android.net.util.KeepaliveUtilsTest
+ */
+@RunWith(JUnit4::class)
+@SmallTest
+class KeepaliveUtilsTest {
+
+    // Prepare mocked context with given resource strings.
+    private fun getMockedContextWithStringArrayRes(id: Int, res: Array<out String?>?): Context {
+        val mockRes = mock(Resources::class.java)
+        doReturn(res).`when`(mockRes).getStringArray(ArgumentMatchers.eq(id))
+
+        return mock(Context::class.java).apply {
+            doReturn(mockRes).`when`(this).getResources()
+        }
+    }
+
+    @Test
+    fun testGetSupportedKeepalives() {
+        fun assertRunWithException(res: Array<out String?>?) {
+            try {
+                val mockContext = getMockedContextWithStringArrayRes(
+                        R.array.config_networkSupportedKeepaliveCount, res)
+                KeepaliveUtils.getSupportedKeepalives(mockContext)
+                fail("Expected KeepaliveDeviceConfigurationException")
+            } catch (expected: KeepaliveUtils.KeepaliveDeviceConfigurationException) {
+            }
+        }
+
+        // Check resource with various invalid format.
+        assertRunWithException(null)
+        assertRunWithException(arrayOf<String?>(null))
+        assertRunWithException(arrayOfNulls<String?>(10))
+        assertRunWithException(arrayOf(""))
+        assertRunWithException(arrayOf("3,ABC"))
+        assertRunWithException(arrayOf("6,3,3"))
+        assertRunWithException(arrayOf("5"))
+
+        // Check resource with invalid slots value.
+        assertRunWithException(arrayOf("2,2"))
+        assertRunWithException(arrayOf("3,-1"))
+
+        // Check resource with invalid transport type.
+        assertRunWithException(arrayOf("-1,3"))
+        assertRunWithException(arrayOf("10,3"))
+
+        // Check valid customization generates expected array.
+        val validRes = arrayOf("0,3", "1,0", "4,4")
+        val expectedValidRes = intArrayOf(3, 0, 0, 0, 4, 0, 0, 0)
+
+        val mockContext = getMockedContextWithStringArrayRes(
+                R.array.config_networkSupportedKeepaliveCount, validRes)
+        val actual = KeepaliveUtils.getSupportedKeepalives(mockContext)
+        assertArrayEquals(expectedValidRes, actual)
+    }
+
+    @Test
+    fun testGetSupportedKeepalivesForNetworkCapabilities() {
+        // Mock customized supported keepalives for each transport type, and assuming:
+        //   3 for cellular,
+        //   6 for wifi,
+        //   0 for others.
+        val cust = IntArray(MAX_TRANSPORT + 1).apply {
+            this[TRANSPORT_CELLULAR] = 3
+            this[TRANSPORT_WIFI] = 6
+        }
+
+        val nc = NetworkCapabilities()
+        // Check supported keepalives with single transport type.
+        nc.transportTypes = intArrayOf(TRANSPORT_CELLULAR)
+        assertEquals(3, KeepaliveUtils.getSupportedKeepalivesForNetworkCapabilities(cust, nc))
+
+        // Check supported keepalives with multiple transport types.
+        nc.transportTypes = intArrayOf(TRANSPORT_WIFI, TRANSPORT_VPN)
+        assertEquals(0, KeepaliveUtils.getSupportedKeepalivesForNetworkCapabilities(cust, nc))
+
+        // Check supported keepalives with non-customized transport type.
+        nc.transportTypes = intArrayOf(TRANSPORT_ETHERNET)
+        assertEquals(0, KeepaliveUtils.getSupportedKeepalivesForNetworkCapabilities(cust, nc))
+
+        // Check supported keepalives with undefined transport type.
+        nc.transportTypes = intArrayOf(MAX_TRANSPORT + 1)
+        try {
+            KeepaliveUtils.getSupportedKeepalivesForNetworkCapabilities(cust, nc)
+            fail("Expected ArrayIndexOutOfBoundsException")
+        } catch (expected: ArrayIndexOutOfBoundsException) {
+        }
+    }
+}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index fe13787..b0cc207 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -4342,8 +4342,9 @@
         }
 
         // Check that there is no port leaked after all keepalives and sockets are closed.
-        assertFalse(isUdpPortInUse(srcPort));
-        assertFalse(isUdpPortInUse(srcPort2));
+        // TODO: enable this check after ensuring a valid free port. See b/129512753#comment7.
+        // assertFalse(isUdpPortInUse(srcPort));
+        // assertFalse(isUdpPortInUse(srcPort2));
 
         mWiFiNetworkAgent.disconnect();
         waitFor(mWiFiNetworkAgent.getDisconnectedCV());
@@ -4471,7 +4472,8 @@
         assertEquals(anyIPv4, sa.getAddress());
 
         testPfd.close();
-        assertFalse(isUdpPortInUse(srcPort));
+        // TODO: enable this check after ensuring a valid free port. See b/129512753#comment7.
+        // assertFalse(isUdpPortInUse(srcPort));
 
         mWiFiNetworkAgent.disconnect();
         waitFor(mWiFiNetworkAgent.getDisconnectedCV());
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
index d5b2c87..3a07166 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -21,11 +21,8 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
@@ -59,16 +56,11 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.List;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -98,48 +90,6 @@
     }
 
     @Test
-    public void testLoggingEvents() throws Exception {
-        IpConnectivityLog logger = new IpConnectivityLog(mMockService);
-
-        assertTrue(logger.log(1, FAKE_EV));
-        assertTrue(logger.log(2, FAKE_EV));
-        assertTrue(logger.log(3, FAKE_EV));
-
-        List<ConnectivityMetricsEvent> got = verifyEvents(3);
-        assertEventsEqual(expectedEvent(1), got.get(0));
-        assertEventsEqual(expectedEvent(2), got.get(1));
-        assertEventsEqual(expectedEvent(3), got.get(2));
-    }
-
-    @Test
-    public void testLoggingEventsWithMultipleCallers() throws Exception {
-        IpConnectivityLog logger = new IpConnectivityLog(mMockService);
-
-        final int nCallers = 10;
-        final int nEvents = 10;
-        for (int n = 0; n < nCallers; n++) {
-            final int i = n;
-            new Thread() {
-                public void run() {
-                    for (int j = 0; j < nEvents; j++) {
-                        assertTrue(logger.log(1 + i * 100 + j, FAKE_EV));
-                    }
-                }
-            }.start();
-        }
-
-        List<ConnectivityMetricsEvent> got = verifyEvents(nCallers * nEvents, 200);
-        Collections.sort(got, EVENT_COMPARATOR);
-        Iterator<ConnectivityMetricsEvent> iter = got.iterator();
-        for (int i = 0; i < nCallers; i++) {
-            for (int j = 0; j < nEvents; j++) {
-                int expectedTimestamp = 1 + i * 100 + j;
-                assertEventsEqual(expectedEvent(expectedTimestamp), iter.next());
-            }
-        }
-    }
-
-    @Test
     public void testBufferFlushing() {
         String output1 = getdump("flush");
         assertEquals("", output1);
@@ -653,16 +603,7 @@
         return nai;
     }
 
-    List<ConnectivityMetricsEvent> verifyEvents(int n, int timeoutMs) throws Exception {
-        ArgumentCaptor<ConnectivityMetricsEvent> captor =
-                ArgumentCaptor.forClass(ConnectivityMetricsEvent.class);
-        verify(mMockService, timeout(timeoutMs).times(n)).logEvent(captor.capture());
-        return captor.getAllValues();
-    }
 
-    List<ConnectivityMetricsEvent> verifyEvents(int n) throws Exception {
-        return verifyEvents(n, 10);
-    }
 
     static void verifySerialization(String want, String output) {
         try {
@@ -674,28 +615,4 @@
             fail(e.toString());
         }
     }
-
-    static String joinLines(String ... elems) {
-        StringBuilder b = new StringBuilder();
-        for (String s : elems) {
-            b.append(s).append("\n");
-        }
-        return b.toString();
-    }
-
-    static ConnectivityMetricsEvent expectedEvent(int timestamp) {
-        ConnectivityMetricsEvent ev = new ConnectivityMetricsEvent();
-        ev.timestamp = timestamp;
-        ev.data = FAKE_EV;
-        return ev;
-    }
-
-    /** Outer equality for ConnectivityMetricsEvent to avoid overriding equals() and hashCode(). */
-    static void assertEventsEqual(ConnectivityMetricsEvent expected, ConnectivityMetricsEvent got) {
-        assertEquals(expected.timestamp, got.timestamp);
-        assertEquals(expected.data, got.data);
-    }
-
-    static final Comparator<ConnectivityMetricsEvent> EVENT_COMPARATOR =
-        Comparator.comparingLong((ev) -> ev.timestamp);
 }