Merge "Restricted permission mechanism - framework" into qt-dev
diff --git a/api/current.txt b/api/current.txt
index d24e350..561a20b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -52359,18 +52359,18 @@
     method public void setCheckable(boolean);
     method public void setChecked(boolean);
     method public void setClassName(CharSequence);
-    method @Deprecated public void setClickable(boolean);
+    method public void setClickable(boolean);
     method public void setCollectionInfo(android.view.accessibility.AccessibilityNodeInfo.CollectionInfo);
     method public void setCollectionItemInfo(android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo);
     method public void setContentDescription(CharSequence);
     method public void setContentInvalid(boolean);
-    method @Deprecated public void setContextClickable(boolean);
-    method @Deprecated public void setDismissable(boolean);
+    method public void setContextClickable(boolean);
+    method public void setDismissable(boolean);
     method public void setDrawingOrder(int);
     method public void setEditable(boolean);
     method public void setEnabled(boolean);
     method public void setError(CharSequence);
-    method @Deprecated public void setFocusable(boolean);
+    method public void setFocusable(boolean);
     method public void setFocused(boolean);
     method public void setHeading(boolean);
     method public void setHintText(CharSequence);
@@ -52381,7 +52381,7 @@
     method public void setLabeledBy(android.view.View);
     method public void setLabeledBy(android.view.View, int);
     method public void setLiveRegion(int);
-    method @Deprecated public void setLongClickable(boolean);
+    method public void setLongClickable(boolean);
     method public void setMaxTextLength(int);
     method public void setMovementGranularities(int);
     method public void setMultiLine(boolean);
@@ -52392,7 +52392,7 @@
     method public void setPassword(boolean);
     method public void setRangeInfo(android.view.accessibility.AccessibilityNodeInfo.RangeInfo);
     method public void setScreenReaderFocusable(boolean);
-    method @Deprecated public void setScrollable(boolean);
+    method public void setScrollable(boolean);
     method public void setSelected(boolean);
     method public void setShowingHintText(boolean);
     method public void setSource(android.view.View);
diff --git a/api/system-current.txt b/api/system-current.txt
index eb9f82a..3ee6a49 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4461,16 +4461,11 @@
 package android.net.util {
 
   public final class SocketUtils {
-    method public static void addArpEntry(@NonNull java.net.Inet4Address, @NonNull android.net.MacAddress, @NonNull String, @NonNull java.io.FileDescriptor) throws java.io.IOException;
-    method public static void attachControlPacketFilter(@NonNull java.io.FileDescriptor, int) throws java.net.SocketException;
-    method public static void attachDhcpFilter(@NonNull java.io.FileDescriptor) throws java.net.SocketException;
-    method public static void attachRaFilter(@NonNull java.io.FileDescriptor, int) throws java.net.SocketException;
     method public static void bindSocketToInterface(@NonNull java.io.FileDescriptor, @NonNull String) throws android.system.ErrnoException;
     method public static void closeSocket(@Nullable java.io.FileDescriptor) throws java.io.IOException;
     method @NonNull public static java.net.SocketAddress makeNetlinkSocketAddress(int, int);
     method @NonNull public static java.net.SocketAddress makePacketSocketAddress(int, int);
     method @NonNull public static java.net.SocketAddress makePacketSocketAddress(int, @NonNull byte[]);
-    method public static void setSocketTimeValueOption(@NonNull java.io.FileDescriptor, int, int, long) throws android.system.ErrnoException;
   }
 
 }
@@ -6108,7 +6103,6 @@
     field public static final String LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS = "lock_screen_allow_private_notifications";
     field public static final String LOCK_SCREEN_SHOW_NOTIFICATIONS = "lock_screen_show_notifications";
     field public static final String ODI_CAPTIONS_ENABLED = "odi_captions_enabled";
-    field public static final String ODI_CAPTIONS_OPTED_OUT = "odi_captions_opted_out";
     field public static final String THEME_CUSTOMIZATION_OVERLAY_PACKAGES = "theme_customization_overlay_packages";
     field public static final String USER_SETUP_COMPLETE = "user_setup_complete";
     field public static final int USER_SETUP_PERSONALIZATION_COMPLETE = 10; // 0xa
diff --git a/api/test-current.txt b/api/test-current.txt
index 973e700..b35b90f 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1552,16 +1552,11 @@
 package android.net.util {
 
   public final class SocketUtils {
-    method public static void addArpEntry(@NonNull java.net.Inet4Address, @NonNull android.net.MacAddress, @NonNull String, @NonNull java.io.FileDescriptor) throws java.io.IOException;
-    method public static void attachControlPacketFilter(@NonNull java.io.FileDescriptor, int) throws java.net.SocketException;
-    method public static void attachDhcpFilter(@NonNull java.io.FileDescriptor) throws java.net.SocketException;
-    method public static void attachRaFilter(@NonNull java.io.FileDescriptor, int) throws java.net.SocketException;
     method public static void bindSocketToInterface(@NonNull java.io.FileDescriptor, @NonNull String) throws android.system.ErrnoException;
     method public static void closeSocket(@Nullable java.io.FileDescriptor) throws java.io.IOException;
     method @NonNull public static java.net.SocketAddress makeNetlinkSocketAddress(int, int);
     method @NonNull public static java.net.SocketAddress makePacketSocketAddress(int, int);
     method @NonNull public static java.net.SocketAddress makePacketSocketAddress(int, @NonNull byte[]);
-    method public static void setSocketTimeValueOption(@NonNull java.io.FileDescriptor, int, int, long) throws android.system.ErrnoException;
   }
 
 }
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index 5188866..db87c97 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -29,7 +29,6 @@
 import android.util.Pair;
 
 import java.io.FileDescriptor;
-import java.io.IOException;
 import java.math.BigInteger;
 import java.net.Inet4Address;
 import java.net.Inet6Address;
@@ -50,32 +49,6 @@
     private static final String TAG = "NetworkUtils";
 
     /**
-     * Attaches a socket filter that accepts DHCP packets to the given socket.
-     */
-    @UnsupportedAppUsage
-    public native static void attachDhcpFilter(FileDescriptor fd) throws SocketException;
-
-    /**
-     * Attaches a socket filter that accepts ICMPv6 router advertisements to the given socket.
-     * @param fd the socket's {@link FileDescriptor}.
-     * @param packetType the hardware address type, one of ARPHRD_*.
-     */
-    @UnsupportedAppUsage
-    public native static void attachRaFilter(FileDescriptor fd, int packetType) throws SocketException;
-
-    /**
-     * Attaches a socket filter that accepts L2-L4 signaling traffic required for IP connectivity.
-     *
-     * This includes: all ARP, ICMPv6 RS/RA/NS/NA messages, and DHCPv4 exchanges.
-     *
-     * @param fd the socket's {@link FileDescriptor}.
-     * @param packetType the hardware address type, one of ARPHRD_*.
-     */
-    @UnsupportedAppUsage
-    public native static void attachControlPacketFilter(FileDescriptor fd, int packetType)
-            throws SocketException;
-
-    /**
      * Attaches a socket filter that drops all of incoming packets.
      * @param fd the socket's {@link FileDescriptor}.
      */
@@ -183,18 +156,6 @@
     public static native void resNetworkCancel(FileDescriptor fd);
 
     /**
-     * Add an entry into the ARP cache.
-     */
-    public static void addArpEntry(Inet4Address ipv4Addr, MacAddress ethAddr, String ifname,
-            FileDescriptor fd) throws IOException {
-        addArpEntry(ethAddr.toByteArray(), ipv4Addr.getAddress(), ifname, fd);
-    }
-
-    private static native void addArpEntry(byte[] ethAddr, byte[] netAddr, String ifname,
-            FileDescriptor fd) throws IOException;
-
-
-    /**
      * Get the tcp repair window associated with the {@code fd}.
      *
      * @param fd the tcp socket's {@link FileDescriptor}.
diff --git a/core/java/android/net/util/SocketUtils.java b/core/java/android/net/util/SocketUtils.java
index 6f8aece..1364d8c 100644
--- a/core/java/android/net/util/SocketUtils.java
+++ b/core/java/android/net/util/SocketUtils.java
@@ -23,21 +23,17 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
-import android.net.MacAddress;
 import android.net.NetworkUtils;
 import android.system.ErrnoException;
 import android.system.NetlinkSocketAddress;
 import android.system.Os;
 import android.system.PacketSocketAddress;
-import android.system.StructTimeval;
 
 import libcore.io.IoBridge;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
-import java.net.Inet4Address;
 import java.net.SocketAddress;
-import java.net.SocketException;
 
 /**
  * Collection of utilities to interact with raw sockets.
@@ -85,57 +81,11 @@
     }
 
     /**
-     * Set an option on a socket that takes a time value argument.
-     */
-    public static void setSocketTimeValueOption(
-            @NonNull FileDescriptor fd, int level, int option, long millis) throws ErrnoException {
-        Os.setsockoptTimeval(fd, level, option, StructTimeval.fromMillis(millis));
-    }
-
-    /**
      * @see IoBridge#closeAndSignalBlockedThreads(FileDescriptor)
      */
     public static void closeSocket(@Nullable FileDescriptor fd) throws IOException {
         IoBridge.closeAndSignalBlockedThreads(fd);
     }
 
-    /**
-     * Attaches a socket filter that accepts DHCP packets to the given socket.
-     */
-    public static void attachDhcpFilter(@NonNull FileDescriptor fd) throws SocketException {
-        NetworkUtils.attachDhcpFilter(fd);
-    }
-
-    /**
-     * Attaches a socket filter that accepts ICMPv6 router advertisements to the given socket.
-     * @param fd the socket's {@link FileDescriptor}.
-     * @param packetType the hardware address type, one of ARPHRD_*.
-     */
-    public static void attachRaFilter(@NonNull FileDescriptor fd, int packetType)
-            throws SocketException {
-        NetworkUtils.attachRaFilter(fd, packetType);
-    }
-
-    /**
-     * Attaches a socket filter that accepts L2-L4 signaling traffic required for IP connectivity.
-     *
-     * This includes: all ARP, ICMPv6 RS/RA/NS/NA messages, and DHCPv4 exchanges.
-     *
-     * @param fd the socket's {@link FileDescriptor}.
-     * @param packetType the hardware address type, one of ARPHRD_*.
-     */
-    public static void attachControlPacketFilter(@NonNull FileDescriptor fd, int packetType)
-            throws SocketException {
-        NetworkUtils.attachControlPacketFilter(fd, packetType);
-    }
-
-    /**
-     * Add an entry into the ARP cache.
-     */
-    public static void addArpEntry(@NonNull Inet4Address ipv4Addr, @NonNull MacAddress ethAddr,
-            @NonNull String ifname, @NonNull FileDescriptor fd) throws IOException {
-        NetworkUtils.addArpEntry(ipv4Addr, ethAddr, ifname, fd);
-    }
-
     private SocketUtils() {}
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 2e1ef38..3db6b2b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5662,17 +5662,6 @@
         private static final Validator ODI_CAPTIONS_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
 
         /**
-         * Setting to indicate that on device captions cannot be shown because the app
-         * which is currently playing media had opted out.
-         *
-         * @hide
-         */
-        @SystemApi
-        public static final String ODI_CAPTIONS_OPTED_OUT = "odi_captions_opted_out";
-
-        private static final Validator ODI_CAPTIONS_OPTED_OUT_VALIDATOR = BOOLEAN_VALIDATOR;
-
-        /**
          * On Android 8.0 (API level 26) and higher versions of the platform,
          * a 64-bit number (expressed as a hexadecimal string), unique to
          * each combination of app-signing key, user, and device.
@@ -8998,7 +8987,6 @@
             VALIDATORS.put(SILENCE_CALL_GESTURE_COUNT, SILENCE_GESTURE_COUNT_VALIDATOR);
             VALIDATORS.put(SILENCE_NOTIFICATION_GESTURE_COUNT, SILENCE_GESTURE_COUNT_VALIDATOR);
             VALIDATORS.put(ODI_CAPTIONS_ENABLED, ODI_CAPTIONS_ENABLED_VALIDATOR);
-            VALIDATORS.put(ODI_CAPTIONS_OPTED_OUT, ODI_CAPTIONS_OPTED_OUT_VALIDATOR);
         }
 
         /**
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 65fe87f..dfa51ff 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -9891,10 +9891,14 @@
         info.setContentDescription(getContentDescription());
 
         info.setEnabled(isEnabled());
+        info.setClickable(isClickable());
+        info.setFocusable(isFocusable());
         info.setScreenReaderFocusable(isScreenReaderFocusable());
         info.setFocused(isFocused());
         info.setAccessibilityFocused(isAccessibilityFocused());
         info.setSelected(isSelected());
+        info.setLongClickable(isLongClickable());
+        info.setContextClickable(isContextClickable());
         info.setLiveRegion(getAccessibilityLiveRegion());
         if ((mTooltipInfo != null) && (mTooltipInfo.mTooltipText != null)) {
             info.setTooltipText(mTooltipInfo.mTooltipText);
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 774a359..3b310fc 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -611,14 +611,22 @@
 
     private static final int BOOLEAN_PROPERTY_CHECKED = 0x00000002;
 
+    private static final int BOOLEAN_PROPERTY_FOCUSABLE = 0x00000004;
+
     private static final int BOOLEAN_PROPERTY_FOCUSED = 0x00000008;
 
     private static final int BOOLEAN_PROPERTY_SELECTED = 0x00000010;
 
+    private static final int BOOLEAN_PROPERTY_CLICKABLE = 0x00000020;
+
+    private static final int BOOLEAN_PROPERTY_LONG_CLICKABLE = 0x00000040;
+
     private static final int BOOLEAN_PROPERTY_ENABLED = 0x00000080;
 
     private static final int BOOLEAN_PROPERTY_PASSWORD = 0x00000100;
 
+    private static final int BOOLEAN_PROPERTY_SCROLLABLE = 0x00000200;
+
     private static final int BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED = 0x00000400;
 
     private static final int BOOLEAN_PROPERTY_VISIBLE_TO_USER = 0x00000800;
@@ -633,6 +641,8 @@
 
     private static final int BOOLEAN_PROPERTY_CONTENT_INVALID = 0x00010000;
 
+    private static final int BOOLEAN_PROPERTY_CONTEXT_CLICKABLE = 0x00020000;
+
     private static final int BOOLEAN_PROPERTY_IMPORTANCE = 0x0040000;
 
     private static final int BOOLEAN_PROPERTY_SCREEN_READER_FOCUSABLE = 0x0080000;
@@ -1191,16 +1201,6 @@
         mActions.add(action);
     }
 
-    private boolean hasActionWithId(int actionId) {
-        List<AccessibilityAction> actions = getActionList();
-        for (int i = 0; i < actions.size(); i++) {
-            if (actions.get(i).getId() == actionId) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     /**
      * Adds an action that can be performed on the node.
      * <p>
@@ -1814,7 +1814,7 @@
      * @return True if the node is focusable.
      */
     public boolean isFocusable() {
-        return hasActionWithId(ACTION_FOCUS) || hasActionWithId(ACTION_CLEAR_FOCUS);
+        return getBooleanProperty(BOOLEAN_PROPERTY_FOCUSABLE);
     }
 
     /**
@@ -1828,11 +1828,10 @@
      * @param focusable True if the node is focusable.
      *
      * @throws IllegalStateException If called from an AccessibilityService.
-     * @deprecated Use {@link #addAction(AccessibilityAction)}
-     * with {@link AccessibilityAction#ACTION_FOCUS}
      */
-    @Deprecated
-    public void setFocusable(boolean focusable) { }
+    public void setFocusable(boolean focusable) {
+        setBooleanProperty(BOOLEAN_PROPERTY_FOCUSABLE, focusable);
+    }
 
     /**
      * Gets whether this node is focused.
@@ -1940,7 +1939,7 @@
      * @return True if the node is clickable.
      */
     public boolean isClickable() {
-        return hasActionWithId(ACTION_CLICK);
+        return getBooleanProperty(BOOLEAN_PROPERTY_CLICKABLE);
     }
 
     /**
@@ -1954,11 +1953,10 @@
      * @param clickable True if the node is clickable.
      *
      * @throws IllegalStateException If called from an AccessibilityService.
-     * @deprecated Use {@link #addAction(AccessibilityAction)}
-     * with {@link AccessibilityAction#ACTION_CLICK}
      */
-    @Deprecated
-    public void setClickable(boolean clickable) { }
+    public void setClickable(boolean clickable) {
+        setBooleanProperty(BOOLEAN_PROPERTY_CLICKABLE, clickable);
+    }
 
     /**
      * Gets whether this node is long clickable.
@@ -1966,7 +1964,7 @@
      * @return True if the node is long clickable.
      */
     public boolean isLongClickable() {
-        return hasActionWithId(ACTION_LONG_CLICK);
+        return getBooleanProperty(BOOLEAN_PROPERTY_LONG_CLICKABLE);
     }
 
     /**
@@ -1980,11 +1978,10 @@
      * @param longClickable True if the node is long clickable.
      *
      * @throws IllegalStateException If called from an AccessibilityService.
-     * @deprecated Use {@link #addAction(AccessibilityAction)}
-     * with {@link AccessibilityAction#ACTION_LONG_CLICK}
      */
-    @Deprecated
-    public void setLongClickable(boolean longClickable) { }
+    public void setLongClickable(boolean longClickable) {
+        setBooleanProperty(BOOLEAN_PROPERTY_LONG_CLICKABLE, longClickable);
+    }
 
     /**
      * Gets whether this node is enabled.
@@ -2042,13 +2039,7 @@
      * @return True if the node is scrollable, false otherwise.
      */
     public boolean isScrollable() {
-        return hasActionWithId(ACTION_SCROLL_BACKWARD)
-                || hasActionWithId(ACTION_SCROLL_FORWARD)
-                || hasActionWithId(R.id.accessibilityActionScrollToPosition)
-                || hasActionWithId(R.id.accessibilityActionScrollUp)
-                || hasActionWithId(R.id.accessibilityActionScrollDown)
-                || hasActionWithId(R.id.accessibilityActionScrollLeft)
-                || hasActionWithId(R.id.accessibilityActionScrollRight);
+        return getBooleanProperty(BOOLEAN_PROPERTY_SCROLLABLE);
     }
 
     /**
@@ -2062,11 +2053,9 @@
      * @param scrollable True if the node is scrollable, false otherwise.
      *
      * @throws IllegalStateException If called from an AccessibilityService.
-     * @deprecated Use {@link #addAction(AccessibilityAction)}
      */
-    @Deprecated
-
     public void setScrollable(boolean scrollable) {
+        setBooleanProperty(BOOLEAN_PROPERTY_SCROLLABLE, scrollable);
     }
 
     /**
@@ -2257,7 +2246,7 @@
      * @return True if the node is context clickable.
      */
     public boolean isContextClickable() {
-        return hasActionWithId(R.id.accessibilityActionContextClick);
+        return getBooleanProperty(BOOLEAN_PROPERTY_CONTEXT_CLICKABLE);
     }
 
     /**
@@ -2270,11 +2259,10 @@
      *
      * @param contextClickable True if the node is context clickable.
      * @throws IllegalStateException If called from an AccessibilityService.
-     * @deprecated Use {@link #addAction(AccessibilityAction)}
-     * with {@link AccessibilityAction#ACTION_CONTEXT_CLICK}
      */
-    @Deprecated
-    public void setContextClickable(boolean contextClickable) { }
+    public void setContextClickable(boolean contextClickable) {
+        setBooleanProperty(BOOLEAN_PROPERTY_CONTEXT_CLICKABLE, contextClickable);
+    }
 
     /**
      * Gets the node's live region mode.
@@ -2368,7 +2356,7 @@
      * @return If the node can be dismissed.
      */
     public boolean isDismissable() {
-        return hasActionWithId(ACTION_DISMISS);
+        return getBooleanProperty(BOOLEAN_PROPERTY_DISMISSABLE);
     }
 
     /**
@@ -2380,11 +2368,10 @@
      * </p>
      *
      * @param dismissable If the node can be dismissed.
-     * @deprecated Use {@link #addAction(AccessibilityAction)}
-     * with {@link AccessibilityAction#ACTION_DISMISS}
      */
-    @Deprecated
-    public void setDismissable(boolean dismissable) { }
+    public void setDismissable(boolean dismissable) {
+        setBooleanProperty(BOOLEAN_PROPERTY_DISMISSABLE, dismissable);
+    }
 
     /**
      * Returns whether the node originates from a view considered important for accessibility.
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index 82acf6f..dd754f3 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -48,17 +48,6 @@
 
 namespace android {
 
-static const uint32_t kEtherTypeOffset = offsetof(ether_header, ether_type);
-static const uint32_t kEtherHeaderLen = sizeof(ether_header);
-static const uint32_t kIPv4Protocol = kEtherHeaderLen + offsetof(iphdr, protocol);
-static const uint32_t kIPv4FlagsOffset = kEtherHeaderLen + offsetof(iphdr, frag_off);
-static const uint32_t kIPv6NextHeader = kEtherHeaderLen + offsetof(ip6_hdr, ip6_nxt);
-static const uint32_t kIPv6PayloadStart = kEtherHeaderLen + sizeof(ip6_hdr);
-static const uint32_t kICMPv6TypeOffset = kIPv6PayloadStart + offsetof(icmp6_hdr, icmp6_type);
-static const uint32_t kUDPSrcPortIndirectOffset = kEtherHeaderLen + offsetof(udphdr, source);
-static const uint32_t kUDPDstPortIndirectOffset = kEtherHeaderLen + offsetof(udphdr, dest);
-static const uint16_t kDhcpClientPort = 68;
-
 constexpr int MAXPACKETSIZE = 8 * 1024;
 // FrameworkListener limits the size of commands to 1024 bytes. TODO: fix this.
 constexpr int MAXCMDSIZE = 1024;
@@ -84,149 +73,6 @@
     env->Throw(reinterpret_cast<jthrowable>(exception));
 }
 
-static void android_net_utils_attachDhcpFilter(JNIEnv *env, jobject clazz, jobject javaFd)
-{
-    struct sock_filter filter_code[] = {
-        // Check the protocol is UDP.
-        BPF_STMT(BPF_LD  | BPF_B   | BPF_ABS,  kIPv4Protocol),
-        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    IPPROTO_UDP, 0, 6),
-
-        // Check this is not a fragment.
-        BPF_STMT(BPF_LD  | BPF_H    | BPF_ABS, kIPv4FlagsOffset),
-        BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K,   IP_OFFMASK, 4, 0),
-
-        // Get the IP header length.
-        BPF_STMT(BPF_LDX | BPF_B    | BPF_MSH, kEtherHeaderLen),
-
-        // Check the destination port.
-        BPF_STMT(BPF_LD  | BPF_H    | BPF_IND, kUDPDstPortIndirectOffset),
-        BPF_JUMP(BPF_JMP | BPF_JEQ  | BPF_K,   kDhcpClientPort, 0, 1),
-
-        // Accept or reject.
-        BPF_STMT(BPF_RET | BPF_K,              0xffff),
-        BPF_STMT(BPF_RET | BPF_K,              0)
-    };
-    struct sock_fprog filter = {
-        sizeof(filter_code) / sizeof(filter_code[0]),
-        filter_code,
-    };
-
-    int fd = jniGetFDFromFileDescriptor(env, javaFd);
-    if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
-        jniThrowExceptionFmt(env, "java/net/SocketException",
-                "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno));
-    }
-}
-
-static void android_net_utils_attachRaFilter(JNIEnv *env, jobject clazz, jobject javaFd,
-        jint hardwareAddressType)
-{
-    if (hardwareAddressType != ARPHRD_ETHER) {
-        jniThrowExceptionFmt(env, "java/net/SocketException",
-                "attachRaFilter only supports ARPHRD_ETHER");
-        return;
-    }
-
-    struct sock_filter filter_code[] = {
-        // Check IPv6 Next Header is ICMPv6.
-        BPF_STMT(BPF_LD  | BPF_B   | BPF_ABS,  kIPv6NextHeader),
-        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    IPPROTO_ICMPV6, 0, 3),
-
-        // Check ICMPv6 type is Router Advertisement.
-        BPF_STMT(BPF_LD  | BPF_B   | BPF_ABS,  kICMPv6TypeOffset),
-        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    ND_ROUTER_ADVERT, 0, 1),
-
-        // Accept or reject.
-        BPF_STMT(BPF_RET | BPF_K,              0xffff),
-        BPF_STMT(BPF_RET | BPF_K,              0)
-    };
-    struct sock_fprog filter = {
-        sizeof(filter_code) / sizeof(filter_code[0]),
-        filter_code,
-    };
-
-    int fd = jniGetFDFromFileDescriptor(env, javaFd);
-    if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
-        jniThrowExceptionFmt(env, "java/net/SocketException",
-                "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno));
-    }
-}
-
-// TODO: Move all this filter code into libnetutils.
-static void android_net_utils_attachControlPacketFilter(
-        JNIEnv *env, jobject clazz, jobject javaFd, jint hardwareAddressType) {
-    if (hardwareAddressType != ARPHRD_ETHER) {
-        jniThrowExceptionFmt(env, "java/net/SocketException",
-                "attachControlPacketFilter only supports ARPHRD_ETHER");
-        return;
-    }
-
-    // Capture all:
-    //     - ARPs
-    //     - DHCPv4 packets
-    //     - Router Advertisements & Solicitations
-    //     - Neighbor Advertisements & Solicitations
-    //
-    // tcpdump:
-    //     arp or
-    //     '(ip and udp port 68)' or
-    //     '(icmp6 and ip6[40] >= 133 and ip6[40] <= 136)'
-    struct sock_filter filter_code[] = {
-        // Load the link layer next payload field.
-        BPF_STMT(BPF_LD  | BPF_H   | BPF_ABS,  kEtherTypeOffset),
-
-        // Accept all ARP.
-        // TODO: Figure out how to better filter ARPs on noisy networks.
-        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ETHERTYPE_ARP, 16, 0),
-
-        // If IPv4:
-        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ETHERTYPE_IP, 0, 9),
-
-        // Check the protocol is UDP.
-        BPF_STMT(BPF_LD  | BPF_B   | BPF_ABS,  kIPv4Protocol),
-        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    IPPROTO_UDP, 0, 14),
-
-        // Check this is not a fragment.
-        BPF_STMT(BPF_LD  | BPF_H    | BPF_ABS, kIPv4FlagsOffset),
-        BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K,   IP_OFFMASK, 12, 0),
-
-        // Get the IP header length.
-        BPF_STMT(BPF_LDX | BPF_B    | BPF_MSH, kEtherHeaderLen),
-
-        // Check the source port.
-        BPF_STMT(BPF_LD  | BPF_H    | BPF_IND, kUDPSrcPortIndirectOffset),
-        BPF_JUMP(BPF_JMP | BPF_JEQ  | BPF_K,   kDhcpClientPort, 8, 0),
-
-        // Check the destination port.
-        BPF_STMT(BPF_LD  | BPF_H    | BPF_IND, kUDPDstPortIndirectOffset),
-        BPF_JUMP(BPF_JMP | BPF_JEQ  | BPF_K,   kDhcpClientPort, 6, 7),
-
-        // IPv6 ...
-        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ETHERTYPE_IPV6, 0, 6),
-        // ... check IPv6 Next Header is ICMPv6 (ignore fragments), ...
-        BPF_STMT(BPF_LD  | BPF_B   | BPF_ABS,  kIPv6NextHeader),
-        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    IPPROTO_ICMPV6, 0, 4),
-        // ... and check the ICMPv6 type is one of RS/RA/NS/NA.
-        BPF_STMT(BPF_LD  | BPF_B   | BPF_ABS,  kICMPv6TypeOffset),
-        BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K,    ND_ROUTER_SOLICIT, 0, 2),
-        BPF_JUMP(BPF_JMP | BPF_JGT | BPF_K,    ND_NEIGHBOR_ADVERT, 1, 0),
-
-        // Accept or reject.
-        BPF_STMT(BPF_RET | BPF_K,              0xffff),
-        BPF_STMT(BPF_RET | BPF_K,              0)
-    };
-    struct sock_fprog filter = {
-        sizeof(filter_code) / sizeof(filter_code[0]),
-        filter_code,
-    };
-
-    int fd = jniGetFDFromFileDescriptor(env, javaFd);
-    if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
-        jniThrowExceptionFmt(env, "java/net/SocketException",
-                "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno));
-    }
-}
-
 static void android_net_utils_attachDropAllBPFFilter(JNIEnv *env, jobject clazz, jobject javaFd)
 {
     struct sock_filter filter_code[] = {
@@ -389,46 +235,6 @@
     return true;
 }
 
-static void android_net_utils_addArpEntry(JNIEnv *env, jobject thiz, jbyteArray ethAddr,
-        jbyteArray ipv4Addr, jstring ifname, jobject javaFd)
-{
-    struct arpreq req = {};
-    struct sockaddr_in& netAddrStruct = *reinterpret_cast<sockaddr_in*>(&req.arp_pa);
-    struct sockaddr& ethAddrStruct = req.arp_ha;
-
-    ethAddrStruct.sa_family = ARPHRD_ETHER;
-    if (!checkLenAndCopy(env, ethAddr, ETH_ALEN, ethAddrStruct.sa_data)) {
-        jniThrowException(env, "java/io/IOException", "Invalid ethAddr length");
-        return;
-    }
-
-    netAddrStruct.sin_family = AF_INET;
-    if (!checkLenAndCopy(env, ipv4Addr, sizeof(in_addr), &netAddrStruct.sin_addr)) {
-        jniThrowException(env, "java/io/IOException", "Invalid ipv4Addr length");
-        return;
-    }
-
-    int ifLen = env->GetStringLength(ifname);
-    // IFNAMSIZ includes the terminating NULL character
-    if (ifLen >= IFNAMSIZ) {
-        jniThrowException(env, "java/io/IOException", "ifname too long");
-        return;
-    }
-    env->GetStringUTFRegion(ifname, 0, ifLen, req.arp_dev);
-
-    req.arp_flags = ATF_COM;  // Completed entry (ha valid)
-    int fd = jniGetFDFromFileDescriptor(env, javaFd);
-    if (fd < 0) {
-        jniThrowExceptionFmt(env, "java/io/IOException", "Invalid file descriptor");
-        return;
-    }
-    // See also: man 7 arp
-    if (ioctl(fd, SIOCSARP, &req)) {
-        jniThrowExceptionFmt(env, "java/io/IOException", "ioctl error: %s", strerror(errno));
-        return;
-    }
-}
-
 static jobject android_net_utils_resNetworkQuery(JNIEnv *env, jobject thiz, jint netId,
         jstring dname, jint ns_class, jint ns_type, jint flags) {
     const jsize javaCharsCount = env->GetStringLength(dname);
@@ -542,10 +348,6 @@
     { "bindSocketToNetwork", "(II)I", (void*) android_net_utils_bindSocketToNetwork },
     { "protectFromVpn", "(I)Z", (void*)android_net_utils_protectFromVpn },
     { "queryUserAccess", "(II)Z", (void*)android_net_utils_queryUserAccess },
-    { "addArpEntry", "([B[BLjava/lang/String;Ljava/io/FileDescriptor;)V", (void*) android_net_utils_addArpEntry },
-    { "attachDhcpFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDhcpFilter },
-    { "attachRaFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachRaFilter },
-    { "attachControlPacketFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachControlPacketFilter },
     { "attachDropAllBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDropAllBPFFilter },
     { "detachBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_detachBPFFilter },
     { "getTcpRepairWindow", "(Ljava/io/FileDescriptor;)Landroid/net/TcpRepairWindow;", (void*) android_net_utils_getTcpRepairWindow },
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 21f5acb..e8cc96c 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3558,6 +3558,12 @@
     -->
     <string name="config_defaultSystemCaptionsService" translatable="false"></string>
 
+    <!-- The component name for the system-wide captions manager service.
+         This service must be trusted, as the system binds to it and keeps it running.
+         Example: "com.android.captions/.SystemCaptionsManagerService"
+    -->
+    <string name="config_defaultSystemCaptionsManagerService" translatable="false"></string>
+
     <!-- The package name for the incident report approver app.
         This app is usually PermissionController or an app that replaces it.  When
         a bugreport or incident report with EXPLICT-level sharing flags is going to be
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a6841d4..664059a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3409,6 +3409,7 @@
   <java-symbol type="string" name="config_defaultContentSuggestionsService" />
   <java-symbol type="string" name="config_defaultAttentionService" />
   <java-symbol type="string" name="config_defaultSystemCaptionsService" />
+  <java-symbol type="string" name="config_defaultSystemCaptionsManagerService" />
 
   <java-symbol type="string" name="notification_channel_foreground_service" />
   <java-symbol type="string" name="foreground_service_app_in_background" />
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 33c23c4..8cc6e37 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -649,7 +649,6 @@
                  Settings.Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
                  Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED,
                  Settings.Secure.ODI_CAPTIONS_ENABLED,
-                 Settings.Secure.ODI_CAPTIONS_OPTED_OUT,
                  Settings.Secure.PACKAGE_VERIFIER_STATE,
                  Settings.Secure.PACKAGE_VERIFIER_USER_CONSENT,
                  Settings.Secure.PARENTAL_CONTROL_LAST_UPDATE,
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index 0ed690c..4c59207 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -58,7 +58,7 @@
 
     // The number of flags held in boolean properties. Their values should also be double-checked
     // in the methods above.
-    private static final int NUM_BOOLEAN_PROPERTIES = 18;
+    private static final int NUM_BOOLEAN_PROPERTIES = 23;
 
     @Test
     public void testStandardActions_serializationFlagIsValid() {
diff --git a/packages/NetworkStack/Android.bp b/packages/NetworkStack/Android.bp
index 57a3db5..262e6f6 100644
--- a/packages/NetworkStack/Android.bp
+++ b/packages/NetworkStack/Android.bp
@@ -49,6 +49,27 @@
     manifest: "AndroidManifestBase.xml",
 }
 
+cc_library_shared {
+    name: "libnetworkstackutilsjni",
+    srcs: [
+        "jni/network_stack_utils_jni.cpp"
+    ],
+
+    shared_libs: [
+        "liblog",
+        "libcutils",
+        "libnativehelper",
+    ],
+    static_libs: [
+        "libpcap",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-unused-parameter",
+    ],
+}
+
 java_defaults {
     name: "NetworkStackAppCommon",
     defaults: ["NetworkStackCommon"],
@@ -56,6 +77,7 @@
     static_libs: [
         "NetworkStackBase",
     ],
+    jni_libs: ["libnetworkstackutilsjni"],
     // Resources already included in NetworkStackBase
     resource_dirs: [],
     jarjar_rules: "jarjar-rules-shared.txt",
diff --git a/packages/NetworkStack/jni/network_stack_utils_jni.cpp b/packages/NetworkStack/jni/network_stack_utils_jni.cpp
new file mode 100644
index 0000000..5544eaa
--- /dev/null
+++ b/packages/NetworkStack/jni/network_stack_utils_jni.cpp
@@ -0,0 +1,264 @@
+/*
+ * Copyright 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "NetworkStackUtils-JNI"
+
+#include <errno.h>
+#include <jni.h>
+#include <linux/filter.h>
+#include <linux/if_arp.h>
+#include <net/if.h>
+#include <netinet/ether.h>
+#include <netinet/icmp6.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <netinet/udp.h>
+#include <stdlib.h>
+
+#include <string>
+
+#include <nativehelper/JNIHelp.h>
+#include <utils/Log.h>
+
+namespace android {
+constexpr const char NETWORKSTACKUTILS_PKG_NAME[] = "android/net/util/NetworkStackUtils";
+
+static const uint32_t kEtherTypeOffset = offsetof(ether_header, ether_type);
+static const uint32_t kEtherHeaderLen = sizeof(ether_header);
+static const uint32_t kIPv4Protocol = kEtherHeaderLen + offsetof(iphdr, protocol);
+static const uint32_t kIPv4FlagsOffset = kEtherHeaderLen + offsetof(iphdr, frag_off);
+static const uint32_t kIPv6NextHeader = kEtherHeaderLen + offsetof(ip6_hdr, ip6_nxt);
+static const uint32_t kIPv6PayloadStart = kEtherHeaderLen + sizeof(ip6_hdr);
+static const uint32_t kICMPv6TypeOffset = kIPv6PayloadStart + offsetof(icmp6_hdr, icmp6_type);
+static const uint32_t kUDPSrcPortIndirectOffset = kEtherHeaderLen + offsetof(udphdr, source);
+static const uint32_t kUDPDstPortIndirectOffset = kEtherHeaderLen + offsetof(udphdr, dest);
+static const uint16_t kDhcpClientPort = 68;
+
+static bool checkLenAndCopy(JNIEnv* env, const jbyteArray& addr, int len, void* dst) {
+    if (env->GetArrayLength(addr) != len) {
+        return false;
+    }
+    env->GetByteArrayRegion(addr, 0, len, reinterpret_cast<jbyte*>(dst));
+    return true;
+}
+
+static void network_stack_utils_addArpEntry(JNIEnv *env, jobject thiz, jbyteArray ethAddr,
+        jbyteArray ipv4Addr, jstring ifname, jobject javaFd) {
+    arpreq req = {};
+    sockaddr_in& netAddrStruct = *reinterpret_cast<sockaddr_in*>(&req.arp_pa);
+    sockaddr& ethAddrStruct = req.arp_ha;
+
+    ethAddrStruct.sa_family = ARPHRD_ETHER;
+    if (!checkLenAndCopy(env, ethAddr, ETH_ALEN, ethAddrStruct.sa_data)) {
+        jniThrowException(env, "java/io/IOException", "Invalid ethAddr length");
+        return;
+    }
+
+    netAddrStruct.sin_family = AF_INET;
+    if (!checkLenAndCopy(env, ipv4Addr, sizeof(in_addr), &netAddrStruct.sin_addr)) {
+        jniThrowException(env, "java/io/IOException", "Invalid ipv4Addr length");
+        return;
+    }
+
+    int ifLen = env->GetStringLength(ifname);
+    // IFNAMSIZ includes the terminating NULL character
+    if (ifLen >= IFNAMSIZ) {
+        jniThrowException(env, "java/io/IOException", "ifname too long");
+        return;
+    }
+    env->GetStringUTFRegion(ifname, 0, ifLen, req.arp_dev);
+
+    req.arp_flags = ATF_COM;  // Completed entry (ha valid)
+    int fd = jniGetFDFromFileDescriptor(env, javaFd);
+    if (fd < 0) {
+        jniThrowExceptionFmt(env, "java/io/IOException", "Invalid file descriptor");
+        return;
+    }
+    // See also: man 7 arp
+    if (ioctl(fd, SIOCSARP, &req)) {
+        jniThrowExceptionFmt(env, "java/io/IOException", "ioctl error: %s", strerror(errno));
+        return;
+    }
+}
+
+static void network_stack_utils_attachDhcpFilter(JNIEnv *env, jobject clazz, jobject javaFd) {
+    static sock_filter filter_code[] = {
+        // Check the protocol is UDP.
+        BPF_STMT(BPF_LD  | BPF_B    | BPF_ABS, kIPv4Protocol),
+        BPF_JUMP(BPF_JMP | BPF_JEQ  | BPF_K,   IPPROTO_UDP, 0, 6),
+
+        // Check this is not a fragment.
+        BPF_STMT(BPF_LD  | BPF_H    | BPF_ABS, kIPv4FlagsOffset),
+        BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K,   IP_OFFMASK, 4, 0),
+
+        // Get the IP header length.
+        BPF_STMT(BPF_LDX | BPF_B    | BPF_MSH, kEtherHeaderLen),
+
+        // Check the destination port.
+        BPF_STMT(BPF_LD  | BPF_H    | BPF_IND, kUDPDstPortIndirectOffset),
+        BPF_JUMP(BPF_JMP | BPF_JEQ  | BPF_K,   kDhcpClientPort, 0, 1),
+
+        // Accept or reject.
+        BPF_STMT(BPF_RET | BPF_K,              0xffff),
+        BPF_STMT(BPF_RET | BPF_K,              0)
+    };
+    static const sock_fprog filter = {
+        sizeof(filter_code) / sizeof(filter_code[0]),
+        filter_code,
+    };
+
+    int fd = jniGetFDFromFileDescriptor(env, javaFd);
+    if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
+        jniThrowExceptionFmt(env, "java/net/SocketException",
+                "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno));
+    }
+}
+
+static void network_stack_utils_attachRaFilter(JNIEnv *env, jobject clazz, jobject javaFd,
+        jint hardwareAddressType) {
+    if (hardwareAddressType != ARPHRD_ETHER) {
+        jniThrowExceptionFmt(env, "java/net/SocketException",
+                "attachRaFilter only supports ARPHRD_ETHER");
+        return;
+    }
+
+    static sock_filter filter_code[] = {
+        // Check IPv6 Next Header is ICMPv6.
+        BPF_STMT(BPF_LD  | BPF_B   | BPF_ABS,  kIPv6NextHeader),
+        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    IPPROTO_ICMPV6, 0, 3),
+
+        // Check ICMPv6 type is Router Advertisement.
+        BPF_STMT(BPF_LD  | BPF_B   | BPF_ABS,  kICMPv6TypeOffset),
+        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    ND_ROUTER_ADVERT, 0, 1),
+
+        // Accept or reject.
+        BPF_STMT(BPF_RET | BPF_K,              0xffff),
+        BPF_STMT(BPF_RET | BPF_K,              0)
+    };
+    static const sock_fprog filter = {
+        sizeof(filter_code) / sizeof(filter_code[0]),
+        filter_code,
+    };
+
+    int fd = jniGetFDFromFileDescriptor(env, javaFd);
+    if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
+        jniThrowExceptionFmt(env, "java/net/SocketException",
+                "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno));
+    }
+}
+
+// TODO: Move all this filter code into libnetutils.
+static void network_stack_utils_attachControlPacketFilter(
+        JNIEnv *env, jobject clazz, jobject javaFd, jint hardwareAddressType) {
+    if (hardwareAddressType != ARPHRD_ETHER) {
+        jniThrowExceptionFmt(env, "java/net/SocketException",
+                "attachControlPacketFilter only supports ARPHRD_ETHER");
+        return;
+    }
+
+    // Capture all:
+    //     - ARPs
+    //     - DHCPv4 packets
+    //     - Router Advertisements & Solicitations
+    //     - Neighbor Advertisements & Solicitations
+    //
+    // tcpdump:
+    //     arp or
+    //     '(ip and udp port 68)' or
+    //     '(icmp6 and ip6[40] >= 133 and ip6[40] <= 136)'
+    static sock_filter filter_code[] = {
+        // Load the link layer next payload field.
+        BPF_STMT(BPF_LD  | BPF_H    | BPF_ABS,  kEtherTypeOffset),
+
+        // Accept all ARP.
+        // TODO: Figure out how to better filter ARPs on noisy networks.
+        BPF_JUMP(BPF_JMP | BPF_JEQ  | BPF_K,   ETHERTYPE_ARP, 16, 0),
+
+        // If IPv4:
+        BPF_JUMP(BPF_JMP | BPF_JEQ  | BPF_K,   ETHERTYPE_IP, 0, 9),
+
+        // Check the protocol is UDP.
+        BPF_STMT(BPF_LD  | BPF_B    | BPF_ABS, kIPv4Protocol),
+        BPF_JUMP(BPF_JMP | BPF_JEQ  | BPF_K,   IPPROTO_UDP, 0, 14),
+
+        // Check this is not a fragment.
+        BPF_STMT(BPF_LD  | BPF_H    | BPF_ABS, kIPv4FlagsOffset),
+        BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K,   IP_OFFMASK, 12, 0),
+
+        // Get the IP header length.
+        BPF_STMT(BPF_LDX | BPF_B    | BPF_MSH, kEtherHeaderLen),
+
+        // Check the source port.
+        BPF_STMT(BPF_LD  | BPF_H    | BPF_IND, kUDPSrcPortIndirectOffset),
+        BPF_JUMP(BPF_JMP | BPF_JEQ  | BPF_K,   kDhcpClientPort, 8, 0),
+
+        // Check the destination port.
+        BPF_STMT(BPF_LD  | BPF_H    | BPF_IND, kUDPDstPortIndirectOffset),
+        BPF_JUMP(BPF_JMP | BPF_JEQ  | BPF_K,   kDhcpClientPort, 6, 7),
+
+        // IPv6 ...
+        BPF_JUMP(BPF_JMP | BPF_JEQ  | BPF_K,   ETHERTYPE_IPV6, 0, 6),
+        // ... check IPv6 Next Header is ICMPv6 (ignore fragments), ...
+        BPF_STMT(BPF_LD  | BPF_B    | BPF_ABS, kIPv6NextHeader),
+        BPF_JUMP(BPF_JMP | BPF_JEQ  | BPF_K,   IPPROTO_ICMPV6, 0, 4),
+        // ... and check the ICMPv6 type is one of RS/RA/NS/NA.
+        BPF_STMT(BPF_LD  | BPF_B    | BPF_ABS, kICMPv6TypeOffset),
+        BPF_JUMP(BPF_JMP | BPF_JGE  | BPF_K,   ND_ROUTER_SOLICIT, 0, 2),
+        BPF_JUMP(BPF_JMP | BPF_JGT  | BPF_K,   ND_NEIGHBOR_ADVERT, 1, 0),
+
+        // Accept or reject.
+        BPF_STMT(BPF_RET | BPF_K,              0xffff),
+        BPF_STMT(BPF_RET | BPF_K,              0)
+    };
+    static const sock_fprog filter = {
+        sizeof(filter_code) / sizeof(filter_code[0]),
+        filter_code,
+    };
+
+    int fd = jniGetFDFromFileDescriptor(env, javaFd);
+    if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
+        jniThrowExceptionFmt(env, "java/net/SocketException",
+                "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno));
+    }
+}
+
+/*
+ * JNI registration.
+ */
+static const JNINativeMethod gNetworkStackUtilsMethods[] = {
+    /* name, signature, funcPtr */
+    { "addArpEntry", "([B[BLjava/lang/String;Ljava/io/FileDescriptor;)V", (void*) network_stack_utils_addArpEntry },
+    { "attachDhcpFilter", "(Ljava/io/FileDescriptor;)V", (void*) network_stack_utils_attachDhcpFilter },
+    { "attachRaFilter", "(Ljava/io/FileDescriptor;I)V", (void*) network_stack_utils_attachRaFilter },
+    { "attachControlPacketFilter", "(Ljava/io/FileDescriptor;I)V", (void*) network_stack_utils_attachControlPacketFilter },
+};
+
+extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
+    JNIEnv *env;
+    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+        ALOGE("ERROR: GetEnv failed");
+        return JNI_ERR;
+    }
+
+    if (jniRegisterNativeMethods(env, NETWORKSTACKUTILS_PKG_NAME,
+            gNetworkStackUtilsMethods, NELEM(gNetworkStackUtilsMethods)) < 0) {
+        return JNI_ERR;
+    }
+
+    return JNI_VERSION_1_6;
+
+}
+}; // namespace android
\ No newline at end of file
diff --git a/packages/NetworkStack/src/android/net/apf/ApfFilter.java b/packages/NetworkStack/src/android/net/apf/ApfFilter.java
index d2f3259..663e2f1 100644
--- a/packages/NetworkStack/src/android/net/apf/ApfFilter.java
+++ b/packages/NetworkStack/src/android/net/apf/ApfFilter.java
@@ -49,7 +49,6 @@
 import android.net.metrics.RaEvent;
 import android.net.util.InterfaceParams;
 import android.net.util.NetworkStackUtils;
-import android.net.util.SocketUtils;
 import android.os.PowerManager;
 import android.os.SystemClock;
 import android.system.ErrnoException;
@@ -478,7 +477,7 @@
             SocketAddress addr = makePacketSocketAddress(
                     (short) ETH_P_IPV6, mInterfaceParams.index);
             Os.bind(socket, addr);
-            SocketUtils.attachRaFilter(socket, mApfCapabilities.apfPacketFormat);
+            NetworkStackUtils.attachRaFilter(socket, mApfCapabilities.apfPacketFormat);
         } catch(SocketException|ErrnoException e) {
             Log.e(TAG, "Error starting filter", e);
             return;
diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java b/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java
index 79d6a55..64adc0d 100644
--- a/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java
+++ b/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java
@@ -51,6 +51,7 @@
 import android.net.metrics.DhcpErrorEvent;
 import android.net.metrics.IpConnectivityLog;
 import android.net.util.InterfaceParams;
+import android.net.util.NetworkStackUtils;
 import android.net.util.SocketUtils;
 import android.os.Message;
 import android.os.SystemClock;
@@ -319,7 +320,7 @@
             mPacketSock = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IP);
             SocketAddress addr = makePacketSocketAddress((short) ETH_P_IP, mIface.index);
             Os.bind(mPacketSock, addr);
-            SocketUtils.attachDhcpFilter(mPacketSock);
+            NetworkStackUtils.attachDhcpFilter(mPacketSock);
         } catch(SocketException|ErrnoException e) {
             Log.e(TAG, "Error creating packet socket", e);
             return false;
diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java b/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java
index cd993e9..8832eaa 100644
--- a/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java
+++ b/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java
@@ -45,6 +45,7 @@
 import android.net.INetworkStackStatusCallback;
 import android.net.MacAddress;
 import android.net.TrafficStats;
+import android.net.util.NetworkStackUtils;
 import android.net.util.SharedLog;
 import android.net.util.SocketUtils;
 import android.os.Handler;
@@ -207,7 +208,7 @@
         @Override
         public void addArpEntry(@NonNull Inet4Address ipv4Addr, @NonNull MacAddress ethAddr,
                 @NonNull String ifname, @NonNull FileDescriptor fd) throws IOException {
-            SocketUtils.addArpEntry(ipv4Addr, ethAddr, ifname, fd);
+            NetworkStackUtils.addArpEntry(ipv4Addr, ethAddr, ifname, fd);
         }
 
         @Override
diff --git a/packages/NetworkStack/src/android/net/ip/ConnectivityPacketTracker.java b/packages/NetworkStack/src/android/net/ip/ConnectivityPacketTracker.java
index de54824..eb49218 100644
--- a/packages/NetworkStack/src/android/net/ip/ConnectivityPacketTracker.java
+++ b/packages/NetworkStack/src/android/net/ip/ConnectivityPacketTracker.java
@@ -25,8 +25,8 @@
 
 import android.net.util.ConnectivityPacketSummary;
 import android.net.util.InterfaceParams;
+import android.net.util.NetworkStackUtils;
 import android.net.util.PacketReader;
-import android.net.util.SocketUtils;
 import android.os.Handler;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -103,7 +103,7 @@
             FileDescriptor s = null;
             try {
                 s = Os.socket(AF_PACKET, SOCK_RAW | SOCK_NONBLOCK, 0);
-                SocketUtils.attachControlPacketFilter(s, ARPHRD_ETHER);
+                NetworkStackUtils.attachControlPacketFilter(s, ARPHRD_ETHER);
                 Os.bind(s, makePacketSocketAddress((short) ETH_P_ALL, mInterface.index));
             } catch (ErrnoException | IOException e) {
                 logError("Failed to create packet tracking socket: ", e);
diff --git a/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java b/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java
index 9d2df57..dada61c 100644
--- a/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java
+++ b/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java
@@ -23,14 +23,18 @@
 
 import java.io.FileDescriptor;
 import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.SocketException;
 import java.util.List;
 import java.util.function.Predicate;
 
-
 /**
  * Collection of utilities for the network stack.
  */
 public class NetworkStackUtils {
+    static {
+        System.loadLibrary("networkstackutilsjni");
+    }
 
     /**
      * @return True if the array is null or 0-length.
@@ -98,4 +102,39 @@
         String value = DeviceConfig.getProperty(namespace, name);
         return value != null ? value : defaultValue;
     }
+
+    /**
+     * Attaches a socket filter that accepts DHCP packets to the given socket.
+     */
+    public static native void attachDhcpFilter(FileDescriptor fd) throws SocketException;
+
+    /**
+     * Attaches a socket filter that accepts ICMPv6 router advertisements to the given socket.
+     * @param fd the socket's {@link FileDescriptor}.
+     * @param packetType the hardware address type, one of ARPHRD_*.
+     */
+    public static native void attachRaFilter(FileDescriptor fd, int packetType)
+            throws SocketException;
+
+    /**
+     * Attaches a socket filter that accepts L2-L4 signaling traffic required for IP connectivity.
+     *
+     * This includes: all ARP, ICMPv6 RS/RA/NS/NA messages, and DHCPv4 exchanges.
+     *
+     * @param fd the socket's {@link FileDescriptor}.
+     * @param packetType the hardware address type, one of ARPHRD_*.
+     */
+    public static native void attachControlPacketFilter(FileDescriptor fd, int packetType)
+            throws SocketException;
+
+    /**
+     * Add an entry into the ARP cache.
+     */
+    public static void addArpEntry(Inet4Address ipv4Addr, android.net.MacAddress ethAddr,
+            String ifname, FileDescriptor fd) throws IOException {
+        addArpEntry(ethAddr.toByteArray(), ipv4Addr.getAddress(), ifname, fd);
+    }
+
+    private static native void addArpEntry(byte[] ethAddr, byte[] netAddr, String ifname,
+            FileDescriptor fd) throws IOException;
 }
diff --git a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
index 6f31f9b..8f7d988 100644
--- a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
+++ b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
@@ -779,6 +779,7 @@
 
         @Override
         public void exit() {
+            mLaunchCaptivePortalAppBroadcastReceiver = null;
             hideProvisioningNotification();
         }
     }
@@ -902,9 +903,10 @@
                 mLaunchCaptivePortalAppBroadcastReceiver = new CustomIntentReceiver(
                         ACTION_LAUNCH_CAPTIVE_PORTAL_APP, new Random().nextInt(),
                         CMD_LAUNCH_CAPTIVE_PORTAL_APP);
+                // Display the sign in notification.
+                // Only do this once for every time we enter MaybeNotifyState. b/122164725
+                showProvisioningNotification(mLaunchCaptivePortalAppBroadcastReceiver.mAction);
             }
-            // Display the sign in notification.
-            showProvisioningNotification(mLaunchCaptivePortalAppBroadcastReceiver.mAction);
             // Retest for captive portal occasionally.
             sendMessageDelayed(CMD_CAPTIVE_PORTAL_RECHECK, 0 /* no UID */,
                     CAPTIVE_PORTAL_REEVALUATE_DELAY_MS);
diff --git a/packages/NetworkStack/tests/Android.bp b/packages/NetworkStack/tests/Android.bp
index d0f419c..fe3c1e8 100644
--- a/packages/NetworkStack/tests/Android.bp
+++ b/packages/NetworkStack/tests/Android.bp
@@ -57,6 +57,7 @@
         "liblzma",
         "libnativehelper",
         "libnetworkstacktestsjni",
+        "libnetworkstackutilsjni",
         "libpackagelistparser",
         "libpcre2",
         "libprocessgroup",
diff --git a/packages/SystemUI/res/layout/global_actions_grid_item.xml b/packages/SystemUI/res/layout/global_actions_grid_item.xml
index 999c7b8..e92ec0f 100644
--- a/packages/SystemUI/res/layout/global_actions_grid_item.xml
+++ b/packages/SystemUI/res/layout/global_actions_grid_item.xml
@@ -43,6 +43,9 @@
         android:id="@*android:id/message"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
+        android:ellipsize="marquee"
+        android:marqueeRepeatLimit="marquee_forever"
+        android:singleLine="true"
         android:gravity="center"
         android:textSize="12dp"
         android:textAppearance="?android:attr/textAppearanceSmall"
diff --git a/packages/SystemUI/res/layout/global_actions_wrapped.xml b/packages/SystemUI/res/layout/global_actions_wrapped.xml
index f932303..d441070 100644
--- a/packages/SystemUI/res/layout/global_actions_wrapped.xml
+++ b/packages/SystemUI/res/layout/global_actions_wrapped.xml
@@ -15,7 +15,7 @@
     <!-- Global actions is right-aligned to be physically near power button -->
     <LinearLayout
         android:id="@android:id/list"
-        android:layout_width="@dimen/global_actions_panel_width"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="top|right"
         android:gravity="center"
@@ -26,7 +26,7 @@
     <!-- For separated button-->
     <FrameLayout
         android:id="@+id/separated_button"
-        android:layout_width="@dimen/global_actions_panel_width"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="top|right"
         android:layout_marginTop="6dp"
diff --git a/packages/SystemUI/res/values-sw410dp/dimens.xml b/packages/SystemUI/res/values-sw410dp/dimens.xml
index 73d1857..fea1ef1 100644
--- a/packages/SystemUI/res/values-sw410dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw410dp/dimens.xml
@@ -35,7 +35,7 @@
 
     <dimen name="global_actions_grid_item_icon_width">24dp</dimen>
     <dimen name="global_actions_grid_item_icon_height">24dp</dimen>
-    <dimen name="global_actions_grid_item_icon_top_margin">14dp</dimen>
+    <dimen name="global_actions_grid_item_icon_top_margin">18dp</dimen>
     <dimen name="global_actions_grid_item_icon_side_margin">24dp</dimen>
     <dimen name="global_actions_grid_item_icon_bottom_margin">4dp</dimen>
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index a4870d4..b1e2212 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -325,12 +325,11 @@
      * Adds or updates a bubble associated with the provided notification entry.
      *
      * @param notif          the notification associated with this bubble.
-     * @param updatePosition whether this update should promote the bubble to the top of the stack.
      */
-    public void updateBubble(NotificationEntry notif, boolean updatePosition) {
+    void updateBubble(NotificationEntry notif) {
         if (mStackView != null && mBubbleData.getBubble(notif.key) != null) {
             // It's an update
-            mStackView.updateBubble(notif, updatePosition);
+            mStackView.updateBubble(notif);
         } else {
             if (mStackView == null) {
                 mStackView = new BubbleStackView(mContext, mBubbleData, mSurfaceSynchronizer);
@@ -403,7 +402,7 @@
                 return;
             }
             if (entry.isBubble() && mNotificationInterruptionStateProvider.shouldBubbleUp(entry)) {
-                updateBubble(entry, true /* updatePosition */);
+                updateBubble(entry);
             }
         }
 
@@ -416,7 +415,7 @@
                     && alertAgain(entry, entry.notification.getNotification())) {
                 entry.setShowInShadeWhenBubble(true);
                 entry.setBubbleDismissed(false); // updates come back as bubbles even if dismissed
-                updateBubble(entry, true /* updatePosition */);
+                updateBubble(entry);
                 mStackView.updateDotVisibility(entry.key);
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index a4e1ad7..53e65e6 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -595,15 +595,13 @@
 
     /**
      * Updates a bubble in the stack.
-     *
-     * @param entry the entry to update in the stack.
-     * @param updatePosition whether this bubble should be moved to top of the stack.
+     *  @param entry the entry to update in the stack.
      */
-    public void updateBubble(NotificationEntry entry, boolean updatePosition) {
+    public void updateBubble(NotificationEntry entry) {
         Bubble b = mBubbleData.getBubble(entry.key);
         mBubbleData.updateBubble(entry.key, entry);
 
-        if (updatePosition && !mIsExpanded) {
+        if (!mIsExpanded) {
             // If alerting it gets promoted to top of the stack.
             if (mBubbleContainer.indexOfChild(b.iconView) != 0) {
                 mBubbleContainer.moveViewTo(b.iconView, 0);
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index e22b24e..7a3f3be 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -505,6 +505,7 @@
             }
             TextView messageView = v.findViewById(R.id.message);
             messageView.setTextColor(textColor);
+            messageView.setSelected(true); // necessary for marquee to work
             ImageView icon = (ImageView) v.findViewById(R.id.icon);
             icon.getDrawable().setTint(textColor);
             return v;
@@ -1137,6 +1138,7 @@
 
             ImageView icon = (ImageView) v.findViewById(R.id.icon);
             TextView messageView = (TextView) v.findViewById(R.id.message);
+            messageView.setSelected(true); // necessary for marquee to work
 
             TextView statusView = (TextView) v.findViewById(R.id.status);
             final String status = getStatus();
@@ -1240,6 +1242,7 @@
             if (messageView != null) {
                 messageView.setText(mMessageResId);
                 messageView.setEnabled(enabled);
+                messageView.setSelected(true); // necessary for marquee to work
             }
 
             boolean on = ((mState == State.On) || (mState == State.TurningOn));
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index a3db533..69d2e31 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -284,9 +284,8 @@
 
     @Override
     public boolean isCaptionStreamOptedOut() {
-        int currentValue = Settings.Secure.getInt(mContext.getContentResolver(),
-                Settings.Secure.ODI_CAPTIONS_OPTED_OUT, 0);
-        return currentValue == 1;
+        // TODO(b/129768185): Removing secure setting, to be replaced by sound event listener
+        return false;
     }
 
     public void getCaptionsComponentState(boolean fromTooltip) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index 5e16721..20f539b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -160,7 +160,7 @@
 
     @Test
     public void testAddBubble() {
-        mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
+        mBubbleController.updateBubble(mRow.getEntry());
         assertTrue(mBubbleController.hasBubbles());
 
         verify(mBubbleStateChangeListener).onHasBubblesChanged(true);
@@ -169,13 +169,13 @@
     @Test
     public void testHasBubbles() {
         assertFalse(mBubbleController.hasBubbles());
-        mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
+        mBubbleController.updateBubble(mRow.getEntry());
         assertTrue(mBubbleController.hasBubbles());
     }
 
     @Test
     public void testRemoveBubble() {
-        mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
+        mBubbleController.updateBubble(mRow.getEntry());
         assertTrue(mBubbleController.hasBubbles());
 
         verify(mBubbleStateChangeListener).onHasBubblesChanged(true);
@@ -189,8 +189,8 @@
 
     @Test
     public void testDismissStack() {
-        mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
-        mBubbleController.updateBubble(mRow2.getEntry(), true /* updatePosition */);
+        mBubbleController.updateBubble(mRow.getEntry());
+        mBubbleController.updateBubble(mRow2.getEntry());
         assertTrue(mBubbleController.hasBubbles());
 
         mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
@@ -206,7 +206,7 @@
 
         // Mark it as a bubble and add it explicitly
         mEntryListener.onPendingEntryAdded(mRow.getEntry());
-        mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
+        mBubbleController.updateBubble(mRow.getEntry());
 
         // We should have bubbles & their notifs should show in the shade
         assertTrue(mBubbleController.hasBubbles());
@@ -235,8 +235,8 @@
         // Mark it as a bubble and add it explicitly
         mEntryListener.onPendingEntryAdded(mRow.getEntry());
         mEntryListener.onPendingEntryAdded(mRow2.getEntry());
-        mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
-        mBubbleController.updateBubble(mRow2.getEntry(), true /* updatePosition */);
+        mBubbleController.updateBubble(mRow.getEntry());
+        mBubbleController.updateBubble(mRow2.getEntry());
 
         // We should have bubbles & their notifs should show in the shade
         assertTrue(mBubbleController.hasBubbles());
@@ -272,7 +272,7 @@
     public void testExpansionRemovesShowInShade() {
         // Mark it as a bubble and add it explicitly
         mEntryListener.onPendingEntryAdded(mRow.getEntry());
-        mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
+        mBubbleController.updateBubble(mRow.getEntry());
 
         // We should have bubbles & their notifs should show in the shade
         assertTrue(mBubbleController.hasBubbles());
@@ -293,8 +293,8 @@
         // Mark it as a bubble and add it explicitly
         mEntryListener.onPendingEntryAdded(mRow.getEntry());
         mEntryListener.onPendingEntryAdded(mRow2.getEntry());
-        mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
-        mBubbleController.updateBubble(mRow2.getEntry(), true /* updatePosition */);
+        mBubbleController.updateBubble(mRow.getEntry());
+        mBubbleController.updateBubble(mRow2.getEntry());
         verify(mBubbleStateChangeListener).onHasBubblesChanged(true);
 
         // Expand
@@ -333,7 +333,7 @@
 
         // Add the auto expand bubble
         mEntryListener.onPendingEntryAdded(mAutoExpandRow.getEntry());
-        mBubbleController.updateBubble(mAutoExpandRow.getEntry(), true /* updatePosition */);
+        mBubbleController.updateBubble(mAutoExpandRow.getEntry());
 
         // Expansion shouldn't change
         verify(mBubbleExpandListener, never()).onBubbleExpandChanged(false /* expanded */,
@@ -371,7 +371,7 @@
 
         // Add the auto expand bubble
         mEntryListener.onPendingEntryAdded(mAutoExpandRow.getEntry());
-        mBubbleController.updateBubble(mAutoExpandRow.getEntry(), true /* updatePosition */);
+        mBubbleController.updateBubble(mAutoExpandRow.getEntry());
 
         // Expansion should change
         verify(mBubbleExpandListener).onBubbleExpandChanged(true /* expanded */,
@@ -387,7 +387,7 @@
     public void testSuppressNotif_FailsNotForeground() {
         // Add the suppress notif bubble
         mEntryListener.onPendingEntryAdded(mSuppressNotifRow.getEntry());
-        mBubbleController.updateBubble(mSuppressNotifRow.getEntry(), true /* updatePosition */);
+        mBubbleController.updateBubble(mSuppressNotifRow.getEntry());
 
         // Should show in shade because we weren't forground
         assertTrue(mSuppressNotifRow.getEntry().showInShadeWhenBubble());
@@ -423,7 +423,7 @@
 
         // Add the suppress notif bubble
         mEntryListener.onPendingEntryAdded(mSuppressNotifRow.getEntry());
-        mBubbleController.updateBubble(mSuppressNotifRow.getEntry(), true /* updatePosition */);
+        mBubbleController.updateBubble(mSuppressNotifRow.getEntry());
 
         // Should NOT show in shade because we were foreground
         assertFalse(mSuppressNotifRow.getEntry().showInShadeWhenBubble());
@@ -438,7 +438,7 @@
         final String key = mRow.getEntry().key;
 
         mEntryListener.onPendingEntryAdded(mRow.getEntry());
-        mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
+        mBubbleController.updateBubble(mRow.getEntry());
 
         // Simulate notification cancellation.
         mEntryListener.onEntryRemoved(mRow.getEntry(), null /* notificationVisibility (unused) */,
@@ -464,22 +464,22 @@
 
     @Test
     public void testDeleteIntent_removeBubble_aged() throws PendingIntent.CanceledException {
-        mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
+        mBubbleController.updateBubble(mRow.getEntry());
         mBubbleController.removeBubble(mRow.getEntry().key, BubbleController.DISMISS_AGED);
         verify(mDeleteIntent, never()).send();
     }
 
     @Test
     public void testDeleteIntent_removeBubble_user() throws PendingIntent.CanceledException {
-        mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
+        mBubbleController.updateBubble(mRow.getEntry());
         mBubbleController.removeBubble(mRow.getEntry().key, BubbleController.DISMISS_USER_GESTURE);
         verify(mDeleteIntent, times(1)).send();
     }
 
     @Test
     public void testDeleteIntent_dismissStack() throws PendingIntent.CanceledException {
-        mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
-        mBubbleController.updateBubble(mRow2.getEntry(), true /* updatePosition */);
+        mBubbleController.updateBubble(mRow.getEntry());
+        mBubbleController.updateBubble(mRow2.getEntry());
         mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
         verify(mDeleteIntent, times(2)).send();
     }
diff --git a/services/Android.bp b/services/Android.bp
index 567efac..b08d1a8 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -31,6 +31,7 @@
         "services.print",
         "services.restrictions",
         "services.startop",
+        "services.systemcaptions",
         "services.usage",
         "services.usb",
         "services.voiceinteraction",
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 9b02c4e..757c2dc 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -129,7 +129,8 @@
     public ContentCaptureManagerService(@NonNull Context context) {
         super(context, new FrameworkResourcesServiceNameResolver(context,
                 com.android.internal.R.string.config_defaultContentCaptureService),
-                UserManager.DISALLOW_CONTENT_CAPTURE, /* refreshServiceOnPackageUpdate= */ false);
+                UserManager.DISALLOW_CONTENT_CAPTURE,
+                /*packageUpdatePolicy=*/ PACKAGE_UPDATE_POLICY_NO_REFRESH);
         DeviceConfig.addOnPropertyChangedListener(DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
                 ActivityThread.currentApplication().getMainExecutor(),
                 (namespace, key, value) -> onDeviceConfigChange(key, value));
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index feffe2f..0c681df 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -77,6 +77,7 @@
     private static final String ATTR_VERSION = "version";
     private static final String ATTR_NAME = "name";
     private static final String ATTR_DURATION = "duration";
+    private static final String ATTR_EXPLICIT_HEALTH_CHECK_DURATION = "health-check-duration";
     private static final String ATTR_PASSED_HEALTH_CHECK = "passed-health-check";
 
     private static PackageWatchdog sPackageWatchdog;
@@ -95,20 +96,22 @@
     private final ArrayMap<String, ObserverInternal> mAllObservers = new ArrayMap<>();
     // File containing the XML data of monitored packages /data/system/package-watchdog.xml
     private final AtomicFile mPolicyFile;
-    // Runnable to prune monitored packages that have expired
-    private final Runnable mPackageCleanup;
     private final ExplicitHealthCheckController mHealthCheckController;
     // Flag to control whether explicit health checks are supported or not
     @GuardedBy("mLock")
     private boolean mIsHealthCheckEnabled = true;
     @GuardedBy("mLock")
     private boolean mIsPackagesReady;
-    // Last SystemClock#uptimeMillis a package clean up was executed.
-    // 0 if mPackageCleanup not running.
-    private long mUptimeAtLastRescheduleMs;
-    // Duration a package cleanup was last scheduled for.
-    // 0 if mPackageCleanup not running.
-    private long mDurationAtLastReschedule;
+    // SystemClock#uptimeMillis when we last executed #pruneObservers.
+    // 0 if no prune is scheduled.
+    @GuardedBy("mLock")
+    private long mUptimeAtLastPruneMs;
+    // Duration in millis that the last prune was scheduled for.
+    // Used along with #mUptimeAtLastPruneMs after scheduling a prune to determine the remaining
+    // duration before #pruneObservers will be executed.
+    // 0 if no prune is scheduled.
+    @GuardedBy("mLock")
+    private long mDurationAtLastPrune;
 
     private PackageWatchdog(Context context) {
         // Needs to be constructed inline
@@ -129,7 +132,6 @@
         mPolicyFile = policyFile;
         mShortTaskHandler = shortTaskHandler;
         mLongTaskHandler = longTaskHandler;
-        mPackageCleanup = this::rescheduleCleanup;
         mHealthCheckController = controller;
         loadFromFile();
     }
@@ -171,9 +173,9 @@
             if (internalObserver != null) {
                 internalObserver.mRegisteredObserver = observer;
             }
-            if (mDurationAtLastReschedule == 0) {
-                // Nothing running, schedule
-                rescheduleCleanup();
+            if (mDurationAtLastPrune == 0) {
+                // Nothing running, prune
+                pruneAndSchedule();
             }
         }
     }
@@ -208,6 +210,7 @@
 
         List<MonitoredPackage> packages = new ArrayList<>();
         for (int i = 0; i < packageNames.size(); i++) {
+            // Health checks not available yet so health check state will start INACTIVE
             packages.add(new MonitoredPackage(packageNames.get(i), durationMs, false));
         }
 
@@ -225,9 +228,9 @@
             }
         }
         registerHealthObserver(observer);
-        // Always reschedule because we may need to expire packages
-        // earlier than we are already scheduled for
-        rescheduleCleanup();
+        // Always prune because we may have received packges requiring an earlier
+        // schedule than we are currently scheduled for.
+        pruneAndSchedule();
         Slog.i(TAG, "Syncing health check requests, observing packages " + packageNames);
         syncRequestsAsync();
         saveToFileAsync();
@@ -312,15 +315,18 @@
         });
     }
 
-    // TODO(b/120598832): Optimize write? Maybe only write a separate smaller file?
+    // TODO(b/120598832): Optimize write? Maybe only write a separate smaller file? Also
+    // avoid holding lock?
     // This currently adds about 7ms extra to shutdown thread
     /** Writes the package information to file during shutdown. */
     public void writeNow() {
-        if (!mAllObservers.isEmpty()) {
-            mLongTaskHandler.removeCallbacks(this::saveToFile);
-            pruneObservers(SystemClock.uptimeMillis() - mUptimeAtLastRescheduleMs);
-            saveToFile();
-            Slog.i(TAG, "Last write to update package durations");
+        synchronized (mLock) {
+            if (!mAllObservers.isEmpty()) {
+                mLongTaskHandler.removeCallbacks(this::saveToFile);
+                pruneObservers(SystemClock.uptimeMillis() - mUptimeAtLastPruneMs);
+                saveToFile();
+                Slog.i(TAG, "Last write to update package durations");
+            }
         }
     }
 
@@ -450,9 +456,10 @@
 
     private void onSupportedPackages(List<String> supportedPackages) {
         boolean shouldUpdateFile = false;
+        boolean shouldPrune = false;
 
         synchronized (mLock) {
-            Slog.i(TAG, "Received supported packages " + supportedPackages);
+            Slog.d(TAG, "Received supported packages " + supportedPackages);
             Iterator<ObserverInternal> oit = mAllObservers.values().iterator();
             while (oit.hasNext()) {
                 ObserverInternal observer = oit.next();
@@ -461,12 +468,31 @@
                 while (pit.hasNext()) {
                     MonitoredPackage monitoredPackage = pit.next();
                     String packageName = monitoredPackage.mName;
-                    if (!monitoredPackage.mHasPassedHealthCheck
-                            && !supportedPackages.contains(packageName)) {
-                        // Hasn't passed health check but health check is not supported
-                        Slog.i(TAG, packageName + " does not support health checks, passing");
+                    int healthCheckState = monitoredPackage.getHealthCheckState();
+
+                    if (healthCheckState != MonitoredPackage.STATE_PASSED) {
+                        // Have to update file, we will either transition state or reduce
+                        // health check duration
                         shouldUpdateFile = true;
-                        monitoredPackage.mHasPassedHealthCheck = true;
+
+                        if (supportedPackages.contains(packageName)) {
+                            // Supports health check, transition to ACTIVE if not already.
+                            // We need to prune packages earlier than already scheduled.
+                            shouldPrune = true;
+
+                            // TODO: Get healthCheckDuration from supportedPackages
+                            long healthCheckDuration = monitoredPackage.mDurationMs;
+                            monitoredPackage.mHealthCheckDurationMs = Math.min(healthCheckDuration,
+                                    monitoredPackage.mDurationMs);
+                            Slog.i(TAG, packageName + " health check state is now: ACTIVE("
+                                    + monitoredPackage.mHealthCheckDurationMs + "ms)");
+                        } else {
+                            // Does not support health check, transistion to PASSED
+                            monitoredPackage.mHasPassedHealthCheck = true;
+                            Slog.i(TAG, packageName + " health check state is now: PASSED");
+                        }
+                    } else {
+                        Slog.i(TAG, packageName + " does not support health check, state: PASSED");
                     }
                 }
             }
@@ -475,6 +501,9 @@
         if (shouldUpdateFile) {
             saveToFileAsync();
         }
+        if (shouldPrune) {
+            pruneAndSchedule();
+        }
     }
 
     private Set<String> getPackagesPendingHealthChecksLocked() {
@@ -496,59 +525,64 @@
         return packages;
     }
 
-    /** Reschedules handler to prune expired packages from observers. */
-    private void rescheduleCleanup() {
+    /** Executes {@link #pruneObservers} and schedules the next execution. */
+    private void pruneAndSchedule() {
         synchronized (mLock) {
-            long nextDurationToScheduleMs = getEarliestPackageExpiryLocked();
+            long nextDurationToScheduleMs = getNextPruneScheduleMillisLocked();
             if (nextDurationToScheduleMs == Long.MAX_VALUE) {
-                Slog.i(TAG, "No monitored packages, ending package cleanup");
-                mDurationAtLastReschedule = 0;
-                mUptimeAtLastRescheduleMs = 0;
+                Slog.i(TAG, "No monitored packages, ending prune");
+                mDurationAtLastPrune = 0;
+                mUptimeAtLastPruneMs = 0;
                 return;
             }
             long uptimeMs = SystemClock.uptimeMillis();
-            // O if mPackageCleanup not running
-            long elapsedDurationMs = mUptimeAtLastRescheduleMs == 0
-                    ? 0 : uptimeMs - mUptimeAtLastRescheduleMs;
-            // Less than O if mPackageCleanup unexpectedly didn't run yet even though
-            // and we are past the last duration scheduled to run
-            long remainingDurationMs = mDurationAtLastReschedule - elapsedDurationMs;
-            if (mUptimeAtLastRescheduleMs == 0
+            // O if not running
+            long elapsedDurationMs = mUptimeAtLastPruneMs == 0
+                    ? 0 : uptimeMs - mUptimeAtLastPruneMs;
+            // Less than O if unexpectedly didn't run yet even though
+            // we are past the last duration scheduled to run
+            long remainingDurationMs = mDurationAtLastPrune - elapsedDurationMs;
+            if (mUptimeAtLastPruneMs == 0
                     || remainingDurationMs <= 0
                     || nextDurationToScheduleMs < remainingDurationMs) {
                 // First schedule or an earlier reschedule
                 pruneObservers(elapsedDurationMs);
-                mShortTaskHandler.removeCallbacks(mPackageCleanup);
-                mShortTaskHandler.postDelayed(mPackageCleanup, nextDurationToScheduleMs);
-                mDurationAtLastReschedule = nextDurationToScheduleMs;
-                mUptimeAtLastRescheduleMs = uptimeMs;
+                // We don't use Handler#hasCallbacks because we want to update the schedule delay
+                mShortTaskHandler.removeCallbacks(this::pruneAndSchedule);
+                mShortTaskHandler.postDelayed(this::pruneAndSchedule, nextDurationToScheduleMs);
+                mDurationAtLastPrune = nextDurationToScheduleMs;
+                mUptimeAtLastPruneMs = uptimeMs;
             }
         }
     }
 
     /**
-     * Returns the earliest time a package should expire.
+     * Returns the next time in millis to schedule a prune.
+     *
      * @returns Long#MAX_VALUE if there are no observed packages.
      */
-    private long getEarliestPackageExpiryLocked() {
+    private long getNextPruneScheduleMillisLocked() {
         long shortestDurationMs = Long.MAX_VALUE;
         for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
             ArrayMap<String, MonitoredPackage> packages = mAllObservers.valueAt(oIndex).mPackages;
             for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
-                long duration = packages.valueAt(pIndex).mDurationMs;
+                MonitoredPackage mp = packages.valueAt(pIndex);
+                long duration = Math.min(mp.mDurationMs, mp.mHealthCheckDurationMs);
                 if (duration < shortestDurationMs) {
                     shortestDurationMs = duration;
                 }
             }
         }
-        Slog.v(TAG, "Earliest package time is " + shortestDurationMs);
+        Slog.i(TAG, "Next prune will be scheduled in " + shortestDurationMs + "ms");
 
         return shortestDurationMs;
     }
 
     /**
      * Removes {@code elapsedMs} milliseconds from all durations on monitored packages.
-     * Discards expired packages and discards observers without any packages.
+     *
+     * <p> Prunes all observers with {@link ObserverInternal#prunePackages} and discards observers
+     * without any packages left.
      */
     private void pruneObservers(long elapsedMs) {
         if (elapsedMs == 0) {
@@ -559,8 +593,8 @@
             Iterator<ObserverInternal> it = mAllObservers.values().iterator();
             while (it.hasNext()) {
                 ObserverInternal observer = it.next();
-                List<MonitoredPackage> failedPackages =
-                        observer.updateMonitoringDurations(elapsedMs);
+                Set<MonitoredPackage> failedPackages =
+                        observer.prunePackages(elapsedMs);
                 if (!failedPackages.isEmpty()) {
                     onHealthCheckFailed(observer, failedPackages);
                 }
@@ -570,32 +604,34 @@
                 }
             }
         }
-        Slog.i(TAG, "Syncing health check requests pruned packages");
+        Slog.i(TAG, "Syncing health check requests, pruned observers");
         syncRequestsAsync();
         saveToFileAsync();
     }
 
     private void onHealthCheckFailed(ObserverInternal observer,
-            List<MonitoredPackage> failedPackages) {
+            Set<MonitoredPackage> failedPackages) {
         mLongTaskHandler.post(() -> {
             synchronized (mLock) {
                 PackageHealthObserver registeredObserver = observer.mRegisteredObserver;
                 if (registeredObserver != null) {
                     PackageManager pm = mContext.getPackageManager();
-                    for (int i = 0; i < failedPackages.size(); i++) {
-                        String packageName = failedPackages.get(i).mName;
+                    Iterator<MonitoredPackage> it = failedPackages.iterator();
+                    while (it.hasNext()) {
+                        String failedPackage = it.next().mName;
                         long versionCode = 0;
-                        Slog.i(TAG, "Explicit health check failed for package " + packageName);
+                        Slog.i(TAG, "Explicit health check failed for package " + failedPackage);
                         try {
                             versionCode = pm.getPackageInfo(
-                                    packageName, 0 /* flags */).getLongVersionCode();
+                                    failedPackage, 0 /* flags */).getLongVersionCode();
                         } catch (PackageManager.NameNotFoundException e) {
                             Slog.w(TAG, "Explicit health check failed but could not find package "
-                                    + packageName);
+                                    + failedPackage);
                             // TODO(b/120598832): Skip. We only continue to pass tests for now since
                             // the tests don't install any packages
                         }
-                        registeredObserver.execute(new VersionedPackage(packageName, versionCode));
+                        registeredObserver.execute(
+                                new VersionedPackage(failedPackage, versionCode));
                     }
                 }
             }
@@ -670,34 +706,38 @@
     }
 
     private void saveToFileAsync() {
-        // TODO(b/120598832): Use Handler#hasCallbacks instead of removing and posting
-        mLongTaskHandler.removeCallbacks(this::saveToFile);
-        mLongTaskHandler.post(this::saveToFile);
+        if (!mLongTaskHandler.hasCallbacks(this::saveToFile)) {
+            mLongTaskHandler.post(this::saveToFile);
+        }
     }
 
     /**
      * Represents an observer monitoring a set of packages along with the failure thresholds for
      * each package.
+     *
+     * <p> Note, the PackageWatchdog#mLock must always be held when reading or writing
+     * instances of this class.
      */
-    static class ObserverInternal {
+    //TODO(b/120598832): Remove 'm' from non-private fields
+    private static class ObserverInternal {
         public final String mName;
         //TODO(b/120598832): Add getter for mPackages
-        public final ArrayMap<String, MonitoredPackage> mPackages;
+        @GuardedBy("mLock")
+        public final ArrayMap<String, MonitoredPackage> mPackages = new ArrayMap<>();
         @Nullable
+        @GuardedBy("mLock")
         public PackageHealthObserver mRegisteredObserver;
 
         ObserverInternal(String name, List<MonitoredPackage> packages) {
             mName = name;
-            mPackages = new ArrayMap<>();
             updatePackages(packages);
         }
 
         /**
-         * Writes important details to file. Doesn't persist any package failure thresholds.
-         *
-         * <p>Note that this method is <b>not</b> thread safe. It should only be called from
-         * #saveToFile which runs on a single threaded handler.
+         * Writes important {@link MonitoredPackage} details for this observer to file.
+         * Does not persist any package failure thresholds.
          */
+        @GuardedBy("mLock")
         public boolean write(XmlSerializer out) {
             try {
                 out.startTag(null, TAG_OBSERVER);
@@ -707,6 +747,8 @@
                     out.startTag(null, TAG_PACKAGE);
                     out.attribute(null, ATTR_NAME, p.mName);
                     out.attribute(null, ATTR_DURATION, String.valueOf(p.mDurationMs));
+                    out.attribute(null, ATTR_EXPLICIT_HEALTH_CHECK_DURATION,
+                            String.valueOf(p.mHealthCheckDurationMs));
                     out.attribute(null, ATTR_PASSED_HEALTH_CHECK,
                             String.valueOf(p.mHasPassedHealthCheck));
                     out.endTag(null, TAG_PACKAGE);
@@ -719,56 +761,68 @@
             }
         }
 
+        @GuardedBy("mLock")
         public void updatePackages(List<MonitoredPackage> packages) {
-            synchronized (mName) {
-                for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
-                    MonitoredPackage p = packages.get(pIndex);
-                    mPackages.put(p.mName, p);
-                }
+            for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
+                MonitoredPackage p = packages.get(pIndex);
+                mPackages.put(p.mName, p);
             }
         }
 
         /**
          * Reduces the monitoring durations of all packages observed by this observer by
-         *  {@code elapsedMs}. If any duration is less than 0, the package is removed from
-         * observation.
+         * {@code elapsedMs}. If any duration is less than 0, the package is removed from
+         * observation. If any health check duration is less than 0, the health check result
+         * is evaluated.
          *
-         * @returns a {@link List} of packages that were removed from the observer without explicit
+         * @returns a {@link Set} of packages that were removed from the observer without explicit
          * health check passing, or an empty list if no package expired for which an explicit health
          * check was still pending
          */
-        public List<MonitoredPackage> updateMonitoringDurations(long elapsedMs) {
-            List<MonitoredPackage> removedPackages = new ArrayList<>();
-            synchronized (mName) {
-                Iterator<MonitoredPackage> it = mPackages.values().iterator();
-                while (it.hasNext()) {
-                    MonitoredPackage p = it.next();
-                    long newDuration = p.mDurationMs - elapsedMs;
-                    if (newDuration > 0) {
-                        p.mDurationMs = newDuration;
-                    } else {
-                        if (!p.mHasPassedHealthCheck) {
-                            removedPackages.add(p);
-                        }
-                        it.remove();
+        @GuardedBy("mLock")
+        private Set<MonitoredPackage> prunePackages(long elapsedMs) {
+            Set<MonitoredPackage> failedPackages = new ArraySet<>();
+            Iterator<MonitoredPackage> it = mPackages.values().iterator();
+            while (it.hasNext()) {
+                MonitoredPackage p = it.next();
+                int healthCheckState = p.getHealthCheckState();
+
+                // Handle health check timeouts
+                if (healthCheckState == MonitoredPackage.STATE_ACTIVE) {
+                    // Only reduce duration if state is active
+                    p.mHealthCheckDurationMs -= elapsedMs;
+                    // Check duration after reducing duration
+                    if (p.mHealthCheckDurationMs <= 0) {
+                        failedPackages.add(p);
                     }
                 }
-                return removedPackages;
+
+                // Handle package expiry
+                p.mDurationMs -= elapsedMs;
+                // Check duration after reducing duration
+                if (p.mDurationMs <= 0) {
+                    if (healthCheckState == MonitoredPackage.STATE_INACTIVE) {
+                        Slog.w(TAG, "Package " + p.mName
+                                + " expiring without starting health check, failing");
+                        failedPackages.add(p);
+                    }
+                    it.remove();
+                }
             }
+            return failedPackages;
         }
 
         /**
          * Increments failure counts of {@code packageName}.
          * @returns {@code true} if failure threshold is exceeded, {@code false} otherwise
          */
+        @GuardedBy("mLock")
         public boolean onPackageFailure(String packageName) {
-            synchronized (mName) {
-                MonitoredPackage p = mPackages.get(packageName);
-                if (p != null) {
-                    return p.onFailure();
-                }
-                return false;
+            MonitoredPackage p = mPackages.get(packageName);
+            if (p != null) {
+                return p.onFailure();
             }
+            return false;
         }
 
         /**
@@ -796,11 +850,14 @@
                             String packageName = parser.getAttributeValue(null, ATTR_NAME);
                             long duration = Long.parseLong(
                                     parser.getAttributeValue(null, ATTR_DURATION));
+                            long healthCheckDuration = Long.parseLong(
+                                    parser.getAttributeValue(null,
+                                            ATTR_EXPLICIT_HEALTH_CHECK_DURATION));
                             boolean hasPassedHealthCheck = Boolean.parseBoolean(
                                     parser.getAttributeValue(null, ATTR_PASSED_HEALTH_CHECK));
                             if (!TextUtils.isEmpty(packageName)) {
                                 packages.add(new MonitoredPackage(packageName, duration,
-                                        hasPassedHealthCheck));
+                                        healthCheckDuration, hasPassedHealthCheck));
                             }
                         } catch (NumberFormatException e) {
                             Slog.wtf(TAG, "Skipping package for observer " + observerName, e);
@@ -819,21 +876,50 @@
         }
     }
 
-    /** Represents a package along with the time it should be monitored for. */
-    static class MonitoredPackage {
+    /**
+     * Represents a package along with the time it should be monitored for.
+     *
+     * <p> Note, the PackageWatchdog#mLock must always be held when reading or writing
+     * instances of this class.
+     */
+    //TODO(b/120598832): Remove 'm' from non-private fields
+    private static class MonitoredPackage {
+        // Health check states
+        // mName has not passed health check but has requested a health check
+        public static int STATE_ACTIVE = 0;
+        // mName has not passed health check and has not requested a health check
+        public static int STATE_INACTIVE = 1;
+        // mName has passed health check
+        public static int STATE_PASSED = 2;
+
         public final String mName;
         // Whether an explicit health check has passed
+        @GuardedBy("mLock")
         public boolean mHasPassedHealthCheck;
         // System uptime duration to monitor package
+        @GuardedBy("mLock")
         public long mDurationMs;
+        // System uptime duration to check the result of an explicit health check
+        // Initially, MAX_VALUE until we get a value from the health check service
+        // and request health checks.
+        @GuardedBy("mLock")
+        public long mHealthCheckDurationMs = Long.MAX_VALUE;
         // System uptime of first package failure
+        @GuardedBy("mLock")
         private long mUptimeStartMs;
         // Number of failures since mUptimeStartMs
+        @GuardedBy("mLock")
         private int mFailures;
 
         MonitoredPackage(String name, long durationMs, boolean hasPassedHealthCheck) {
+            this(name, durationMs, Long.MAX_VALUE, hasPassedHealthCheck);
+        }
+
+        MonitoredPackage(String name, long durationMs, long healthCheckDurationMs,
+                boolean hasPassedHealthCheck) {
             mName = name;
             mDurationMs = durationMs;
+            mHealthCheckDurationMs = healthCheckDurationMs;
             mHasPassedHealthCheck = hasPassedHealthCheck;
         }
 
@@ -842,7 +928,8 @@
          *
          * @return {@code true} if failure count exceeds a threshold, {@code false} otherwise
          */
-        public synchronized boolean onFailure() {
+        @GuardedBy("mLock")
+        public boolean onFailure() {
             final long now = SystemClock.uptimeMillis();
             final long duration = now - mUptimeStartMs;
             if (duration > TRIGGER_DURATION_MS) {
@@ -860,5 +947,20 @@
             }
             return failed;
         }
+
+        /**
+         * Returns any of the health check states of {@link #STATE_ACTIVE},
+         * {@link #STATE_INACTIVE} or {@link #STATE_PASSED}
+         */
+        @GuardedBy("mLock")
+        public int getHealthCheckState() {
+            if (mHasPassedHealthCheck) {
+                return STATE_PASSED;
+            } else if (mHealthCheckDurationMs == Long.MAX_VALUE) {
+                return STATE_INACTIVE;
+            } else {
+                return STATE_ACTIVE;
+            }
+        }
     }
 }
diff --git a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
index 098b0e9..9782f30 100644
--- a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
+++ b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.infra;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -45,6 +46,8 @@
 import com.android.server.SystemService;
 
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.List;
 
 /**
@@ -75,6 +78,30 @@
 public abstract class AbstractMasterSystemService<M extends AbstractMasterSystemService<M, S>,
         S extends AbstractPerUserSystemService<S, M>> extends SystemService {
 
+    /** On a package update, does not refresh the per-user service in the cache. */
+    public static final int PACKAGE_UPDATE_POLICY_NO_REFRESH = 0;
+
+    /**
+     * On a package update, removes any existing per-user services in the cache.
+     *
+     * <p>This does not immediately recreate these services. It is assumed they will be recreated
+     * for the next user request.
+     */
+    public static final int PACKAGE_UPDATE_POLICY_REFRESH_LAZY = 1;
+
+    /**
+     * On a package update, removes and recreates any existing per-user services in the cache.
+     */
+    public static final int PACKAGE_UPDATE_POLICY_REFRESH_EAGER = 2;
+
+    @IntDef(flag = true, prefix = { "PACKAGE_UPDATE_POLICY_" }, value = {
+            PACKAGE_UPDATE_POLICY_NO_REFRESH,
+            PACKAGE_UPDATE_POLICY_REFRESH_LAZY,
+            PACKAGE_UPDATE_POLICY_REFRESH_EAGER
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PackageUpdatePolicy {}
+
     /**
      * Log tag
      */
@@ -127,8 +154,11 @@
 
     /**
      * Whether the per-user service should be removed from the cache when its apk is updated.
+     *
+     * <p>One of {@link #PACKAGE_UPDATE_POLICY_NO_REFRESH},
+     * {@link #PACKAGE_UPDATE_POLICY_REFRESH_LAZY} or {@link #PACKAGE_UPDATE_POLICY_REFRESH_EAGER}.
      */
-    private final boolean mRefreshServiceOnPackageUpdate;
+    private final @PackageUpdatePolicy int mPackageUpdatePolicy;
 
     /**
      * Name of the service packages whose APK are being updated, keyed by user id.
@@ -154,7 +184,7 @@
             @Nullable ServiceNameResolver serviceNameResolver,
             @Nullable String disallowProperty) {
         this(context, serviceNameResolver, disallowProperty,
-                /* refreshServiceOnPackageUpdate=*/ true);
+                /*packageUpdatePolicy=*/ PACKAGE_UPDATE_POLICY_REFRESH_LAZY);
     }
 
     /**
@@ -167,17 +197,19 @@
      * @param disallowProperty when not {@code null}, defines a {@link UserManager} restriction that
      *        disables the service. <b>NOTE: </b> you'll also need to add it to
      *        {@code UserRestrictionsUtils.USER_RESTRICTIONS}.
-     * @param refreshServiceOnPackageUpdate when {@code true}, the
-     *        {@link AbstractPerUserSystemService} is removed from the cache (and re-added) when the
-     *        service package is updated; when {@code false}, the service is untouched during the
-     *        update.
+     * @param packageUpdatePolicy when {@link #PACKAGE_UPDATE_POLICY_REFRESH_LAZY}, the
+     *        {@link AbstractPerUserSystemService} is removed from the cache when the service
+     *        package is updated; when {@link #PACKAGE_UPDATE_POLICY_REFRESH_EAGER}, the
+     *        {@link AbstractPerUserSystemService} is removed from the cache and immediately
+     *        re-added when the service package is updated; when
+     *        {@link #PACKAGE_UPDATE_POLICY_NO_REFRESH}, the service is untouched during the update.
      */
     protected AbstractMasterSystemService(@NonNull Context context,
             @Nullable ServiceNameResolver serviceNameResolver,
-            @Nullable String disallowProperty, boolean refreshServiceOnPackageUpdate) {
+            @Nullable String disallowProperty, @PackageUpdatePolicy int packageUpdatePolicy) {
         super(context);
 
-        mRefreshServiceOnPackageUpdate = refreshServiceOnPackageUpdate;
+        mPackageUpdatePolicy = packageUpdatePolicy;
 
         mServiceNameResolver = serviceNameResolver;
         if (mServiceNameResolver != null) {
@@ -645,7 +677,7 @@
             final int size = mServicesCache.size();
             pw.print(prefix); pw.print("Debug: "); pw.print(realDebug);
             pw.print(" Verbose: "); pw.println(realVerbose);
-            pw.print("Refresh on package update: "); pw.println(mRefreshServiceOnPackageUpdate);
+            pw.print("Refresh on package update: "); pw.println(mPackageUpdatePolicy);
             if (mUpdatingPackageNames != null) {
                 pw.print("Packages being updated: "); pw.println(mUpdatingPackageNames);
             }
@@ -701,12 +733,21 @@
                     }
                     mUpdatingPackageNames.put(userId, packageName);
                     onServicePackageUpdatingLocked(userId);
-                    if (mRefreshServiceOnPackageUpdate) {
+                    if (mPackageUpdatePolicy != PACKAGE_UPDATE_POLICY_NO_REFRESH) {
                         if (debug) {
-                            Slog.d(mTag, "Removing service for user " + userId + " because package "
-                                    + activePackageName + " is being updated");
+                            Slog.d(mTag, "Removing service for user " + userId
+                                    + " because package " + activePackageName
+                                    + " is being updated");
                         }
                         removeCachedServiceLocked(userId);
+
+                        if (mPackageUpdatePolicy == PACKAGE_UPDATE_POLICY_REFRESH_EAGER) {
+                            if (debug) {
+                                Slog.d(mTag, "Eagerly recreating service for user "
+                                        + userId);
+                            }
+                            getServiceForUserLocked(userId);
+                        }
                     } else {
                         if (debug) {
                             Slog.d(mTag, "Holding service for user " + userId + " while package "
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 4824d7b..0cd730b 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -256,6 +256,8 @@
             "com.android.server.autofill.AutofillManagerService";
     private static final String CONTENT_CAPTURE_MANAGER_SERVICE_CLASS =
             "com.android.server.contentcapture.ContentCaptureManagerService";
+    private static final String SYSTEM_CAPTIONS_MANAGER_SERVICE_CLASS =
+            "com.android.server.systemcaptions.SystemCaptionsManagerService";
     private static final String TIME_ZONE_RULES_MANAGER_SERVICE_CLASS =
             "com.android.server.timezone.RulesManagerService$Lifecycle";
     private static final String IOT_SERVICE_CLASS =
@@ -1234,6 +1236,8 @@
             startContentCaptureService(context);
             startAttentionService(context);
 
+            startSystemCaptionsManagerService(context);
+
             // App prediction manager service
             traceBeginAndSlog("StartAppPredictionService");
             mSystemServiceManager.startService(APP_PREDICTION_MANAGER_SERVICE_CLASS);
@@ -2232,6 +2236,19 @@
         }, BOOT_TIMINGS_TRACE_LOG);
     }
 
+    private void startSystemCaptionsManagerService(@NonNull Context context) {
+        String serviceName = context.getString(
+                com.android.internal.R.string.config_defaultSystemCaptionsManagerService);
+        if (TextUtils.isEmpty(serviceName)) {
+            Slog.d(TAG, "SystemCaptionsManagerService disabled because resource is not overlaid");
+            return;
+        }
+
+        traceBeginAndSlog("StartSystemCaptionsManagerService");
+        mSystemServiceManager.startService(SYSTEM_CAPTIONS_MANAGER_SERVICE_CLASS);
+        traceEnd();
+    }
+
     private void startContentCaptureService(@NonNull Context context) {
         // First check if it was explicitly enabled by DeviceConfig
         boolean explicitlyEnabled = false;
@@ -2280,7 +2297,7 @@
         traceEnd();
     }
 
-    static final void startSystemUi(Context context, WindowManagerService windowManager) {
+    private static void startSystemUi(Context context, WindowManagerService windowManager) {
         Intent intent = new Intent();
         intent.setComponent(new ComponentName("com.android.systemui",
                 "com.android.systemui.SystemUIService"));
diff --git a/services/net/java/android/net/netlink/NetlinkSocket.java b/services/net/java/android/net/netlink/NetlinkSocket.java
index 4240d24..7311fc5 100644
--- a/services/net/java/android/net/netlink/NetlinkSocket.java
+++ b/services/net/java/android/net/netlink/NetlinkSocket.java
@@ -30,6 +30,7 @@
 import android.net.util.SocketUtils;
 import android.system.ErrnoException;
 import android.system.Os;
+import android.system.StructTimeval;
 import android.util.Log;
 
 import java.io.FileDescriptor;
@@ -128,7 +129,7 @@
             throws ErrnoException, IllegalArgumentException, InterruptedIOException {
         checkTimeout(timeoutMs);
 
-        SocketUtils.setSocketTimeValueOption(fd, SOL_SOCKET, SO_RCVTIMEO, timeoutMs);
+        Os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, StructTimeval.fromMillis(timeoutMs));
 
         ByteBuffer byteBuffer = ByteBuffer.allocate(bufsize);
         int length = Os.read(fd, byteBuffer);
@@ -151,7 +152,7 @@
             FileDescriptor fd, byte[] bytes, int offset, int count, long timeoutMs)
             throws ErrnoException, IllegalArgumentException, InterruptedIOException {
         checkTimeout(timeoutMs);
-        SocketUtils.setSocketTimeValueOption(fd, SOL_SOCKET, SO_SNDTIMEO, timeoutMs);
+        Os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(timeoutMs));
         return Os.write(fd, bytes, offset, count);
     }
 }
diff --git a/services/systemcaptions/Android.bp b/services/systemcaptions/Android.bp
new file mode 100644
index 0000000..4e190b6
--- /dev/null
+++ b/services/systemcaptions/Android.bp
@@ -0,0 +1,5 @@
+java_library_static {
+    name: "services.systemcaptions",
+    srcs: ["java/**/*.java"],
+    libs: ["services.core"],
+}
diff --git a/services/systemcaptions/java/com/android/server/systemcaptions/RemoteSystemCaptionsManagerService.java b/services/systemcaptions/java/com/android/server/systemcaptions/RemoteSystemCaptionsManagerService.java
new file mode 100644
index 0000000..5480b6c
--- /dev/null
+++ b/services/systemcaptions/java/com/android/server/systemcaptions/RemoteSystemCaptionsManagerService.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.systemcaptions;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+/** Manages the connection to the remote system captions manager service. */
+final class RemoteSystemCaptionsManagerService {
+
+    private static final String TAG = RemoteSystemCaptionsManagerService.class.getSimpleName();
+
+    private static final String SERVICE_INTERFACE =
+            "android.service.systemcaptions.SystemCaptionsManagerService";
+
+    private final Object mLock = new Object();
+
+    private final Context mContext;
+    private final Intent mIntent;
+    private final ComponentName mComponentName;
+    private final int mUserId;
+    private final boolean mVerbose;
+    private final Handler mHandler;
+
+    private final RemoteServiceConnection mServiceConnection = new RemoteServiceConnection();
+
+    @GuardedBy("mLock")
+    @Nullable private IBinder mService;
+
+    @GuardedBy("mLock")
+    private boolean mBinding = false;
+
+    @GuardedBy("mLock")
+    private boolean mDestroyed = false;
+
+    RemoteSystemCaptionsManagerService(
+            Context context, ComponentName componentName, int userId, boolean verbose) {
+        mContext = context;
+        mComponentName = componentName;
+        mUserId = userId;
+        mVerbose = verbose;
+        mIntent = new Intent(SERVICE_INTERFACE).setComponent(componentName);
+        mHandler = new Handler(Looper.getMainLooper());
+    }
+
+    void initialize() {
+        if (mVerbose) {
+            Slog.v(TAG, "initialize()");
+        }
+        ensureBound();
+    }
+
+    void destroy() {
+        if (mVerbose) {
+            Slog.v(TAG, "destroy()");
+        }
+
+        synchronized (mLock) {
+            if (mDestroyed) {
+                if (mVerbose) {
+                    Slog.v(TAG, "destroy(): Already destroyed");
+                }
+                return;
+            }
+            mDestroyed = true;
+            ensureUnboundLocked();
+        }
+    }
+
+    boolean isDestroyed() {
+        synchronized (mLock) {
+            return mDestroyed;
+        }
+    }
+
+    private void ensureBound() {
+        synchronized (mLock) {
+            if (mService != null || mBinding) {
+                return;
+            }
+
+            if (mVerbose) {
+                Slog.v(TAG, "ensureBound(): binding");
+            }
+            mBinding = true;
+
+            int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE;
+            boolean willBind = mContext.bindServiceAsUser(mIntent, mServiceConnection, flags,
+                    mHandler, new UserHandle(mUserId));
+            if (!willBind) {
+                Slog.w(TAG, "Could not bind to " + mIntent + " with flags " + flags);
+                mBinding = false;
+                mService = null;
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void ensureUnboundLocked() {
+        if (mService == null && !mBinding) {
+            return;
+        }
+
+        mBinding = false;
+        mService = null;
+
+        if (mVerbose) {
+            Slog.v(TAG, "ensureUnbound(): unbinding");
+        }
+        mContext.unbindService(mServiceConnection);
+    }
+
+    private class RemoteServiceConnection implements ServiceConnection {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            synchronized (mLock) {
+                if (mVerbose) {
+                    Slog.v(TAG, "onServiceConnected()");
+                }
+                if (mDestroyed || !mBinding) {
+                    Slog.wtf(TAG, "onServiceConnected() dispatched after unbindService");
+                    return;
+                }
+                mBinding = false;
+                mService = service;
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            synchronized (mLock) {
+                if (mVerbose) {
+                    Slog.v(TAG, "onServiceDisconnected()");
+                }
+                mBinding = true;
+                mService = null;
+            }
+        }
+    }
+}
diff --git a/services/systemcaptions/java/com/android/server/systemcaptions/SystemCaptionsManagerPerUserService.java b/services/systemcaptions/java/com/android/server/systemcaptions/SystemCaptionsManagerPerUserService.java
new file mode 100644
index 0000000..b503670
--- /dev/null
+++ b/services/systemcaptions/java/com/android/server/systemcaptions/SystemCaptionsManagerPerUserService.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.systemcaptions;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.infra.AbstractPerUserSystemService;
+
+/** Manages the captions manager service on a per-user basis. */
+final class SystemCaptionsManagerPerUserService extends
+        AbstractPerUserSystemService<SystemCaptionsManagerPerUserService,
+                SystemCaptionsManagerService> {
+
+    private static final String TAG = SystemCaptionsManagerPerUserService.class.getSimpleName();
+
+    @Nullable
+    @GuardedBy("mLock")
+    private RemoteSystemCaptionsManagerService mRemoteService;
+
+    SystemCaptionsManagerPerUserService(
+            @NonNull SystemCaptionsManagerService master,
+            @NonNull Object lock, boolean disabled, @UserIdInt int userId) {
+        super(master, lock, userId);
+    }
+
+    @Override
+    @NonNull
+    protected ServiceInfo newServiceInfoLocked(
+            @SuppressWarnings("unused") @NonNull ComponentName serviceComponent)
+            throws PackageManager.NameNotFoundException {
+        try {
+            return AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
+                    PackageManager.GET_META_DATA, mUserId);
+        } catch (RemoteException e) {
+            throw new PackageManager.NameNotFoundException(
+                    "Could not get service for " + serviceComponent);
+        }
+    }
+
+    @GuardedBy("mLock")
+    void initializeLocked() {
+        if (mMaster.verbose) {
+            Slog.v(TAG, "initialize()");
+        }
+
+        RemoteSystemCaptionsManagerService service = getRemoteServiceLocked();
+        if (service == null && mMaster.verbose) {
+            Slog.v(TAG, "initialize(): Failed to init remote server");
+        }
+    }
+
+    @GuardedBy("mLock")
+    void destroyLocked() {
+        if (mMaster.verbose) {
+            Slog.v(TAG, "destroyLocked()");
+        }
+
+        if (mRemoteService != null) {
+            mRemoteService.destroy();
+            mRemoteService = null;
+        }
+    }
+
+    @GuardedBy("mLock")
+    @Nullable
+    private RemoteSystemCaptionsManagerService getRemoteServiceLocked() {
+        if (mRemoteService == null) {
+            String serviceName = getComponentNameLocked();
+            if (serviceName == null) {
+                if (mMaster.verbose) {
+                    Slog.v(TAG, "getRemoteServiceLocked(): Not set");
+                }
+                return null;
+            }
+
+            ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
+            mRemoteService = new RemoteSystemCaptionsManagerService(
+                    getContext(),
+                    serviceComponent,
+                    mUserId,
+                    mMaster.verbose);
+            if (mMaster.verbose) {
+                Slog.v(TAG, "getRemoteServiceLocked(): initialize for user " + mUserId);
+            }
+            mRemoteService.initialize();
+        }
+
+        return mRemoteService;
+    }
+}
diff --git a/services/systemcaptions/java/com/android/server/systemcaptions/SystemCaptionsManagerService.java b/services/systemcaptions/java/com/android/server/systemcaptions/SystemCaptionsManagerService.java
new file mode 100644
index 0000000..27a116c
--- /dev/null
+++ b/services/systemcaptions/java/com/android/server/systemcaptions/SystemCaptionsManagerService.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.systemcaptions;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.Context;
+
+import com.android.server.infra.AbstractMasterSystemService;
+import com.android.server.infra.FrameworkResourcesServiceNameResolver;
+
+/** A system service to bind to a remote system captions manager service. */
+public final class SystemCaptionsManagerService extends
+        AbstractMasterSystemService<SystemCaptionsManagerService,
+                SystemCaptionsManagerPerUserService> {
+
+    public SystemCaptionsManagerService(@NonNull Context context) {
+        super(context,
+                new FrameworkResourcesServiceNameResolver(
+                        context,
+                        com.android.internal.R.string.config_defaultSystemCaptionsManagerService),
+                /*disallowProperty=*/ null,
+                /*packageUpdatePolicy=*/ PACKAGE_UPDATE_POLICY_REFRESH_EAGER);
+    }
+
+    @Override
+    public void onStart() {
+        // Do nothing. This service does not publish any local or system services.
+    }
+
+    @Override
+    protected SystemCaptionsManagerPerUserService newServiceLocked(
+            @UserIdInt int resolvedUserId, boolean disabled) {
+        SystemCaptionsManagerPerUserService perUserService =
+                new SystemCaptionsManagerPerUserService(this, mLock, disabled, resolvedUserId);
+        perUserService.initializeLocked();
+        return perUserService;
+    }
+
+    @Override
+    protected void onServiceRemoved(
+            SystemCaptionsManagerPerUserService service, @UserIdInt int userId) {
+        synchronized (mLock) {
+            service.destroyLocked();
+        }
+    }
+}
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index d2c0705..8c92e84 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -1045,6 +1045,7 @@
         mIsEmergencyOnly = false;
         mLteEarfcnRsrpBoost = 0;
         mNrFrequencyRange = FREQUENCY_RANGE_UNKNOWN;
+        mNetworkRegistrationInfos.clear();
         addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
                 .setDomain(NetworkRegistrationInfo.DOMAIN_CS)
                 .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 33bb4cc..b308982 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -661,8 +661,10 @@
             if (mIsEnabled) {
                 packages.retainAll(mSupportedPackages);
                 mRequestedPackages.addAll(packages);
+                mSupportedConsumer.accept(mSupportedPackages);
+            } else {
+                mSupportedConsumer.accept(Collections.emptyList());
             }
-            mSupportedConsumer.accept(mSupportedPackages);
         }
 
         public void setSupportedPackages(List<String> packages) {