Merge "Remove netlink-client dependency from net-module-wifi."
diff --git a/Android.bp b/Android.bp
index 1c4f628..003f0ad 100644
--- a/Android.bp
+++ b/Android.bp
@@ -359,6 +359,7 @@
         "core/java/com/android/internal/util/IState.java",
         "core/java/com/android/internal/util/State.java",
         "core/java/com/android/internal/util/StateMachine.java",
+        "core/java/com/android/internal/util/WakeupMessage.java",
         "services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java",
         "telephony/java/android/telephony/Annotation.java",
     ],
@@ -528,4 +529,5 @@
     "StubLibraries.bp",
     "ApiDocs.bp",
     "ProtoLibraries.bp",
+    "TestProtoLibraries.bp",
 ]
diff --git a/TestProtoLibraries.bp b/TestProtoLibraries.bp
new file mode 100644
index 0000000..513d45f
--- /dev/null
+++ b/TestProtoLibraries.bp
@@ -0,0 +1,35 @@
+// Copyright 2021 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.
+
+java_library_host {
+    name: "platformtestprotos",
+    srcs: [
+        ":libstats_atom_enum_protos",
+        ":libstats_internal_protos",
+        ":statsd_internal_protos",
+    ],
+    libs: [
+        "libprotobuf-java-full",
+    ],
+    proto: {
+        include_dirs: [
+            "external/protobuf/src",
+            "frameworks/proto_logging/stats",
+        ],
+        type: "full",
+    },
+    errorprone: {
+        javacflags: ["-Xep:MissingOverride:OFF"], // b/72714520
+    },
+}
diff --git a/core/api/current.txt b/core/api/current.txt
index 536c118..734d64f 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9094,6 +9094,7 @@
     field public static final int STATE_CONNECTING = 1; // 0x1
     field public static final int STATE_DISCONNECTED = 0; // 0x0
     field public static final int STATE_DISCONNECTING = 3; // 0x3
+    field public static final int VOLUME_CONTROL = 23; // 0x17
   }
 
   public static interface BluetoothProfile.ServiceListener {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index f5b40f5..400601f 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1666,6 +1666,16 @@
     field @NonNull public static final android.os.ParcelUuid VOLUME_CONTROL;
   }
 
+  public final class BluetoothVolumeControl implements java.lang.AutoCloseable android.bluetooth.BluetoothProfile {
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void close();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) protected void finalize();
+    method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void setVolume(@Nullable android.bluetooth.BluetoothDevice, @IntRange(from=0, to=255) int);
+    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.volume-control.profile.action.CONNECTION_STATE_CHANGED";
+  }
+
   public final class BufferConstraint implements android.os.Parcelable {
     ctor public BufferConstraint(int, int, int);
     method public int describeContents();
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 331fd07..e305aa8 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -2815,6 +2815,9 @@
                 return true;
             }
             return false;
+        } else if (profile == BluetoothProfile.VOLUME_CONTROL) {
+            BluetoothVolumeControl vcs = new BluetoothVolumeControl(context, listener, this);
+            return true;
         } else {
             return false;
         }
@@ -2899,6 +2902,11 @@
             case BluetoothProfile.HEARING_AID:
                 BluetoothHearingAid hearingAid = (BluetoothHearingAid) proxy;
                 hearingAid.close();
+                break;
+            case BluetoothProfile.VOLUME_CONTROL:
+                BluetoothVolumeControl vcs = (BluetoothVolumeControl) proxy;
+                vcs.close();
+                break;
         }
     }
 
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index 201d6c4..d979102 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -215,12 +215,18 @@
     int LE_AUDIO = 22;
 
     /**
+     * Volume Control profile
+     *
+     */
+    int VOLUME_CONTROL = 23;
+
+    /**
      * Max profile ID. This value should be updated whenever a new profile is added to match
      * the largest value assigned to a profile.
      *
      * @hide
      */
-    int MAX_PROFILE_ID = 22;
+    int MAX_PROFILE_ID = 23;
 
     /**
      * Default priority for devices that we try to auto-connect to and
diff --git a/core/java/android/bluetooth/BluetoothVolumeControl.java b/core/java/android/bluetooth/BluetoothVolumeControl.java
new file mode 100644
index 0000000..4eb28ad
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothVolumeControl.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright 2021 HIMSA II K/S - www.himsa.com.
+ * Represented by EHIMA - www.ehima.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import android.Manifest;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.CloseGuard;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class provides the public APIs to control the Bluetooth Volume Control service.
+ *
+ * <p>BluetoothVolumeControl is a proxy object for controlling the Bluetooth VC
+ * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
+ * the BluetoothVolumeControl proxy object.
+ * @hide
+ */
+@SystemApi
+public final class BluetoothVolumeControl implements BluetoothProfile, AutoCloseable {
+    private static final String TAG = "BluetoothVolumeControl";
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false;
+
+    private CloseGuard mCloseGuard;
+
+    /**
+     * Intent used to broadcast the change in connection state of the Volume Control
+     * profile.
+     *
+     * <p>This intent will have 3 extras:
+     * <ul>
+     * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
+     * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
+     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
+     * </ul>
+     *
+     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
+     * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
+     * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
+     *
+     * @hide
+     */
+    @SystemApi
+    @SuppressLint("ActionValue")
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_CONNECTION_STATE_CHANGED =
+            "android.bluetooth.volume-control.profile.action.CONNECTION_STATE_CHANGED";
+
+    private BluetoothAdapter mAdapter;
+    private final BluetoothProfileConnector<IBluetoothVolumeControl> mProfileConnector =
+            new BluetoothProfileConnector(this, BluetoothProfile.VOLUME_CONTROL, TAG,
+                    IBluetoothVolumeControl.class.getName()) {
+                @Override
+                public IBluetoothVolumeControl getServiceInterface(IBinder service) {
+                    return IBluetoothVolumeControl.Stub.asInterface(Binder.allowBlocking(service));
+                }
+            };
+
+    /**
+     * Create a BluetoothVolumeControl proxy object for interacting with the local
+     * Bluetooth Volume Control service.
+     */
+    /*package*/ BluetoothVolumeControl(Context context, ServiceListener listener,
+            BluetoothAdapter adapter) {
+        mAdapter = adapter;
+        mProfileConnector.connect(context, listener);
+        mCloseGuard = new CloseGuard();
+        mCloseGuard.open("close");
+    }
+
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    protected void finalize() {
+        if (mCloseGuard != null) {
+            mCloseGuard.warnIfOpen();
+        }
+        close();
+    }
+
+    /*package*/
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public void close() {
+        mProfileConnector.disconnect();
+    }
+
+    private IBluetoothVolumeControl getService() { return mProfileConnector.getService(); }
+
+    /**
+     * Get the list of connected devices. Currently at most one.
+     *
+     * @return list of connected devices
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public @NonNull List<BluetoothDevice> getConnectedDevices() {
+        if (DBG) log("getConnectedDevices()");
+        final IBluetoothVolumeControl service = getService();
+        if (service != null && isEnabled()) {
+            try {
+                return service.getConnectedDevices();
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return new ArrayList<BluetoothDevice>();
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * Get the list of devices matching specified states. Currently at most one.
+     *
+     * @return list of matching devices
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        if (DBG) log("getDevicesMatchingStates()");
+        final IBluetoothVolumeControl service = getService();
+        if (service != null && isEnabled()) {
+            try {
+                return service.getDevicesMatchingConnectionStates(states);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return new ArrayList<BluetoothDevice>();
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * Get connection state of device
+     *
+     * @return device connection state
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public int getConnectionState(BluetoothDevice device) {
+        if (DBG) log("getConnectionState(" + device + ")");
+        final IBluetoothVolumeControl service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.getConnectionState(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return BluetoothProfile.STATE_DISCONNECTED;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return BluetoothProfile.STATE_DISCONNECTED;
+    }
+
+    /**
+     * Tells remote device to set an absolute volume.
+     *
+     * @param volume Absolute volume to be set on remote device.
+     *               Minimum value is 0 and maximum value is 255
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public void setVolume(@Nullable BluetoothDevice device,
+            @IntRange(from = 0, to = 255) int volume) {
+        if (DBG)
+            log("setVolume(" + volume + ")");
+        final IBluetoothVolumeControl service = getService();
+        try {
+            if (service != null && isEnabled()) {
+                service.setVolume(device, volume);
+                return;
+            }
+            if (service == null)
+                Log.w(TAG, "Proxy not attached to service");
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+        }
+    }
+
+    /**
+     * Set priority of the profile
+     *
+     * <p> The device should already be paired.
+     * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF},
+     *
+     * @param device Paired bluetooth device
+     * @param priority
+     * @return true if priority is set, false on error
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setPriority(BluetoothDevice device, int priority) {
+        if (DBG) log("setPriority(" + device + ", " + priority + ")");
+        return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
+    }
+
+    /**
+     * Set connection policy of the profile
+     *
+     * <p> The device should already be paired.
+     * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
+     * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Paired bluetooth device
+     * @param connectionPolicy is the connection policy to set to for this profile
+     * @return true if connectionPolicy is set, false on error
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
+            @ConnectionPolicy int connectionPolicy) {
+        if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
+        final IBluetoothVolumeControl service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                    && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+                return false;
+            }
+            try {
+                return service.setConnectionPolicy(device, connectionPolicy);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Get the priority of the profile.
+     *
+     * <p> The priority can be any of:
+     * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
+     *
+     * @param device Bluetooth device
+     * @return priority of the device
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public int getPriority(BluetoothDevice device) {
+        if (VDBG) log("getPriority(" + device + ")");
+        return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
+    }
+
+    /**
+     * Get the connection policy of the profile.
+     *
+     * <p> The connection policy can be any of:
+     * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN},
+     * {@link #CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Bluetooth device
+     * @return connection policy of the device
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
+        if (VDBG) log("getConnectionPolicy(" + device + ")");
+        final IBluetoothVolumeControl service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.getConnectionPolicy(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+    }
+
+    private boolean isEnabled() {
+        return mAdapter.getState() == BluetoothAdapter.STATE_ON;
+    }
+
+    private static boolean isValidDevice(@Nullable BluetoothDevice device) {
+        return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
+    }
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+}
diff --git a/core/java/android/bluetooth/OobData.java b/core/java/android/bluetooth/OobData.java
index 4e5ede7..bb0b956 100644
--- a/core/java/android/bluetooth/OobData.java
+++ b/core/java/android/bluetooth/OobData.java
@@ -937,17 +937,18 @@
     }
 
     @NonNull
-    private String toHexString(@NonNull int b) {
+    private String toHexString(int b) {
         return toHexString(new byte[] {(byte) b});
     }
 
     @NonNull
-    private String toHexString(@NonNull byte b) {
+    private String toHexString(byte b) {
         return toHexString(new byte[] {b});
     }
 
     @NonNull
-    private String toHexString(@NonNull byte[] array) {
+    private String toHexString(byte[] array) {
+        if (array == null) return "null";
         StringBuilder builder = new StringBuilder(array.length * 2);
         for (byte b: array) {
             builder.append(String.format("%02x", b));
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
index d59ad6f..86cd23d 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -83,7 +83,12 @@
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     static final int MIN_MTU_V6 = 1280;
 
-    private static final Set<Integer> ALLOWED_CAPABILITIES;
+    /**
+     * The set of allowed capabilities for exposed capabilities.
+     *
+     * @hide
+     */
+    public static final Set<Integer> ALLOWED_CAPABILITIES;
 
     static {
         Set<Integer> allowedCaps = new ArraySet<>();
diff --git a/core/java/android/view/accessibility/AccessibilityCache.java b/core/java/android/view/accessibility/AccessibilityCache.java
index 9ab2c2b..d14dc6e 100644
--- a/core/java/android/view/accessibility/AccessibilityCache.java
+++ b/core/java/android/view/accessibility/AccessibilityCache.java
@@ -158,6 +158,7 @@
      * @param event An event.
      */
     public void onAccessibilityEvent(AccessibilityEvent event) {
+        AccessibilityNodeInfo nodeToRefresh = null;
         synchronized (mLock) {
             if (DEBUG) {
                 Log.i(LOG_TAG, "onAccessibilityEvent(" + event + ")");
@@ -166,17 +167,19 @@
             switch (eventType) {
                 case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: {
                     if (mAccessibilityFocus != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
-                        refreshCachedNodeLocked(mAccessibilityFocusedWindow, mAccessibilityFocus);
+                        removeCachedNodeLocked(mAccessibilityFocusedWindow, mAccessibilityFocus);
                     }
                     mAccessibilityFocus = event.getSourceNodeId();
                     mAccessibilityFocusedWindow = event.getWindowId();
-                    refreshCachedNodeLocked(mAccessibilityFocusedWindow, mAccessibilityFocus);
+                    nodeToRefresh = removeCachedNodeLocked(mAccessibilityFocusedWindow,
+                            mAccessibilityFocus);
                 } break;
 
                 case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: {
                     if (mAccessibilityFocus == event.getSourceNodeId()
                             && mAccessibilityFocusedWindow == event.getWindowId()) {
-                        refreshCachedNodeLocked(mAccessibilityFocusedWindow, mAccessibilityFocus);
+                        nodeToRefresh = removeCachedNodeLocked(mAccessibilityFocusedWindow,
+                                mAccessibilityFocus);
                         mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
                         mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
                     }
@@ -184,17 +187,18 @@
 
                 case AccessibilityEvent.TYPE_VIEW_FOCUSED: {
                     if (mInputFocus != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
-                        refreshCachedNodeLocked(event.getWindowId(), mInputFocus);
+                        removeCachedNodeLocked(event.getWindowId(), mInputFocus);
                     }
                     mInputFocus = event.getSourceNodeId();
-                    refreshCachedNodeLocked(event.getWindowId(), mInputFocus);
+                    nodeToRefresh = removeCachedNodeLocked(event.getWindowId(), mInputFocus);
                 } break;
 
                 case AccessibilityEvent.TYPE_VIEW_SELECTED:
                 case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:
                 case AccessibilityEvent.TYPE_VIEW_CLICKED:
                 case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED: {
-                    refreshCachedNodeLocked(event.getWindowId(), event.getSourceNodeId());
+                    nodeToRefresh = removeCachedNodeLocked(event.getWindowId(),
+                            event.getSourceNodeId());
                 } break;
 
                 case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: {
@@ -205,7 +209,7 @@
                                 & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE) != 0) {
                             clearSubTreeLocked(windowId, sourceId);
                         } else {
-                            refreshCachedNodeLocked(windowId, sourceId);
+                            nodeToRefresh = removeCachedNodeLocked(windowId, sourceId);
                         }
                     }
                 } break;
@@ -218,8 +222,8 @@
                     if (event.getWindowChanges()
                             == AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED) {
                         // Don't need to clear all cache. Unless the changes are related to
-                        // content, we won't clear all cache here.
-                        refreshCachedWindowLocked(event.getWindowId());
+                        // content, we won't clear all cache here with clear().
+                        clearWindowCacheLocked();
                         break;
                     }
                 case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: {
@@ -228,59 +232,34 @@
             }
         }
 
+        if (nodeToRefresh != null) {
+            if (DEBUG) {
+                Log.i(LOG_TAG, "Refreshing and re-adding cached node.");
+            }
+            if (mAccessibilityNodeRefresher.refreshNode(nodeToRefresh, true)) {
+                add(nodeToRefresh);
+            }
+        }
         if (CHECK_INTEGRITY) {
             checkIntegrity();
         }
     }
 
-    private void refreshCachedNodeLocked(int windowId, long sourceId) {
+    private AccessibilityNodeInfo removeCachedNodeLocked(int windowId, long sourceId) {
         if (DEBUG) {
-            Log.i(LOG_TAG, "Refreshing cached node.");
+            Log.i(LOG_TAG, "Removing cached node.");
         }
-
         LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId);
         if (nodes == null) {
-            return;
+            return null;
         }
         AccessibilityNodeInfo cachedInfo = nodes.get(sourceId);
         // If the source is not in the cache - nothing to do.
         if (cachedInfo == null) {
-            return;
+            return null;
         }
-        // The node changed so we will just refresh it right now.
-        if (mAccessibilityNodeRefresher.refreshNode(cachedInfo, true)) {
-            return;
-        }
-        // Weird, we could not refresh. Just evict the entire sub-tree.
-        clearSubTreeLocked(windowId, sourceId);
-    }
-
-    private void refreshCachedWindowLocked(int windowId) {
-        if (DEBUG) {
-            Log.i(LOG_TAG, "Refreshing cached window.");
-        }
-
-        if (windowId == AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) {
-            return;
-        }
-
-        final int displayCounts = mWindowCacheByDisplay.size();
-        for (int i = 0; i < displayCounts; i++) {
-            final SparseArray<AccessibilityWindowInfo> windowsOfDisplay =
-                    mWindowCacheByDisplay.valueAt(i);
-            if (windowsOfDisplay == null) {
-                continue;
-            }
-            final AccessibilityWindowInfo window = windowsOfDisplay.get(windowId);
-            if (window == null) {
-                continue;
-            }
-            if (!mAccessibilityNodeRefresher.refreshWindow(window)) {
-                // If we fail to refresh the window, clear all windows.
-                clearWindowCacheLocked();
-            }
-            return;
-        }
+        nodes.remove(sourceId);
+        return cachedInfo;
     }
 
     /**
@@ -450,7 +429,7 @@
             if (clone.isAccessibilityFocused()) {
                 if (mAccessibilityFocus != AccessibilityNodeInfo.UNDEFINED_ITEM_ID
                         && mAccessibilityFocus != sourceId) {
-                    refreshCachedNodeLocked(windowId, mAccessibilityFocus);
+                    removeCachedNodeLocked(windowId, mAccessibilityFocus);
                 }
                 mAccessibilityFocus = sourceId;
                 mAccessibilityFocusedWindow = windowId;
diff --git a/core/proto/android/server/OWNERS b/core/proto/android/server/OWNERS
new file mode 100644
index 0000000..72d39bf
--- /dev/null
+++ b/core/proto/android/server/OWNERS
@@ -0,0 +1 @@
+per-file window*.proto = file:/services/core/java/com/android/server/wm/OWNERS
diff --git a/core/proto/android/server/inputmethod/OWNERS b/core/proto/android/server/inputmethod/OWNERS
new file mode 100644
index 0000000..5deb2ce
--- /dev/null
+++ b/core/proto/android/server/inputmethod/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/view/inputmethod/OWNERS
diff --git a/core/proto/android/view/OWNERS b/core/proto/android/view/OWNERS
new file mode 100644
index 0000000..d72a0f0
--- /dev/null
+++ b/core/proto/android/view/OWNERS
@@ -0,0 +1,3 @@
+include /services/core/java/com/android/server/wm/OWNERS
+
+per-file ime*.proto = file:/core/java/android/view/inputmethod/OWNERS
diff --git a/core/proto/android/view/inputmethod/OWNERS b/core/proto/android/view/inputmethod/OWNERS
new file mode 100644
index 0000000..5deb2ce
--- /dev/null
+++ b/core/proto/android/view/inputmethod/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/view/inputmethod/OWNERS
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 8db1bbf..33f2598 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -191,6 +191,8 @@
     <protected-broadcast
         android:name="android.bluetooth.hearingaid.profile.action.ACTIVE_DEVICE_CHANGED" />
     <protected-broadcast
+        android:name="android.bluetooth.volume-control.profile.action.CONNECTION_STATE_CHANGED" />
+    <protected-broadcast
         android:name="android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast
         android:name="android.bluetooth.a2dp.profile.action.ACTIVE_DEVICE_CHANGED" />
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
index 0d5db6d..d2b52ba 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
@@ -563,7 +563,7 @@
     }
 
     @Test
-    public void nodeWithA11yFocusWhenAnotherNodeGetsFocus_getsRefreshed() {
+    public void nodeWithA11yFocusWhenAnotherNodeGetsFocus_getsRemoved() {
         AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
         nodeInfo.setAccessibilityFocused(true);
         mAccessibilityCache.add(nodeInfo);
@@ -573,7 +573,7 @@
         mAccessibilityCache.onAccessibilityEvent(event);
         event.recycle();
         try {
-            verify(mAccessibilityNodeRefresher).refreshNode(nodeInfo, true);
+            assertNull(mAccessibilityCache.getNode(WINDOW_ID_1, SINGLE_VIEW_ID));
         } finally {
             nodeInfo.recycle();
         }
@@ -614,7 +614,7 @@
     }
 
     @Test
-    public void nodeWithInputFocusWhenAnotherNodeGetsFocus_getsRefreshed() {
+    public void nodeWithInputFocusWhenAnotherNodeGetsFocus_getsRemoved() {
         AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
         nodeInfo.setFocused(true);
         mAccessibilityCache.add(nodeInfo);
@@ -624,7 +624,7 @@
         mAccessibilityCache.onAccessibilityEvent(event);
         event.recycle();
         try {
-            verify(mAccessibilityNodeRefresher).refreshNode(nodeInfo, true);
+            assertNull(mAccessibilityCache.getNode(WINDOW_ID_1, SINGLE_VIEW_ID));
         } finally {
             nodeInfo.recycle();
         }
@@ -733,20 +733,15 @@
     }
 
     @Test
-    public void addA11yFocusNodeBeforeFocusClearedEvent_previousA11yFocusNodeGetsRefreshed() {
+    public void addA11yFocusNodeBeforeFocusClearedEvent_previousA11yFocusNodeGetsRemoved() {
         AccessibilityNodeInfo nodeInfo1 = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
         nodeInfo1.setAccessibilityFocused(true);
         mAccessibilityCache.add(nodeInfo1);
         AccessibilityNodeInfo nodeInfo2 = getNodeWithA11yAndWindowId(OTHER_VIEW_ID, WINDOW_ID_1);
         nodeInfo2.setAccessibilityFocused(true);
         mAccessibilityCache.add(nodeInfo2);
-        AccessibilityEvent event = AccessibilityEvent.obtain(
-                AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
-        event.setSource(getMockViewWithA11yAndWindowIds(SINGLE_VIEW_ID, WINDOW_ID_1));
-        mAccessibilityCache.onAccessibilityEvent(event);
-        event.recycle();
         try {
-            verify(mAccessibilityNodeRefresher).refreshNode(nodeInfo1, true);
+            assertNull(mAccessibilityCache.getNode(WINDOW_ID_1, SINGLE_VIEW_ID));
         } finally {
             nodeInfo1.recycle();
             nodeInfo2.recycle();
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 1718052..70b0fc1 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -372,8 +372,7 @@
 
     /** Notifies the VcnManagementService that external dependencies can be set up. */
     public void systemReady() {
-        mContext.getSystemService(ConnectivityManager.class)
-                .registerNetworkProvider(mNetworkProvider);
+        mNetworkProvider.register();
         mContext.getSystemService(ConnectivityManager.class)
                 .registerNetworkCallback(
                         new NetworkRequest.Builder().clearCapabilities().build(),
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 5ac86ca..f9dd826 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -75,6 +75,17 @@
     /** Indicates that dexopt may be run with different performance / priority tuned for restore */
     public static final int DEXOPT_FOR_RESTORE = 1 << 13; // TODO(b/135202722): remove
 
+    /** The result of the profile analysis indicating that the app should be optimized. */
+    public static final int PROFILE_ANALYSIS_OPTIMIZE = 1;
+    /** The result of the profile analysis indicating that the app should not be optimized. */
+    public static final int PROFILE_ANALYSIS_DONT_OPTIMIZE_SMALL_DELTA = 2;
+    /**
+     * The result of the profile analysis indicating that the app should not be optimized because
+     * the profiles are empty.
+     */
+    public static final int PROFILE_ANALYSIS_DONT_OPTIMIZE_EMPTY_PROFILES = 3;
+
+
     public static final int FLAG_STORAGE_DE = IInstalld.FLAG_STORAGE_DE;
     public static final int FLAG_STORAGE_CE = IInstalld.FLAG_STORAGE_CE;
     public static final int FLAG_STORAGE_EXTERNAL = IInstalld.FLAG_STORAGE_EXTERNAL;
@@ -388,9 +399,18 @@
         }
     }
 
-    public boolean mergeProfiles(int uid, String packageName, String profileName)
+    /**
+     * Analyzes the ART profiles of the given package, possibly merging the information
+     * into the reference profile. Returns whether or not we should optimize the package
+     * based on how much information is in the profile.
+     *
+     * @return one of {@link #PROFILE_ANALYSIS_OPTIMIZE},
+     *         {@link #PROFILE_ANALYSIS_DONT_OPTIMIZE_SMALL_DELTA},
+     *         {@link #PROFILE_ANALYSIS_DONT_OPTIMIZE_EMPTY_PROFILES}
+     */
+    public int mergeProfiles(int uid, String packageName, String profileName)
             throws InstallerException {
-        if (!checkBeforeRemote()) return false;
+        if (!checkBeforeRemote()) return PROFILE_ANALYSIS_DONT_OPTIMIZE_SMALL_DELTA;
         try {
             return mInstalld.mergeProfiles(uid, packageName, profileName);
         } catch (Exception e) {
@@ -537,13 +557,17 @@
         }
     }
 
-    public void deleteOdex(String apkPath, String instructionSet, String outputPath)
+    /**
+     * Deletes the optimized artifacts generated by ART and returns the number
+     * of freed bytes.
+     */
+    public long deleteOdex(String apkPath, String instructionSet, String outputPath)
             throws InstallerException {
-        if (!checkBeforeRemote()) return;
+        if (!checkBeforeRemote()) return -1;
         BlockGuard.getVmPolicy().onPathAccess(apkPath);
         BlockGuard.getVmPolicy().onPathAccess(outputPath);
         try {
-            mInstalld.deleteOdex(apkPath, instructionSet, outputPath);
+            return mInstalld.deleteOdex(apkPath, instructionSet, outputPath);
         } catch (Exception e) {
             throw InstallerException.from(e);
         }
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 5ef3d76..50be856 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -31,6 +31,9 @@
 import static com.android.server.pm.Installer.DEXOPT_SECONDARY_DEX;
 import static com.android.server.pm.Installer.DEXOPT_STORAGE_CE;
 import static com.android.server.pm.Installer.DEXOPT_STORAGE_DE;
+import static com.android.server.pm.Installer.PROFILE_ANALYSIS_DONT_OPTIMIZE_EMPTY_PROFILES;
+import static com.android.server.pm.Installer.PROFILE_ANALYSIS_DONT_OPTIMIZE_SMALL_DELTA;
+import static com.android.server.pm.Installer.PROFILE_ANALYSIS_OPTIMIZE;
 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
 import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
@@ -248,8 +251,12 @@
                     || packageUseInfo.isUsedByOtherApps(path);
             final String compilerFilter = getRealCompilerFilter(pkg,
                 options.getCompilerFilter(), isUsedByOtherApps);
-            final boolean profileUpdated = options.isCheckForProfileUpdates() &&
-                isProfileUpdated(pkg, sharedGid, profileName, compilerFilter);
+            // If we don't have to check for profiles updates assume
+            // PROFILE_ANALYSIS_DONT_OPTIMIZE_SMALL_DELTA which will be a no-op with respect to
+            // profiles.
+            final int profileAnalysisResult = options.isCheckForProfileUpdates()
+                    ? analyseProfiles(pkg, sharedGid, profileName, compilerFilter)
+                    : PROFILE_ANALYSIS_DONT_OPTIMIZE_SMALL_DELTA;
 
             // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct
             // flags.
@@ -257,7 +264,7 @@
 
             for (String dexCodeIsa : dexCodeInstructionSets) {
                 int newResult = dexOptPath(pkg, pkgSetting, path, dexCodeIsa, compilerFilter,
-                        profileUpdated, classLoaderContexts[i], dexoptFlags, sharedGid,
+                        profileAnalysisResult, classLoaderContexts[i], dexoptFlags, sharedGid,
                         packageStats, options.isDowngrade(), profileName, dexMetadataPath,
                         options.getCompilationReason());
 
@@ -305,11 +312,11 @@
      */
     @GuardedBy("mInstallLock")
     private int dexOptPath(AndroidPackage pkg, @NonNull PackageSetting pkgSetting, String path,
-            String isa, String compilerFilter, boolean profileUpdated, String classLoaderContext,
+            String isa, String compilerFilter, int profileAnalysisResult, String classLoaderContext,
             int dexoptFlags, int uid, CompilerStats.PackageStats packageStats, boolean downgrade,
             String profileName, String dexMetadataPath, int compilationReason) {
         int dexoptNeeded = getDexoptNeeded(path, isa, compilerFilter, classLoaderContext,
-                profileUpdated, downgrade);
+                profileAnalysisResult, downgrade);
         if (Math.abs(dexoptNeeded) == DexFile.NO_DEXOPT_NEEDED) {
             return DEX_OPT_SKIPPED;
         }
@@ -363,7 +370,7 @@
                     isa,
                     options.getCompilerFilter(),
                     dexUseInfo.getClassLoaderContext(),
-                    /* newProfile= */false,
+                    PROFILE_ANALYSIS_DONT_OPTIMIZE_EMPTY_PROFILES,
                     /* downgrade= */ false);
 
             if (dexoptNeeded == DexFile.NO_DEXOPT_NEEDED) {
@@ -749,11 +756,25 @@
      * configuration (isa, compiler filter, profile).
      */
     private int getDexoptNeeded(String path, String isa, String compilerFilter,
-            String classLoaderContext, boolean newProfile, boolean downgrade) {
+            String classLoaderContext, int profileAnalysisResult, boolean downgrade) {
         int dexoptNeeded;
         try {
-            dexoptNeeded = DexFile.getDexOptNeeded(path, isa, compilerFilter, classLoaderContext,
-                    newProfile, downgrade);
+            // A profile guided optimizations with an empty profile is essentially 'verify' and
+            // dex2oat already makes this transformation. However DexFile.getDexOptNeeded() cannot
+            // check the profiles because system server does not have access to them.
+            // As such, we rely on the previous profile analysis (done with dexoptanalyzer) and
+            // manually adjust the actual filter before checking.
+            //
+            // TODO: ideally. we'd move this check in dexoptanalyzer, but that's a large change,
+            // and in the interim we can still improve things here.
+            String actualCompilerFilter = compilerFilter;
+            if (compilerFilterDependsOnProfiles(compilerFilter)
+                    && profileAnalysisResult == PROFILE_ANALYSIS_DONT_OPTIMIZE_EMPTY_PROFILES) {
+                actualCompilerFilter = "verify";
+            }
+            boolean newProfile = profileAnalysisResult == PROFILE_ANALYSIS_OPTIMIZE;
+            dexoptNeeded = DexFile.getDexOptNeeded(path, isa, actualCompilerFilter,
+                    classLoaderContext, newProfile, downgrade);
         } catch (IOException ioe) {
             Slog.w(TAG, "IOException reading apk: " + path, ioe);
             return DEX_OPT_FAILED;
@@ -764,27 +785,34 @@
         return adjustDexoptNeeded(dexoptNeeded);
     }
 
+    /** Returns true if the compiler filter depends on profiles (e.g speed-profile). */
+    private boolean compilerFilterDependsOnProfiles(String compilerFilter) {
+        return compilerFilter.endsWith("-profile");
+    }
+
     /**
      * Checks if there is an update on the profile information of the {@code pkg}.
-     * If the compiler filter is not profile guided the method returns false.
+     * If the compiler filter is not profile guided the method returns a safe default:
+     * PROFILE_ANALYSIS_DONT_OPTIMIZE_SMALL_DELTA.
      *
      * Note that this is a "destructive" operation with side effects. Under the hood the
      * current profile and the reference profile will be merged and subsequent calls
      * may return a different result.
      */
-    private boolean isProfileUpdated(AndroidPackage pkg, int uid, String profileName,
+    private int analyseProfiles(AndroidPackage pkg, int uid, String profileName,
             String compilerFilter) {
         // Check if we are allowed to merge and if the compiler filter is profile guided.
         if (!isProfileGuidedCompilerFilter(compilerFilter)) {
-            return false;
+            return PROFILE_ANALYSIS_DONT_OPTIMIZE_SMALL_DELTA;
         }
         // Merge profiles. It returns whether or not there was an updated in the profile info.
         try {
             return mInstaller.mergeProfiles(uid, pkg.getPackageName(), profileName);
         } catch (InstallerException e) {
             Slog.w(TAG, "Failed to merge profiles", e);
+            // We don't need to optimize if we failed to merge.
+            return PROFILE_ANALYSIS_DONT_OPTIMIZE_SMALL_DELTA;
         }
-        return false;
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 05ba9da..a3df212 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -25555,14 +25555,14 @@
         }
     }
 
-    void deleteOatArtifactsOfPackage(String packageName) {
+    long deleteOatArtifactsOfPackage(String packageName) {
         final AndroidPackage pkg;
         final PackageSetting pkgSetting;
         synchronized (mLock) {
             pkg = mPackages.get(packageName);
             pkgSetting = mSettings.getPackageLPr(packageName);
         }
-        mDexManager.deleteOptimizedFiles(ArtUtils.createArtPackageInfo(pkg, pkgSetting));
+        return mDexManager.deleteOptimizedFiles(ArtUtils.createArtPackageInfo(pkg, pkgSetting));
     }
 
     Set<String> getUnusedPackages(long downgradeTimeThresholdMillis) {
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index c5fbfba..b72da23 100644
--- a/services/core/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -79,7 +79,7 @@
 
     /**
      * Allows opt-in to the latest targetSdkVersion enforced changes without changing target SDK.
-     * Turning this change off for an app targeting the latest SDK is a no-op.
+     * Turning this change off for an app targeting >= the latest SDK is a no-op.
      *
      * <p>Has no effect for apps using shared user id.
      *
@@ -92,7 +92,7 @@
     /**
      * This change gates apps access to untrusted_app_R-targetSDK SELinux domain. Allows opt-in
      * to R targetSdkVersion enforced changes without changing target SDK. Turning this change
-     * off for an app targeting S is a no-op.
+     * off for an app targeting >= S is a no-op.
      *
      * <p>Has no effect for apps using shared user id.
      *
@@ -364,7 +364,7 @@
         }
         final ApplicationInfo appInfo = pkg.toAppInfoWithoutState();
         if (compatibility.isChangeEnabledInternal(SELINUX_LATEST_CHANGES, appInfo)) {
-            return android.os.Build.VERSION_CODES.S;
+            return Math.max(android.os.Build.VERSION_CODES.S, pkg.getTargetSdkVersion());
         } else if (compatibility.isChangeEnabledInternal(SELINUX_R_CHANGES, appInfo)) {
             return Math.max(android.os.Build.VERSION_CODES.R, pkg.getTargetSdkVersion());
         }
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 32ba26c..5820489 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -1034,18 +1034,26 @@
 
     /**
      * Deletes all the optimizations files generated by ART.
+     * This is best effort, and the method will log but not throw errors
+     * for individual deletes
+     *
      * @param packageInfo the package information.
+     * @return the number of freed bytes or -1 if there was an error in the process.
      */
-    public void deleteOptimizedFiles(ArtPackageInfo packageInfo) {
+    public long deleteOptimizedFiles(ArtPackageInfo packageInfo) {
+        long freedBytes = 0;
+        boolean hadErrors = false;
         for (String codePath : packageInfo.getCodePaths()) {
             for (String isa : packageInfo.getInstructionSets()) {
                 try {
-                    mInstaller.deleteOdex(codePath, isa, packageInfo.getOatDir());
+                    freedBytes += mInstaller.deleteOdex(codePath, isa, packageInfo.getOatDir());
                 } catch (InstallerException e) {
                     Log.e(TAG, "Failed deleting oat files for " + codePath, e);
+                    hadErrors = true;
                 }
             }
         }
+        return hadErrors ? -1 : freedBytes;
     }
 
     public static class RegisterDexModuleResult {
diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java
index f7d6136..95a06fcf 100644
--- a/services/core/java/com/android/server/vcn/Vcn.java
+++ b/services/core/java/com/android/server/vcn/Vcn.java
@@ -453,6 +453,10 @@
         for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) {
             gatewayConnection.updateSubscriptionSnapshot(mLastSnapshot);
         }
+
+        // Update the mobile data state after updating the subscription snapshot as a change in
+        // subIds for a subGroup may affect the mobile data state.
+        handleMobileDataToggled();
     }
 
     private void handleMobileDataToggled() {
diff --git a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java
index ebabe2a..31ee247 100644
--- a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java
+++ b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java
@@ -16,12 +16,24 @@
 
 package com.android.server.vcn;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+
 import static com.android.server.VcnManagementService.VDBG;
 
 import android.annotation.NonNull;
 import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkCapabilities;
 import android.net.NetworkProvider;
 import android.net.NetworkRequest;
+import android.net.NetworkScore;
+import android.net.vcn.VcnGatewayConnectionConfig;
+import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.Looper;
 import android.util.ArraySet;
 import android.util.Slog;
@@ -30,7 +42,9 @@
 import com.android.internal.annotations.VisibleForTesting.Visibility;
 import com.android.internal.util.IndentingPrintWriter;
 
+import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.Executor;
 
 /**
  * VCN Network Provider routes NetworkRequests to listeners to bring up tunnels as needed.
@@ -45,6 +59,10 @@
 
     private final Set<NetworkRequestListener> mListeners = new ArraySet<>();
 
+    private final Context mContext;
+    private final Handler mHandler;
+    private final Dependencies mDeps;
+
     /**
      * Cache of NetworkRequest(s).
      *
@@ -52,8 +70,59 @@
      */
     private final Set<NetworkRequest> mRequests = new ArraySet<>();
 
-    public VcnNetworkProvider(Context context, Looper looper) {
-        super(context, looper, VcnNetworkProvider.class.getSimpleName());
+    public VcnNetworkProvider(@NonNull Context context, @NonNull Looper looper) {
+        this(context, looper, new Dependencies());
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public VcnNetworkProvider(
+            @NonNull Context context, @NonNull Looper looper, @NonNull Dependencies dependencies) {
+        super(
+                Objects.requireNonNull(context, "Missing context"),
+                Objects.requireNonNull(looper, "Missing looper"),
+                TAG);
+
+        mContext = context;
+        mHandler = new Handler(looper);
+        mDeps = Objects.requireNonNull(dependencies, "Missing dependencies");
+    }
+
+    /** Registers this VcnNetworkProvider and a generic network offer with ConnectivityService. */
+    public void register() {
+        mContext.getSystemService(ConnectivityManager.class).registerNetworkProvider(this);
+        mDeps.registerNetworkOffer(
+                this,
+                Vcn.getNetworkScore(), // score filter
+                buildCapabilityFilter(),
+                new HandlerExecutor(mHandler),
+                new NetworkOfferCallback() {
+                    @Override
+                    public void onNetworkNeeded(@NonNull NetworkRequest request) {
+                        handleNetworkRequested(request);
+                    }
+
+                    @Override
+                    public void onNetworkUnneeded(@NonNull NetworkRequest request) {
+                        handleNetworkRequestWithdrawn(request);
+                    }
+                });
+    }
+
+    /** Builds the filter for NetworkRequests that can be served by the VcnNetworkProvider. */
+    private NetworkCapabilities buildCapabilityFilter() {
+        final NetworkCapabilities.Builder builder =
+                new NetworkCapabilities.Builder()
+                        .addTransportType(TRANSPORT_CELLULAR)
+                        .addCapability(NET_CAPABILITY_TRUSTED)
+                        .addCapability(NET_CAPABILITY_NOT_RESTRICTED)
+                        .addCapability(NET_CAPABILITY_NOT_VPN)
+                        .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
+
+        for (int cap : VcnGatewayConnectionConfig.ALLOWED_CAPABILITIES) {
+            builder.addCapability(cap);
+        }
+
+        return builder.build();
     }
 
     /**
@@ -88,8 +157,7 @@
         listener.onNetworkRequested(request);
     }
 
-    @Override
-    public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) {
+    private void handleNetworkRequested(@NonNull NetworkRequest request) {
         if (VDBG) {
             Slog.v(TAG, "Network requested: Request = " + request);
         }
@@ -103,8 +171,7 @@
         }
     }
 
-    @Override
-    public void onNetworkRequestWithdrawn(@NonNull NetworkRequest request) {
+    private void handleNetworkRequestWithdrawn(@NonNull NetworkRequest request) {
         if (VDBG) {
             Slog.v(TAG, "Network request withdrawn: Request = " + request);
         }
@@ -144,4 +211,18 @@
 
         pw.decreaseIndent();
     }
+
+    /** Proxy class for dependencies used for testing. */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class Dependencies {
+        /** Registers a given network offer for the given provider. */
+        public void registerNetworkOffer(
+                @NonNull VcnNetworkProvider provider,
+                @NonNull NetworkScore score,
+                @NonNull NetworkCapabilities capabilitiesFilter,
+                @NonNull Executor executor,
+                @NonNull NetworkOfferCallback callback) {
+            provider.registerNetworkOffer(score, capabilitiesFilter, executor, callback);
+        }
+    }
 }
diff --git a/services/net/java/android/net/ip/IpClientManager.java b/services/net/java/android/net/ip/IpClientManager.java
index 274b6dc..b45405f 100644
--- a/services/net/java/android/net/ip/IpClientManager.java
+++ b/services/net/java/android/net/ip/IpClientManager.java
@@ -88,6 +88,8 @@
         } catch (RemoteException e) {
             log("Error confirming IpClient configuration", e);
             return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
         }
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java b/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java
index f1930d7..cee4cda 100644
--- a/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java
@@ -91,6 +91,16 @@
     }
 
     @Test
+    public void getSeInfoTargetingCurDevelopment() {
+        AndroidPackage pkg = makePackage(Build.VERSION_CODES.CUR_DEVELOPMENT);
+        when(mMockCompatibility.isChangeEnabledInternal(eq(SELinuxMMAC.SELINUX_LATEST_CHANGES),
+                argThat(argument -> argument.packageName.equals(pkg.getPackageName()))))
+                .thenReturn(true);
+        assertThat(SELinuxMMAC.getSeInfo(pkg, null, mMockCompatibility),
+                is("default:targetSdkVersion=" + Build.VERSION_CODES.CUR_DEVELOPMENT));
+    }
+
+    @Test
     public void getSeInfoNoOptInButAlreadyR() {
         AndroidPackage pkg = makePackage(R_OPT_IN_VERSION);
         when(mMockCompatibility.isChangeEnabledInternal(eq(SELinuxMMAC.SELINUX_R_CHANGES),
diff --git a/tests/vcn/java/com/android/server/vcn/VcnNetworkProviderTest.java b/tests/vcn/java/com/android/server/vcn/VcnNetworkProviderTest.java
index 79cf746..72db55b 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnNetworkProviderTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnNetworkProviderTest.java
@@ -16,12 +16,18 @@
 
 package com.android.server.vcn;
 
+import static android.net.NetworkProvider.NetworkOfferCallback;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 
 import android.annotation.NonNull;
 import android.content.Context;
+import android.net.ConnectivityManager;
 import android.net.NetworkRequest;
 import android.os.test.TestLooper;
 
@@ -33,6 +39,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -47,6 +54,8 @@
     @NonNull private final Context mContext;
     @NonNull private final TestLooper mTestLooper;
 
+    @NonNull private VcnNetworkProvider.Dependencies mDeps;
+    @NonNull private ConnectivityManager mConnMgr;
     @NonNull private VcnNetworkProvider mVcnNetworkProvider;
     @NonNull private NetworkRequestListener mListener;
 
@@ -57,16 +66,47 @@
 
     @Before
     public void setUp() throws Exception {
-        mVcnNetworkProvider = new VcnNetworkProvider(mContext, mTestLooper.getLooper());
+        mDeps = mock(VcnNetworkProvider.Dependencies.class);
+        mConnMgr = mock(ConnectivityManager.class);
+        VcnTestUtils.setupSystemService(
+                mContext, mConnMgr, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class);
+
+        mVcnNetworkProvider = new VcnNetworkProvider(mContext, mTestLooper.getLooper(), mDeps);
         mListener = mock(NetworkRequestListener.class);
     }
 
+    private NetworkOfferCallback verifyRegisterAndGetOfferCallback() throws Exception {
+        mVcnNetworkProvider.register();
+
+        final ArgumentCaptor<NetworkOfferCallback> cbCaptor =
+                ArgumentCaptor.forClass(NetworkOfferCallback.class);
+
+        verify(mConnMgr).registerNetworkProvider(eq(mVcnNetworkProvider));
+        verify(mDeps)
+                .registerNetworkOffer(
+                        eq(mVcnNetworkProvider),
+                        argThat(
+                                score ->
+                                        score.getLegacyInt()
+                                                == Vcn.getNetworkScore().getLegacyInt()),
+                        any(),
+                        any(),
+                        cbCaptor.capture());
+
+        return cbCaptor.getValue();
+    }
+
+    @Test
+    public void testRegister() throws Exception {
+        verifyRegisterAndGetOfferCallback();
+    }
+
     @Test
     public void testRequestsPassedToRegisteredListeners() throws Exception {
         mVcnNetworkProvider.registerListener(mListener);
 
         final NetworkRequest request = mock(NetworkRequest.class);
-        mVcnNetworkProvider.onNetworkRequested(request, TEST_SCORE_UNSATISFIED, TEST_PROVIDER_ID);
+        verifyRegisterAndGetOfferCallback().onNetworkNeeded(request);
         verify(mListener).onNetworkRequested(request);
     }
 
@@ -76,13 +116,14 @@
         mVcnNetworkProvider.unregisterListener(mListener);
 
         final NetworkRequest request = mock(NetworkRequest.class);
-        mVcnNetworkProvider.onNetworkRequested(request, TEST_SCORE_UNSATISFIED, TEST_PROVIDER_ID);
+        verifyRegisterAndGetOfferCallback().onNetworkNeeded(request);
         verifyNoMoreInteractions(mListener);
     }
 
     @Test
     public void testCachedRequestsPassedOnRegister() throws Exception {
         final List<NetworkRequest> requests = new ArrayList<>();
+        final NetworkOfferCallback offerCb = verifyRegisterAndGetOfferCallback();
 
         for (int i = 0; i < 10; i++) {
             // Build unique network requests; in this case, iterate down the capabilities as a way
@@ -91,12 +132,12 @@
                     new NetworkRequest.Builder().clearCapabilities().addCapability(i).build();
 
             requests.add(request);
-            mVcnNetworkProvider.onNetworkRequested(request, i, i + 1);
+            offerCb.onNetworkNeeded(request);
         }
 
         // Remove one, and verify that it is never sent to the listeners.
         final NetworkRequest removed = requests.remove(0);
-        mVcnNetworkProvider.onNetworkRequestWithdrawn(removed);
+        offerCb.onNetworkUnneeded(removed);
 
         mVcnNetworkProvider.registerListener(mListener);
         for (NetworkRequest request : requests) {
diff --git a/tests/vcn/java/com/android/server/vcn/VcnTest.java b/tests/vcn/java/com/android/server/vcn/VcnTest.java
index f681ee1..5d2f9d7 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnTest.java
@@ -242,6 +242,27 @@
         verifyUpdateSubscriptionSnapshotNotifiesGatewayConnections(VCN_STATUS_CODE_SAFE_MODE);
     }
 
+    @Test
+    public void testSubscriptionSnapshotUpdatesMobileDataState() {
+        final NetworkRequestListener requestListener = verifyAndGetRequestListener();
+        startVcnGatewayWithCapabilities(requestListener, TEST_CAPS[0]);
+
+        // Expect mobile data enabled from setUp()
+        assertTrue(mVcn.isMobileDataEnabled());
+
+        final TelephonySubscriptionSnapshot updatedSnapshot =
+                mock(TelephonySubscriptionSnapshot.class);
+        doReturn(TEST_SUB_IDS_IN_GROUP)
+                .when(updatedSnapshot)
+                .getAllSubIdsInGroup(eq(TEST_SUB_GROUP));
+        doReturn(false).when(mTelephonyManager).isDataEnabled();
+
+        mVcn.updateSubscriptionSnapshot(updatedSnapshot);
+        mTestLooper.dispatchAll();
+
+        assertFalse(mVcn.isMobileDataEnabled());
+    }
+
     private void triggerVcnRequestListeners(NetworkRequestListener requestListener) {
         for (final int[] caps : TEST_CAPS) {
             startVcnGatewayWithCapabilities(requestListener, caps);